Nutshell Series

🧩 Modern C# Operators and Syntax Cheatsheet (C# 6–12 & Beyond)

C# has evolved tremendously β€” from a verbose OOP language into a sleek, expressive, and modern programming language.
With each version, Microsoft introduced powerful operators that make code safer, more concise, and easier to read.
Here’s your ultimate guide to all the important operators you should know in modern C#.


πŸ”Ή 1. Null-Coalescing & Safe Navigation

These operators help you handle null values gracefully without throwing exceptions.

Operator Example Description
?? var name = input ?? "Unknown"; Returns the right-hand value if the left-hand is null.
??= name ??= "Default"; Assigns a value only if the variable is null.
?. var len = person?.Name?.Length; Safely navigates through objects that may be null.
?[] var ch = text?[0]; Safely index into arrays or lists.
! obj!.ToString() Null-forgiving operator β€” tells the compiler β€œI know this isn’t null.”
is null / is not null if (value is null) Type-safe null comparison syntax.

πŸ”Ή 2. Pattern Matching (C# 8–12)

Pattern matching enables powerful, declarative logic without repetitive if/else chains.

Pattern Example Description
is if (x is int i) Checks type and assigns if match succeeds.
is not if (obj is not string) Negated type match.
switch expression var area = shape switch { Circle c => c.Radius, Rectangle r => r.Width, _ => 0 }; Expression-based switch replacement.
Property pattern if (person is { Age: > 18, Name: not null }) Matches properties directly.
Tuple pattern (x, y) switch { (0, 0) => "Origin", (_, 0) => "X-axis" } Match multiple values together.
Relational pattern if (n is > 0 and < 10) Match numeric or range conditions.
List pattern (C# 11+) if (nums is [1, 2, .. var rest]) Match array/list structure.
Slice pattern if (arr is [1, .. var middle, 10]) Match prefix and suffix with ...

πŸ”Ή 3. Expression & Lambda Enhancements

These operators make your code more concise and expressive.

Operator Example Description
=> public int Add(int x, int y) => x + y; Expression-bodied members.
() => () => DoWork() Lambda expression syntax.
_ _ = DoSomething(); Discard or wildcard variable.

πŸ”Ή 4. Assignment & Arithmetic Shortcuts

Operator Example Description
+=, -=, *=, /=, %= x += 5; Compound assignments.
++ / -- count++; Increment or decrement.
<<, >>, >>> uint n = a >>> 1; Bit shift operators; >>> is unsigned right shift (C# 11).

πŸ”Ή 5. Range & Index Operators (C# 8+)

Operator Example Description
^ var last = arr[^1]; Index from the end (1 = last element).
.. var sub = arr[1..4]; Slice between start and end indexes.
..^ arr[1..^1] Slice excluding first and last.

πŸ”Ή 6. Type and Reflection Helpers

Operator Example Description
as obj as string Safe type cast, returns null if fails.
is if (obj is MyType) Checks runtime type.
typeof typeof(string) Gets type info at compile time.
nameof nameof(MyProperty) Gets identifier name as string.
default default(int) Returns default value of a type.

πŸ”Ή 7. New Features in C# 12 / 13

Feature Example Description
Primary constructors class Person(string Name, int Age) Compact way to declare constructor + properties.
Collection expressions var list = [1, 2, 3]; Simpler syntax for new List<int> { 1, 2, 3 }.
Inline arrays Span<int> span = [1, 2, 3]; Allocate arrays inline without new.
Default lambda parameters (int x = 0) => x * 2 Lambdas with default argument values.
Params collections (C# 13) void Log(params string[] msgs) Flexible params syntax with collections.

πŸ’‘ Quick Example


string? input = "";
string name = input ?? "Guest";      // null-coalescing
name ??= "Anonymous";                // assign if null

var len = name?.Length ?? 0;         // null-safe access
if (name is { Length: > 5 }) { }     // property pattern

var arr = [1, 2, 3, 4, 5];
var slice = arr[1..^1];              // [2,3,4]

var point = (x: 0, y: 0);
var message = point switch
{
    (0, 0) => "Origin",
    (_, 0) => "X-axis",
    _ => "Other"
};

Modern C# syntax lets you write expressive, safe, and elegant code with minimal boilerplate.
If you’re upgrading from older C# versions, take time to learn the ??, ?.,
pattern matching, and range operators β€” they’ll make your code cleaner and far more readable.

Stay updated with C# 13 and .NET 9 releases, as the language continues evolving toward simplicity and power!

Nutshell Series

Top Non-Microsoft NuGet Packages Every .NET Developer Should Know

When building applications with .NET Core or modern .NET (6, 7, 8, 9), you’ll often rely on powerful third-party libraries to simplify development, improve performance, and enhance maintainability. While Microsoft provides a robust foundation, the .NET ecosystem is enriched by an extensive range of open-source NuGet packages. Below is a comprehensive list of commonly used and highly recommended non-Microsoft libraries that can significantly boost your productivity and project quality.

1. Core Utilities & Helpers

  • LanguageExt – Brings functional programming concepts like Option, Either, and Try to C#. :contentReference[oaicite:0]{index=0}
  • MoreLinq – Extends LINQ with over 100 additional operators and utilities.
  • AutoMapper – Simplifies object-to-object mapping, ideal for DTOs and ViewModels. :contentReference[oaicite:1]{index=1}
  • CSharpFunctionalExtensions – Functional programming tools like Result and Maybe for clean error handling.
  • Humanizer – Makes text and time data more human-readable (e.g., β€œ3 hours ago”).

2. Web Development & API

  • FluentValidation – Elegant and fluent model validation framework. :contentReference[oaicite:2]{index=2}
  • Swashbuckle.AspNetCore – Generates Swagger/OpenAPI documentation for ASP.NET Core APIs.
  • Sieve – Adds filtering, sorting, and pagination support to APIs via query parameters.
  • AspNetCoreRateLimit – Provides IP and client-based rate limiting for APIs.
  • Newtonsoft.Json – Popular JSON serialization library, still widely used for flexible handling.

3. Data Access & ORM

  • Dapper – Lightweight and super-fast micro ORM for raw SQL queries.
  • SqlKata – Fluent SQL query builder supporting multiple databases.
  • LiteDB – Embedded NoSQL database ideal for local or small-scale apps.
  • Z.EntityFramework.Plus.EFCore – Adds caching, auditing, and filters to EF Core.
  • Ardalis.Specification – Implements the specification pattern for repositories.

4. Security & Encryption

  • BCrypt.Net-Next – Provides secure password hashing using the bcrypt algorithm.
  • Sodium.Core – Modern cryptography library built on libsodium.
  • Jwt.Net – Helps create, sign, and validate JSON Web Tokens manually.

5. Logging & Monitoring

  • Serilog – The go-to structured logging framework for .NET. :contentReference[oaicite:3]{index=3}
  • Serilog.Sinks.Seq – Log viewer for Serilog (requires the Seq server).
  • Serilog.Sinks.Elasticsearch – Integrates logging directly with Elasticsearch.
  • App.Metrics – Tracks metrics, health checks, and application performance.

6. Testing & Mocking

  • xUnit – Widely used open-source testing framework.
  • NUnit – Classic and feature-rich test framework for .NET.
  • Moq – Most popular mocking library for unit testing.
  • NSubstitute – Simpler syntax alternative to Moq.
  • FluentAssertions – Provides human-readable, expressive assertions.
  • Verify – Enables snapshot testing for objects, APIs, and JSON outputs.

7. Dependency Injection & Architecture

  • Autofac – Advanced and flexible IoC container for .NET.
  • Scrutor – Assembly scanning and decoration utilities for DI.
  • ABP Framework – Modular application framework for enterprise apps.
  • FeatureToggle – Simple library for enabling or disabling features dynamically.

8. Caching & Performance

9. Messaging & Event Bus

  • MassTransit – Enterprise-grade distributed application messaging framework.
  • Rebus – Simple and lightweight service bus for .NE
Nutshell Series

C# Delegates and Events – Explained

Delegates and events in C# are foundational concepts that enable flexible, type-safe method references and event-driven programming. Many developers find them confusing at first, but this guide explains them in a simple, easy-to-understand way.


What is a Delegate?

A delegate is a type-safe object that holds a reference to a method with a specific signature. Delegates are similar to function pointers in C or C++, but they are object-oriented, type-safe, and secure.

Delegates allow you to pass methods as parameters to other methods or assign methods at runtime.

// Define a delegate
public delegate int MathOperation(int x, int y);

// Method matching the delegate signature
public class Calculator
{
    public int Add(int a, int b) => a + b;
    public int Multiply(int a, int b) => a * b;
}

// Using the delegate
MathOperation operation = new Calculator().Add;
int result = operation(5, 3); // Output: 8

The delegate’s signature (return type + parameters) must match the method it references. This allows delegates to be used as **callbacks** or **pluggable methods** in your code.


Delegate Magic

Delegates are special objects. Unlike normal objects that contain data, a delegate only contains information about a method.
Delegates do not depend on the class of the object they reference; only the method signature matters.
This allows anonymous method invocation and runtime flexibility.


Benefits of Delegates

  • Type-safe: Ensures the method signature matches.
  • Object-oriented: Fully compatible with OOP principles.
  • Secure: No unsafe pointers.
  • Flexible: Can be used to pass methods around, define callbacks, and plug in new behavior dynamically.

Types of Delegates

C# delegates come in two main types:

1. Singlecast Delegate

A singlecast delegate points to a single method at a time.

public delegate void Notify(string message);

public class Messenger
{
    public void ShowMessage(string msg)
    {
        Console.WriteLine(msg);
    }
}

// Using singlecast delegate
Notify notify = new Messenger().ShowMessage;
notify("Hello World!"); // Only calls ShowMessage

2. Multicast Delegate

A multicast delegate can reference multiple methods. Delegates in C# are multicast by default and derived from System.MulticastDelegate.

public delegate void Notify(string message);

public class Messenger
{
    public void ShowMessage(string msg)
    {
        Console.WriteLine(msg);
    }

    public void LogMessage(string msg)
    {
        Console.WriteLine("Log: " + msg);
    }
}

Messenger m = new Messenger();
Notify notify = m.ShowMessage;
notify += m.LogMessage; // Add second method

notify("Hello World!");
// Output:
// Hello World!
// Log: Hello World!

Events in C#

An event is a mechanism that allows a class to notify other classes or objects when something happens.
Events are based on delegates and are widely used in GUI programming, data binding, and event-driven applications.

// Declare a delegate for the event
public delegate void ThresholdReachedEventHandler(int threshold);

// Class that publishes the event
public class Counter
{
    public event ThresholdReachedEventHandler ThresholdReached;
    private int total;

    public void Add(int x)
    {
        total += x;
        if (total >= 10)
        {
            ThresholdReached?.Invoke(total); // Trigger event
        }
    }
}

// Class that subscribes to the event
public class Listener
{
    public void OnThresholdReached(int value)
    {
        Console.WriteLine($"Threshold reached: {value}");
    }
}

// Usage
Counter counter = new Counter();
Listener listener = new Listener();

counter.ThresholdReached += listener.OnThresholdReached;
counter.Add(5);
counter.Add(6); // Triggers event

In this example, the Counter class fires the ThresholdReached event when a certain total is reached,
and the Listener responds to it.


Steps to Define and Use Delegates

  1. Declare a delegate with a method signature.
  2. Create methods matching the delegate signature.
  3. Create a delegate instance and assign methods.
  4. Invoke the delegate.
  5. (Optional) Use multicast delegates or events.

Summary Table

Concept Definition Example
Delegate Type-safe reference to a method MathOperation delegate
Singlecast Delegate Points to a single method Notify notify = messenger.ShowMessage;
Multicast Delegate Points to multiple methods notify += messenger.LogMessage;
Event Notifies other classes when something happens, based on delegates ThresholdReached event
Anonymous Delegate Delegate without a named method notify = delegate(string msg) { Console.WriteLine(msg); };
Callback Method passed via delegate to be called later MathOperation used as parameter
Nutshell Series

Object-Oriented Programming Concepts in C#

Object-Oriented Programming (OOP) helps us design applications using real-world concepts.
Below we explore important OOP principles and relationships in C#, along with examples.


Class

A Class is a blueprint that defines properties and methods.

public class Car
{
    public string Brand { get; set; }
    public void Drive()
    {
        Console.WriteLine($"{Brand} is driving.");
    }
}

Object

An Object is an instance of a class.

Car car1 = new Car { Brand = "Toyota" };
car1.Drive(); // Output: Toyota is driving.

Abstraction

Abstraction focuses on essential details while hiding the complexity.

public abstract class Payment
{
    public abstract void ProcessPayment(decimal amount);
}

public class CreditCardPayment : Payment
{
    public override void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Paid {amount} using Credit Card.");
    }
}

Encapsulation

Encapsulation hides implementation details using access modifiers.

public class BankAccount
{
    private decimal balance;

    public void Deposit(decimal amount) => balance += amount;
    public decimal GetBalance() => balance; // only controlled access
}

Polymorphism

Polymorphism allows the same method name to perform different tasks.

Overloading

public class Calculator
{
    public int Add(int a, int b) => a + b;
    public double Add(double a, double b) => a + b;
}

Overriding

public class Animal
{
    public virtual void Speak() => Console.WriteLine("Animal sound");
}

public class Dog : Animal
{
    public override void Speak() => Console.WriteLine("Bark");
}

Inheritance

Inheritance lets a class reuse properties and methods from a parent class.

public class Vehicle
{
    public void Start() => Console.WriteLine("Vehicle started");
}

public class Bike : Vehicle
{
    public void RingBell() => Console.WriteLine("Bell rings!");
}

Sealed Class

A sealed class cannot be inherited.

public sealed class Logger
{
    public void Log(string msg) => Console.WriteLine(msg);
}

Multiple Inheritance

C# does not allow multiple inheritance of classes, but interfaces provide it.

public interface IFly
{
    void Fly();
}

public interface ISwim
{
    void Swim();
}

public class Duck : IFly, ISwim
{
    public void Fly() => Console.WriteLine("Duck flying");
    public void Swim() => Console.WriteLine("Duck swimming");
}

Abstract Class

An abstract class cannot be instantiated directly; it must be inherited.

public abstract class Shape
{
    public abstract double Area();
}

public class Circle : Shape
{
    public double Radius { get; set; }
    public override double Area() => Math.PI * Radius * Radius;
}

Generalization

Generalization groups common features into a parent class.

public class Teacher : Person { }
public class Student : Person { }

public class Person
{
    public string Name { get; set; }
}

Association

Association represents a relationship between classes.

public class Customer
{
    public string Name { get; set; }
}

public class Order
{
    public Customer OrderedBy { get; set; }
}

Aggregation

Aggregation is a weak β€œwhole-part” relationship.

public class Department
{
    public List Employees { get; set; } = new List();
}

public class Employee
{
    public string Name { get; set; }
}

Composition

Composition is a strong β€œwhole-part” relationship. If the whole is destroyed, parts are also destroyed.

public class House
{
    private Room room;
    public House()
    {
        room = new Room(); // Room cannot exist without House
    }
}

public class Room { }

Multiplicity

Multiplicity defines how many objects can participate in a relationship.

public class Library
{
    public List Books { get; set; } = new List();
}

public class Book { }

Interface

An interface defines a contract without implementation.

public interface INotifier
{
    void Send(string message);
}

public class EmailNotifier : INotifier
{
    public void Send(string message)
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

Summary Table

Concept Definition Example
Class Blueprint of objects Car
Object Instance of a class car1 = new Car()
Abstraction Focus on essentials Payment (abstract)
Encapsulation Data hiding BankAccount with private balance
Polymorphism Many forms Calculator.Add() overloads, Dog.Speak() override
Inheritance Reuse parent properties Bike inherits Vehicle
Sealed Class Cannot be inherited Logger
Multiple Inheritance Via interfaces Duck : IFly, ISwim
Abstract Class Must be inherited Shape
Generalization Group common features Person parent for Student, Teacher
Association Relationship Order β†’ Customer
Aggregation Weak whole-part Department β†’ Employees
Composition Strong whole-part House β†’ Room
Multiplicity How many objects Library β†’ Books
Interface Contract without implementation INotifier
Nutshell Series

πŸš€ Software Design Principles Every Developer Should Know

Good software design isn’t just about writing code that works β€” it’s about writing clean, maintainable, and scalable code.
To achieve this, developers follow a set of guiding principles that help keep complexity low and quality high.
Here’s a consolidated list of the most important software design principles with explanations and C# examples.


1. 🧩 KISS (Keep It Simple, Stupid)

The KISS principle reminds us to keep software as simple as possible. Avoid unnecessary complexity, write clear code, and focus only on what’s needed.

// ❌ Bad: Over-engineered
if (user.Age > 18 && (user.Country == "US" || user.Country == "UK" || user.Country == "CA"))
{
    AllowAccess();
}
else
{
    DenyAccess();
}

// βœ… Good: Simple and clear
if (user.IsAdult())
{
    AllowAccess();
}

2. πŸ” DRY (Don’t Repeat Yourself)

The DRY principle means you should avoid code duplication.
Instead, encapsulate common logic in functions, classes, or constants.

// ❌ Bad: Duplicate logic
Console.WriteLine("Welcome, John");
Console.WriteLine("Welcome, Mary");

// βœ… Good: Reusable method
void Greet(string name)
{
    Console.WriteLine($"Welcome, {name}");
}

Greet("John");
Greet("Mary");

3. 🚫 YAGNI (You Aren’t Gonna Need It)

Don’t implement features β€œjust in case.” Only build what you need now to keep the system simple and maintainable.

// ❌ Bad: Adding unnecessary parameters for future features
decimal CalculateDiscount(decimal price, string type = "standard", int loyaltyPoints = 0, bool seasonal = false, bool futureFeature = false)
{
    // Too much complexity
    return price;
}

// βœ… Good: Only what is needed now
decimal CalculateDiscount(decimal price, string type = "standard")
{
    return price;
}

4. πŸ“ SOLID Principles

SOLID is a collection of five principles for building maintainable and extensible object-oriented software:

  • Single Responsibility Principle (SRP): A class should have only one reason to change.
  • Open/Closed Principle (OCP): Classes should be open for extension but closed for modification.
  • Liskov Substitution Principle (LSP): Subclasses should be usable in place of their base classes.
  • Interface Segregation Principle (ISP): Clients shouldn’t be forced to depend on methods they don’t use.
  • Dependency Inversion Principle (DIP): Depend on abstractions, not concrete implementations.
// βœ… Example: Dependency Inversion
public interface IMessageService
{
    void Send(string message);
}

public class EmailService : IMessageService
{
    public void Send(string message) 
    {
        Console.WriteLine($"Email sent: {message}");
    }
}

public class Notification
{
    private readonly IMessageService _service;

    public Notification(IMessageService service)
    {
        _service = service;
    }

    public void Notify(string message)
    {
        _service.Send(message);
    }
}

// Usage
var emailService = new EmailService();
var notification = new Notification(emailService);
notification.Notify("Hello, World!");

5. πŸ€” Principle of Least Astonishment (POLA)

Software should behave in a way that is consistent with user expectations.
Use familiar terminology, intuitive design, and clear error messages.

// ❌ Bad: Confusing error
throw new Exception("ERR_451_USER_FAIL");

// βœ… Good: Clear error
throw new UnauthorizedAccessException("User authentication failed. Please check your password.");

6. 🧱 Principle of Modularity

Design software as independent, reusable modules. This makes it easier to maintain, test, and scale.

// Example project structure
- Auth/
- Payment/
- Notifications/

7. 🎭 Principle of Abstraction

Hide unnecessary details and expose only essential features.
Abstraction helps simplify usage and prevents users from depending on internal complexity.

public class EmailSender
{
    public void SendEmail(string to, string subject, string body)
    {
        // Hides SMTP details
        Console.WriteLine($"Sending email to {to}: {subject}");
    }
}

8. πŸ”’ Principle of Encapsulation

Encapsulation hides the internal state of an object and exposes behavior only through well-defined interfaces.

public class BankAccount
{
    private decimal _balance;

    public void Deposit(decimal amount)
    {
        _balance += amount;
    }

    public decimal GetBalance()
    {
        return _balance;
    }
}

9. πŸ“‰ Principle of Least Knowledge (Law of Demeter)

A module should know as little as possible about other modules. This reduces coupling and increases flexibility.

// ❌ Bad: Too much knowledge
var creditLimit = order.Customer.Account.CreditLimit;

// βœ… Good: Ask the object directly
var creditLimit = order.GetCustomerCreditLimit();

10. πŸ”— Low Coupling & High Cohesion

Low Coupling: Modules should have minimal dependencies on each other.
High Cohesion: Each module should serve a single, well-defined purpose.

// βœ… Example: High cohesion, low coupling
public class InvoiceGenerator
{
    public string Generate(Order order)
    {
        return $"Invoice for {order.Id}";
    }
}

public class EmailService
{
    public void SendInvoice(string invoice)
    {
        Console.WriteLine($"Invoice sent: {invoice}");
    }
}

🎯 Conclusion

These principles are not strict rules but guidelines to help you write clean, scalable, and maintainable software.
By applying KISS, DRY, YAGNI, SOLID, and others consistently, you’ll reduce bugs, simplify maintenance, and build systems that are easier to extend as requirements evolve.

Nutshell Series

πŸ”§ Dependency Injection (DI) Explained: Transient vs Scoped vs Singleton

Dependency Injection (DI) is a design pattern that simplifies how objects and their dependencies are managed in an application. Instead of classes creating their own dependencies, DI provides those dependencies from the outside. This makes applications cleaner, testable, and maintainable.

βš™οΈ What is Dependency Injection?

At its core, DI is about inversion of control: your classes don’t create what they need; a container provides them. This container decides:

  • How to create objects
  • When to reuse objects
  • How to dispose of them when no longer needed

πŸ“Œ Service Lifetimes

When you register services in a DI container, you usually choose a lifetime:

  • Transient – A new instance is created every time it’s requested.
  • Scoped – One instance is created per request (or unit of work).
  • Singleton – A single instance is shared across the entire application lifetime.

πŸ”„ Transient

A new instance is created each time the service is requested. Best for lightweight, stateless services.

// Example in C#
services.AddTransient<IEmailService, EmailService>();

πŸ“‚ Scoped

A new instance is created once per request, but reused within that request. Useful for services like database contexts.

// Example in C#
services.AddScoped<IDbContext, AppDbContext>();

β™Ύ Singleton

A single instance is created and reused for the entire lifetime of the application. Perfect for loggers, configuration readers, and caching providers.

// Example in C#
services.AddSingleton<ILogger, Logger>();

πŸ“Š When to Use Each?

  • Transient – For short-lived, stateless operations (e.g., helpers, formatters).
  • Scoped – For services tied to a single request or unit of work (e.g., DbContext).
  • Singleton – For shared state or expensive-to-create services (e.g., loggers, configuration, caching).

πŸš€ Conclusion

Dependency Injection ensures better code structure, easier testing, and improved flexibility.
Choosing the right lifetime β€” Transient, Scoped, or Singleton β€” helps you balance performance with resource management.

Security

Cybersecurity Frameworks & Standards: Quick Reference

Use this cheat sheet to quickly match popular cybersecurity frameworks and regulations to the industries that rely on them. Each entry includes a short description to help you pick the right control set for audits, assessments, or roadmap planning.

At a Glance

Cybersecurity Frameworks & Standards Cheat Sheet
Framework / Standard Primary Industry / Sector Brief Description
ISO 27001 Finance, healthcare, IT, government International standard for establishing, implementing, maintaining, and continually improving an ISMS (information security management system).
NIST Cybersecurity Framework (NIST CSF) Critical infrastructure (energy, healthcare, finance, transportation) Risk-based guidance organized around Identify, Protect, Detect, Respond, and Recover functions.
HIPAA Healthcare providers, health plans, clearinghouses U.S. regulation protecting the privacy and security of protected health information (PHI).
PCI DSS Merchants, financial institutions, payment processors Security standard for safeguarding cardholder data and reducing payment card fraud.
GDPR Any organization handling EU residents’ personal data EU regulation granting data privacy rights and setting obligations for data controllers and processors.
CIS Controls Organizations of all sizes and sectors Prioritized set of practical security controls to defend against common cyberattacks.
HITRUST CSF Healthcare organizations and business associates Certifiable framework that harmonizes requirements from HIPAA, NIST, ISO, and others.
COBIT All industries IT governance and management framework aligning technology with business objectives.
NERC CIP Electric utilities, power generation companies Standards for protecting the bulk electric system in North America.
FISMA U.S. federal agencies and contractors U.S. law requiring comprehensive information security programs for federal information and systems.
SOC 2 SaaS providers, managed service providers, data centers, cloud platforms Attestation report evaluating controls against Trust Services Criteria: security, availability, processing integrity, confidentiality, and privacy.
CCPA Businesses collecting personal information from California residents California law providing consumer data privacy rights and business obligations.
CISA Telecoms Framework U.S. telecommunications providers Guidance and best practices for securing telecommunications infrastructure and services.
NIST SP 800-53 U.S. federal agencies and organizations Catalog of security and privacy controls for federal information systems and organizations.
NIST SP 800-171 Non-federal organizations handling CUI Requirements to protect controlled unclassified information (CUI) for the U.S. government.
UK Telecoms (Security) Act 2021 Telecommunications companies operating in the United Kingdom Legal obligations to strengthen security and resilience of UK telecom networks.

How to Use This Cheat Sheet

  • General maturity: Start with ISO 27001 or NIST CSF for a broad security program.
  • Industry specifics: Apply HIPAA/HITRUST for healthcare, PCI DSS for payments, and NERC CIP for energy.
  • Privacy: Map your data practices to GDPR and CCPA obligations.
  • Cloud & services: Use SOC 2 to demonstrate assurance to customers and partners.

Notes & Caveats

  • Frameworks are complementaryβ€”organizations often implement more than one.
  • Scope and applicability depend on your data types, geography, and contractual obligations.
  • Always consult current official documentation before audits or certifications.

Last updated: August 2025.

.Net, Azure, C#, Cloud Design Patterns, Nutshell Series, Technology and tricks

SOLID Principles (with c# examples) – Nutshell

SOLID Principles in .NET – Explained with Examples

SOLID Principles in .NET – Explained with Examples

The SOLID principles are five guidelines that help developers write clean, maintainable, and extensible object-oriented code.
Below we go through each principle with concise C# examples showing what is bad and what is good.


1. Single Responsibility Principle (SRP)

Rule: A class should have only one reason to change.

Bad Example

public class User
{
    public string Name { get; set; }
    public string Email { get; set; }

    public void SendEmail(string message)
    {
        // Logic to send email
    }
}

Here, User manages both data and email-sending logic. It has more than one responsibility.

Good Example

public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public class EmailService
{
    public void SendEmail(string email, string message)
    {
        // Logic to send email
    }
}

User now manages only user data, while EmailService handles emails. Each has one responsibility.


2. Open/Closed Principle (OCP)

Rule: Classes should be open for extension but closed for modification.

Bad Example

public class AreaCalculator
{
    public double CalculateArea(object shape)
    {
        if (shape is Circle c)
            return Math.PI * c.Radius * c.Radius;
        if (shape is Square s)
            return s.Side * s.Side;
        return 0;
    }
}

Adding a new shape forces us to modify AreaCalculator, violating OCP.

Good Example

public abstract class Shape
{
    public abstract double Area();
}

public class Circle : Shape
{
    public double Radius { get; set; }
    public override double Area() => Math.PI * Radius * Radius;
}

public class Square : Shape
{
    public double Side { get; set; }
    public override double Area() => Side * Side;
}

Each shape implements its own Area. Adding new shapes doesn’t require changing existing code.


3. Liskov Substitution Principle (LSP)

Rule: Derived classes should be substitutable for their base types without breaking behavior.

Bad Example

public class Bird
{
    public virtual void Fly() { }
}

public class Ostrich : Bird
{
    public override void Fly()
    {
        throw new NotImplementedException();
    }
}

Substituting Ostrich for Bird breaks behavior, since ostriches cannot fly.

Good Example

public abstract class Shape
{
    public abstract double Area();
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public override double Area() => Width * Height;
}

Rectangle correctly substitutes Shape and can be used anywhere Shape is expected.


4. Interface Segregation Principle (ISP)

Rule: Don’t force classes to implement methods they don’t use.

Bad Example

public interface IMachine
{
    void Print();
    void Scan();
}

public class SimplePrinter : IMachine
{
    public void Print() { }
    public void Scan() { throw new NotImplementedException(); }
}

SimplePrinter doesn’t support scanning, yet it is forced to implement it.

Good Example

public interface IPrinter
{
    void Print();
}

public interface IScanner
{
    void Scan();
}

public class MultiFunctionPrinter : IPrinter, IScanner
{
    public void Print()
    {
        Console.WriteLine("Printing document...");
    }

    public void Scan()
    {
        Console.WriteLine("Scanning document...");
    }
}

Now classes only implement what they need. A simple printer just implements IPrinter.


5. Dependency Inversion Principle (DIP)

Rule: High-level modules should not depend on low-level modules. Both should depend on abstractions.

Bad Example

public class DataManager
{
    private readonly SQLDatabase _db = new SQLDatabase();

    public void Save(string data)
    {
        _db.SaveData(data);
    }
}

public class SQLDatabase
{
    public void SaveData(string data) { }
}

DataManager is tightly coupled to SQLDatabase, making it hard to switch databases.

Good Example

public interface IDatabase
{
    void SaveData(string data);
}

public class SQLDatabase : IDatabase
{
    public void SaveData(string data)
    {
        // Save data to SQL database
    }
}

public class DataManager
{
    private readonly IDatabase _database;
    public DataManager(IDatabase database)
    {
        _database = database;
    }

    public void Save(string data)
    {
        _database.SaveData(data);
    }
}

DataManager now depends on IDatabase. Any database implementation can be injected.


Key Takeaways

  • SRP β€” One class, one responsibility.
  • OCP β€” Extend, don’t modify working code.
  • LSP β€” Subtypes must preserve base-class behavior.
  • ISP β€” Keep interfaces small and focused.
  • DIP β€” Depend on abstractions, not concretions.