Skip to the content.

C# Basics

Click ★ if you like the project. Your contributions are heartily ♡ welcome.



Table of Contents

L1: Fundamental (Entry-Level / Junior)

Focus: Syntax, basic language constructs, and core type system.

L2: Intermediate (Junior-Mid / Developer)

Focus: Object-oriented programming, collections, and common language features.

L3: Advanced (Mid-Senior / Lead)

Focus: Concurrency, memory management, and advanced language features.

L4: Expert (Senior / Architect)

Focus: Architecture, scalability, performance, and deployment strategies.


# 1. FUNDAMENTALS


Q. What is C# and what are its main features?

C# is a modern, object-oriented, type-safe programming language developed by Microsoft as part of the .NET platform (formerly .NET Core). The current stable versions are .NET 10 (LTS, released November 2025) and C# 14, designed for building web, desktop, mobile, cloud, gaming, and AI applications.

Main features of C#:

// C# 12 Primary Constructor
public class Person(string name, int age)
{
    public string Name { get; } = name;
    public int Age { get; } = age;
}
string? nullableName = null;        // explicitly nullable
string nonNullableName = "Pradeep"; // cannot be null
object shape = new Circle(5);
string description = shape switch
{
    Circle { Radius: > 10 } => "Large circle",
    Circle c => $"Circle with radius {c.Radius}",
    _ => "Unknown shape"
};
public record Product(string Name, decimal Price);
var p1 = new Product("Laptop", 999.99m);
var p2 = p1 with { Price = 899.99m }; // non-destructive mutation
await foreach (var item in GetStreamAsync())
    Console.WriteLine(item);
T Sum<T>(T[] numbers) where T : INumber<T>
    => numbers.Aggregate(T.Zero, (acc, n) => acc + n);
int[] array = [1, 2, 3];
List<string> list = ["a", "b", "c"];
Span<int> span = [10, 20, 30];
↥ back to top

Q. What are the different types of data types available in C#?

C# offers a variety of data types categorized as value types, reference types, and pointer types. Value types store data directly, while reference types store memory addresses to the actual data.

graph TD
    A["C# Data Type"] --> B["Value Type"]
    A --> C["Reference Type"]

    B --> D["Simple Types"]
    B --> E["Enum Types"]
    B --> F["Struct Type"]
    B --> G["Nullable Type"]

    C --> H["Class Types"]
    C --> I["Interface Types"]
    C --> J["Array Types"]
    C --> K["Delegate Types"]

1. Value Types:

These store data directly and include:

2. Reference Types:

These store references to the actual data:

3. Pointer Types:

Used in unsafe code for direct memory manipulation (e.g., int, char).

4. Nullable Types:

Allow value types to represent null (e.g., int?, bool?).

Example:

int number = 10;              // Value type
string name = "Pradeep";      // Reference type
int? age = null;              // Nullable value type
int[] numbers = {10, 20, 30}; // Array (reference type)
nint nativeInt = 42;          // Native-sized integer (C# 9+)
Int128 bigNum = Int128.MaxValue; // 128-bit integer (.NET 7+)
Half half = (Half)3.14f;      // 16-bit float (.NET 5+)
record struct Point(int X, int Y); // Record struct (C# 10+)
Point p = new Point(1, 2);
↥ back to top

Q. Can primitive data types be stored in heap?

Yes, primitive data types in C# (such as int, float, bool, etc.) are value types and are typically stored on the stack when used as local variables. However, they can be stored on the heap in certain scenarios:

Example:

int x = 10; // Stored on the stack

object obj = x; // Boxing: x is copied to the heap
↥ back to top

Q. It is possible to store mixed datatypes such as int, string, float, char all in one array?

Yes, you can store multiple data types in a System.Array if the array is declared as type object[] or Array (the base class). This is because all types in C# ultimately derive from object. However, this approach loses type safety and requires casting when retrieving values.

Example:

object[] mixedArray = { 10, "Hi", 3.14, true };

foreach (var item in mixedArray)
{
    Console.WriteLine(item);
}

Output:

10
Hi
3.14
True

Note:

↥ back to top

Q. What is an extension method in C# and how is it implemented?

An extension method adds functionality to an existing type without modifying its source code or creating a subclass. It is defined as a static method in a static class, with the first parameter prefixed with this. C# 14 also introduces extension members (a superset of extension methods).

1. Traditional extension method (all versions):

public static class StringExtensions
{
    public static string Reverse(this string input)
    {
        char[] chars = input.ToCharArray();
        Array.Reverse(chars);
        return new string(chars);
    }

    public static bool IsNullOrEmpty(this string? input)
        => string.IsNullOrEmpty(input);
}

// Usage
string text = "Hello";
Console.WriteLine(text.Reverse());       // Output: olleH
Console.WriteLine("".IsNullOrEmpty());   // Output: True

2. Extension methods with generic constraints (C# 11+):

public static class NumberExtensions
{
    public static T Clamp<T>(this T value, T min, T max)
        where T : System.Numerics.INumber<T>
        => T.Max(min, T.Min(value, max));
}

Console.WriteLine(15.Clamp(0, 10)); // Output: 10
Console.WriteLine(5.5.Clamp(0.0, 10.0)); // Output: 5.5

3. Extension members (C# 14 — new syntax):

C# 14 introduces a new extension block syntax that allows properties, static methods, and operators as extensions — not just instance methods:

extension(string s) StringEx
{
    public int WordCount => s.Split(' ').Length;
    public static string Repeat(string text, int times) => string.Concat(Enumerable.Repeat(text, times));
}

string sentence = "Hello World";
Console.WriteLine(sentence.WordCount); // Output: 2 (extension property)
Console.WriteLine(StringEx.Repeat("ha", 3)); // Output: hahaha

Note: Extension methods are resolved at compile time. They are found only when the namespace of the static class is imported with using.

↥ back to top

Q. What is a generic type in C# and why is it used?

Generics allow you to write classes, methods, delegates, and interfaces that work with any data type, maintaining compile-time type safety and avoiding boxing/unboxing overhead. With C# 11 Generic Math, generic code can now also perform arithmetic operations across numeric types.

Features:

1. Generic class:

public class Repository<T>
{
    private readonly List<T> _items = new();

    public void Add(T item) => _items.Add(item);
    public IReadOnlyList<T> GetAll() => _items;
}

var repo = new Repository<string>();
repo.Add("Pradeep");
repo.Add("Kumar");
Console.WriteLine(repo.GetAll().Count); // Output: 2

2. Generic method:

T Max<T>(T a, T b) where T : IComparable<T>
    => a.CompareTo(b) >= 0 ? a : b;

Console.WriteLine(Max(10, 20));       // Output: 20
Console.WriteLine(Max("apple", "mango")); // Output: mango

3. Generic Math (C# 11+, .NET 7+):

Write arithmetic algorithms that work over all numeric types using interfaces like INumber<T>, IAdditionOperators<T,T,T>, etc.

using System.Numerics;

T Sum<T>(IEnumerable<T> values) where T : INumber<T>
    => values.Aggregate(T.Zero, (acc, v) => acc + v);

Console.WriteLine(Sum(new[] { 1, 2, 3, 4 }));         // Output: 10
Console.WriteLine(Sum(new[] { 1.5, 2.5, 3.0 }));      // Output: 7
Console.WriteLine(Sum(new[] { 1m, 2m, 3.5m }));       // Output: 6.5

4. Generic constraints:

Constraint Meaning
where T : class T must be a reference type
where T : struct T must be a value type
where T : new() T must have a public parameterless constructor
where T : IDisposable T must implement IDisposable
where T : INumber<T> T must be a numeric type (C# 11+)
where T : notnull T cannot be a nullable type (C# 8+)
↥ back to top

Q. What is an anonymous method in C# and how is it used?

An anonymous method is a named method without a name, defined using the delegate keyword. It's used for inline code, particularly when you need to create a delegate instance without explicitly defining a named method first.

Anonymous methods are helpful when you need a small, one-time-use method and don't want to clutter the code with a separate named method.

Example:

// Declare a delegate
delegate void Greet(string name);

class Program
{
    static void Main()
    {
        // Assign an anonymous method to the delegate
        Greet greet = delegate(string name)
        {
            Console.WriteLine("Hello, " + name + "!");
        };

        greet("Pradeep"); // Output: Hello, Pradeep!
    }
}

Usage:

Note:

Lambda expressions are a more concise way to achieve the same functionality as anonymous methods. Lambda expressions are generally preferred over anonymous methods in modern C# code.

↥ back to top

Q. What is the difference between a method and a function in C#?

In C#, a function is always part of a class, making it a method. A function is a reusable block of code that can be called to perform a specific task. A method, in object-oriented programming, is associated with an object or class, often operating on the object's data or state.

Example:

public class Calculator
{
    // This is a method
    public int Add(int a, int b)
    {
        return a + b;
    }
}
↥ back to top

Q. What is the Common Language Runtime (CLR) in C#?

The Common Language Runtime (CLR) — known as CoreCLR in modern .NET (formerly .NET Core) — is the core execution engine for .NET applications. It manages execution, memory, security, and cross-language interoperability. As of .NET 10, CoreCLR is cross-platform (Windows, Linux, macOS, Android, iOS, WebAssembly).

graph TD
    A["C# / F# / VB.NET Source Code"] --> B["Roslyn Compiler"]
    B --> C["IL Code + Metadata\n(Assembly .dll / .exe)"]
    C --> D["CoreCLR Runtime"]
    D --> E["JIT Compiler\n(Tiered + Dynamic PGO)"]
    D --> F["Garbage Collector\n(Generational GC)"]
    D --> G["Type System / CTS"]
    D --> H["Exception Handling"]
    D --> I["Security & Verification"]
    E --> J["Native Machine Code\n(cached)"]
    J --> K["CPU Execution"]

Key Functions:

Example — inspecting runtime info (.NET 10):

using System.Runtime.InteropServices;

Console.WriteLine(RuntimeInformation.FrameworkDescription); // .NET 10.0.x
Console.WriteLine(RuntimeInformation.OSDescription);        // e.g., Linux 6.x
Console.WriteLine(RuntimeInformation.ProcessArchitecture);  // X64 / Arm64
↥ back to top

Q. What is the purpose of namespaces in C#?

In C#, namespaces primarily serve two crucial purposes: organizing code and preventing naming conflicts. They provide a hierarchical structure for grouping related classes, interfaces, and other types, making code easier to read, maintain, and understand.

Features:

Example:

namespace MyCompany.Project.Utilities
{
    public class Logger
    {
        // Logger implementation
    }
}

You can then use the using directive to access types within a namespace:

using MyCompany.Project.Utilities;

Logger logger = new Logger();
↥ back to top

Q. What is the purpose of the using statement in C#?

The using statement in C# ensures that objects implementing IDisposable are properly disposed when they go out of scope, even if an exception is thrown. .NET 10 supports two syntaxes:

1. Traditional using block (all .NET versions):

using (var file = new StreamReader("example.txt"))
{
    string content = file.ReadToEnd();
    // file is automatically disposed when the block exits
}

2. using declaration (C# 8+, .NET Core 3+):

No braces needed — the object is disposed at the end of the enclosing scope. This is the preferred modern style.

using var file = new StreamReader("example.txt");
string content = file.ReadToEnd();
// file is automatically disposed here (end of method/block)

3. await using for async disposal (C# 8+):

For objects implementing IAsyncDisposable (e.g., async streams, HttpClient, DbContext):

await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
// connection is asynchronously disposed at end of scope

Key points:

↥ back to top

Q. What are properties in C# and how are they used?

Properties in C# are special members that provide a flexible mechanism to read, write, or compute the values of private fields. Modern .NET adds init-only properties and required properties.

1. Traditional property with get/set:

public class Person
{
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    // Auto-implemented property
    public int Age { get; set; }

    // Read-only computed property
    public string Info => $"Name: {Name}, Age: {Age}";
}

2. init-only properties (C# 9+):

Can only be set during object initialization (in constructors or object initializers), making them immutable after construction. Ideal for DTOs and value objects.

public class Product
{
    public string Name { get; init; }
    public decimal Price { get; init; }
}

var p = new Product { Name = "Laptop", Price = 999.99m };
// p.Name = "Phone"; // Compile error — init-only

3. required properties (C# 11+):

Forces callers to set the property in an object initializer — a compile-time guarantee of initialization.

public class Employee
{
    public required string Name { get; init; }
    public required int Id { get; init; }
    public string Department { get; init; } = "General";
}

// Must provide Name and Id — compiler enforces it
var emp = new Employee { Name = "Pradeep", Id = 101 };
Console.WriteLine(emp.Info); // Output: Name: Pradeep, Age: 30

4. Primary Constructor properties (C# 12+):

public class Point(int x, int y)
{
    public int X { get; } = x;
    public int Y { get; } = y;
    public override string ToString() => $"({X}, {Y})";
}
↥ back to top

Q. What are the Arrays in C#.Net?

In C#.NET, an array is a data structure that stores a fixed-size sequential collection of elements of the same type. Arrays are used to store multiple values in a single variable, instead of declaring separate variables for each value.

Key Points:

Example:

// Declaration
int[] numbers;

// Initialization
numbers = new int[5]; // Array of 5 integers

// Declaration and initialization together
string[] names = { "Alice", "Bob", "Charlie" };

Types of Arrays:

1. Single-Dimensional Array:

A linear array with one row of elements.

int[] arr = new int[3] { 10, 20, 30 };

foreach(int num in arr) {
    Console.WriteLine(num); // Output: 10, 20, 30
}

2. Multi-Dimensional Array:

An array with more than one dimension (e.g., matrix).

int[,] matrix = new int[2, 3] { {1, 2, 3}, {4, 5, 6} };

3. Jagged Array:

An array of arrays, where each inner array can have a different length.

int[][] jagged = new int[2][];
jagged[0] = new int[3] { 1, 2, 3 };
jagged[1] = new int[2] { 4, 5 };

Example:

int[] numbers = { 5, 10, 15 };
for (int i = 0; i < numbers.Length; i++)
{
    Console.WriteLine(numbers[i]);
}
↥ back to top

Q. What is the difference between the System.Array.CopyTo() and System.Array.Clone()?

The key difference between System.Array.CopyTo() and System.Array.Clone() lies in their purpose and how they create a copy of the array. Array.CopyTo() copies the elements of an array to a destination array, while Array.Clone() creates a new, independent array that is a shallow copy of the original.

System.Array.CopyTo():

Example:

int[] source = { 10, 20, 30 };
int[] destination = new int[3];

source.CopyTo(destination, 0); // Copy

for (int i = 0; i < destination.Length; i++)
{
    Console.WriteLine(destination[i]); // Output: 10, 20, 30
}

System.Array.Clone():

Example:

int[] source = { 10, 20, 30 };
int[] clone = (int[])source.Clone(); // Clone

for (int i = 0; i < clone.Length; i++)
{
    Console.WriteLine(clone[i]); // Output: 10, 20, 30
}
↥ back to top

Q. What is jagged array in C#.Net?

A jagged array in C#.NET is an array whose elements are arrays themselves, and these inner arrays can have different lengths. It is also known as an “array of arrays.”

Unlike a multidimensional array (e.g., int[,]), a jagged array allows each row to have a different number of columns.

Example:

// Declare a jagged array with 2 rows
int[][] jaggedArray = new int[2][];

// Initialize each row with different lengths
jaggedArray[0] = new int[] { 10, 20, 30 };
jaggedArray[1] = new int[] { 40, 50 };

// Accessing elements
Console.WriteLine(jaggedArray[0][1]); // Output: 20
Console.WriteLine(jaggedArray[1][0]); // Output: 40
↥ back to top

Q. How can you sort the elements of the array in descending order?

To sort the elements of an array in descending order in C#, you can use the Array.Sort method with a custom comparer, or use LINQ for a more concise approach.

Example: Using Array.Sort with a comparer

int[] numbers = { 5, 2, 8, 1, 3 };
Array.Sort(numbers, (a, b) => b.CompareTo(a)); // Sorts in descending order

foreach (int num in numbers)
{
    Console.WriteLine(num); // Output: 8 5 3 2 1
}

Example: Using LINQ

int[] numbers = { 5, 2, 8, 1, 3 };
var descending = numbers.OrderByDescending(n => n).ToArray();

foreach (int num in descending)
{
    Console.WriteLine(num); // Output: 8 5 3 2 1
}
↥ back to top

Q. What is the difference between var and dynamic types?

The var and dynamic are keywords used for declaring variables, but they differ significantly in their approach to type checking and type inference.

var is used for implicit typing, where the compiler infers the variable's type from the initializer value, while dynamic is a type itself that bypasses compile-time type checking, making type verification occur at runtime.

var (Implicit Typing):

Example:

var number = 10;        // number is inferred as int
var name = "Pradeep";   // name is inferred as string
// number = "Kumar";    // Compile-time error: cannot assign string to int

dynamic (Dynamic Typing):

Example:

dynamic value = 10;
value = "Pradeep";     // Allowed: type can change at runtime

Console.WriteLine(value.NonExistentMethod()); // Compiles, but throws runtime exception if method doesn\'t exist

Key Differences:

Feature var dynamic
Type Checking Compile time Runtime
Type Determination Inferred from initializer Determined at runtime
Type Changes Fixed after initialization Allowed to change at runtime
Error Detection Compile time Runtime
↥ back to top

Q. What is a struct in C#?

A struct (structure) is a value type that groups related variables into a single unit. It is stored on the stack (unless boxed or embedded in a reference type). Modern C# introduces record structs for immutable value objects.

Differences from Classes:

1. Traditional struct:

public struct Point
{
    public int X;
    public int Y;

    public Point(int x, int y) { X = x; Y = y; }
}

Point p1 = new Point(10, 20);
Point p2 = p1; // copy
p2.X = 30;
Console.WriteLine(p1.X); // Output: 10 (unchanged)
Console.WriteLine(p2.X); // Output: 30

2. readonly struct (C# 7.2+):

All members must be read-only. The compiler enforces immutability and enables performance optimizations (avoids defensive copies).

public readonly struct Temperature(double celsius)
{
    public double Celsius { get; } = celsius;
    public double Fahrenheit => Celsius * 9 / 5 + 32;
    public override string ToString() => $"{Celsius}°C";
}

3. record struct (C# 10+):

Combines the value semantics of a struct with the conciseness of records. Provides value-based equality and a ToString() override automatically.

public record struct Coordinate(double Latitude, double Longitude);

var c1 = new Coordinate(12.97, 77.59);
var c2 = new Coordinate(12.97, 77.59);

Console.WriteLine(c1 == c2);   // True (value-based equality)
Console.WriteLine(c1);         // Output: Coordinate { Latitude = 12.97, Longitude = 77.59 }

// Non-destructive mutation with 'with'
var c3 = c1 with { Latitude = 28.61 };

4. readonly record struct (C# 10+):

Fully immutable value type with value equality — preferred for small, data-centric types.

public readonly record struct Money(decimal Amount, string Currency);

var price = new Money(9.99m, "USD");
var discounted = price with { Amount = 7.99m };
↥ back to top

Q. What is the difference between abstract and virtual methods?

Abstract methods are declared without an implementation, requiring derived classes to provide one. Virtual methods, on the other hand, come with a default implementation that can be overridden by derived classes.

Abstract Methods:

Virtual Methods:

↥ back to top

Q. What is the difference between out and ref parameters?

The out and ref keywords are both used to pass arguments by reference to methods, but they have important differences:

ref parameter:

out parameter:

Example:

public class Program
{
    static void RefExample(ref int x)
    {
      x = x + 10;
    }

    static void OutExample(out int y)
    {
      y = 20; // Must assign before returning
    }

	public static void Main(string[] args)
	{

      // Using 'ref'
      int a = 5;
      RefExample(ref a); // a is now 15

      // Using 'out'
      int b;
      OutExample(out b); // b is now 20

      Console.WriteLine($"a = {a}"); // Output: a = 15
      Console.WriteLine($"b = {b}"); // Output: b = 20
	}
}
↥ back to top

Q. What is a Tuple in C#?

A Tuple is a data structure that stores a fixed number of elements of potentially different types in a single object. Modern C# uses value tuples (C# 7+) with named elements, deconstruction, and with expressions.

Features:

1. Named tuple elements (preferred):

(string Name, int Age, bool IsEmployed) GetPerson()
{
    return ("Pradeep", 28, true);
}

var person = GetPerson();
Console.WriteLine(person.Name);       // Output: Pradeep
Console.WriteLine(person.Age);        // Output: 28
Console.WriteLine(person.IsEmployed); // Output: True

2. Deconstruction (C# 7+):

var (name, age, isEmployed) = GetPerson();
Console.WriteLine($"{name} is {age} years old."); // Output: Pradeep is 28 years old.

3. Inline tuple:

var point = (X: 10, Y: 20);
Console.WriteLine($"X={point.X}, Y={point.Y}"); // Output: X=10, Y=20

4. Tuple in LINQ (C# 12 collection expressions):

var employees = new[]
{
    (Name: "Alice", Dept: "Engineering"),
    (Name: "Bob",   Dept: "Marketing"),
};

foreach (var (name, dept) in employees)
    Console.WriteLine($"{name}{dept}");
↥ back to top

Q. Explain byval and byref?

In C#, byval (by value) and byref (by reference) describe how arguments are passed to methods:

By Value (byval):

Example:

void Increment(int x)
{
    x = x + 1;
}

int a = 10;
Increment(a);
Console.WriteLine(a); // Output: 10 (original value unchanged)

By Reference (byref):

The method receives a reference to the original variable. Changes made to the parameter affect the original variable. In C#, use the ref or out keyword to pass by reference.

Example:

void Increment(ref int x)
{
    x = x + 1;
}

int a = 10;
Increment(ref a);
Console.WriteLine(a); // Output: 11 (original value changed)
↥ back to top

Q. What is an immutable string?

An immutable string in C# is a string whose value cannot be changed after it is created. When you modify a string (such as by concatenation, replacement, or other operations), a new string object is created in memory, and the original string remains unchanged.

Why are strings immutable?:

Example:

string s1 = "Hello";
string s2 = s1;
s1 = s1 + " World"; // Creates a new string, s1 now points to "Hello World"
Console.WriteLine(s2); // Output: Hello (s2 is unchanged)
↥ back to top

Q. What is the JIT compiler process?

The JIT (Just-In-Time) compiler is a core component of the .NET runtime (CLR/CoreCLR) that converts Intermediate Language (IL) code into native machine code at runtime, just before execution.

JIT Compilation Process:

  1. Source ’ IL: The C# compiler (csc / dotnet build using Roslyn) compiles source code into Intermediate Language (IL) and stores it in assemblies (.dll / .exe).
  2. Assembly Loading: The CoreCLR loads the required assemblies at startup.
  3. JIT Compilation: When a method is called for the first time, the JIT compiler translates its IL to native machine code optimized for the current CPU (x64, Arm64, etc.).
  4. Caching: The native code is cached in memory so subsequent calls execute directly without re-compilation.
  5. Execution: The CPU runs the native code.
flowchart TD
    A["Source Code\n(.cs files)"] -->|"Roslyn Compiler\n(dotnet build)"| B["IL Code + Metadata\n(Assembly .dll / .exe)"]
    B -->|"CoreCLR loads assembly"| C{"First call\nto method?"}
    C -->|Yes| D["JIT Compiler\nIL → Native Machine Code"]
    D --> E["Cache native code\nin memory"]
    E --> F["CPU Executes\nNative Code"]
    C -->|No - already cached| F

    style A fill:#4a90d9,color:#fff
    style B fill:#7b68ee,color:#fff
    style D fill:#e8732a,color:#fff
    style F fill:#27ae60,color:#fff

.NET JIT improvements (.NET 8/9/10):

Alternative: Native AOT (Ahead-of-Time Compilation, .NET 7+):

Native AOT compiles the entire application to a self-contained native binary at build time — no JIT, no .NET runtime required at deployment.

dotnet publish -r linux-x64 -p:PublishAot=true

Benefits of Native AOT:

// Program.cs — minimal Native AOT app (.NET 10)
Console.WriteLine("Hello from Native AOT!");

When to use JIT vs. Native AOT:

Scenario JIT (Default) Native AOT
Long-running services Preferred Supported
Cold-start sensitive apps Warm-up delay Instant start
Reflection-heavy code Full support Limited
Smallest binary size Runtime needed Single file
↥ back to top

Q. Explain the characteristics of value-type variables that are supported in the C# programming language.

In C#, value-type variables hold their data directly in memory, unlike reference types which store a reference to the data. When assigned, value-type variables create a copy of the data, meaning changes to one variable do not affect others, except in cases where ref or out modifiers are used.

Characteristics:

Example:

int number = 42;           // Integral value type
double price = 19.99;      // Floating-point value type
bool isActive = true;      // Boolean value type
char letter = 'A';         // Character value type
DateTime today = DateTime.Now; // Struct (user-defined value type)

Summary Table:

Feature Value Type Example Behavior
Storage int x = 5; Stores value directly in variable
Assignment int y = x; Copies value, not reference
Nullability int? z = null; Nullable only with ? syntax
Default Value int x; Defaults to 0
Inheritance struct Cannot inherit from another struct/class
↥ back to top

Q. What is a parameter? Explain the new types of parameters introduced in C# 4.0.

A parameter in C# is a variable defined in a method, constructor, or indexer declaration that receives a value (called an argument) when the method is called. Parameters allow you to pass data into methods so they can operate on different values.

C# 4.0 introduced two important features related to method parameters:

1. Optional Parameters:

Example:

void PrintMessage(string message, int repeat = 1)
{
    for (int i = 0; i < repeat; i++)
        Console.WriteLine(message);
}

PrintMessage("Hello");      // Uses default repeat = 1
PrintMessage("Hi", 3);      // repeat = 3

// Output
// Hello
// Hi Hi Hi

2. Named Parameters

Example:

void PrintMessage(string name, int age = 0, string city = "Unknown")
{
    Console.WriteLine($"{name}, {age}, {city}");
}

PrintMessage("Pradeep", city: "Bengaluru"); // age uses default value 0

// Output
// Pradeep, 0, Bengaluru
↥ back to top

Q. What are the different types of literals?

In C#, literals are fixed values assigned directly to variables or constants in code. They represent constant values of various data types.

Types of literals in C#:

1. Integer Literals

2. Floating-Point Literals

3. Character Literals

4. String Literals

// Raw string literal (C# 11+)
string json = """
    {
        "name": "Pradeep",
        "age": 30
    }
    """;

// Raw interpolated string
string name = "Pradeep";
string greeting = $"""Hello, {name}! Welcome to "C# 14".""";
ReadOnlySpan<byte> utf8Hello = "Hello"u8;

5. Boolean Literals

6. Null Literal

Examples:

int age = 25;                // Integer literal
double pi = 3.14159;         // Floating-point literal
char letter = 'A';           // Character literal
string name = "Alice";       // String literal
bool isActive = true;        // Boolean literal
object obj = null;           // Null literal
↥ back to top

Q. What is the main difference between sub-procedure and function?

In C#, the main difference between a subroutine (which can be a Sub procedure in some languages) and a function is that a function returns a value, while a subroutine (or sub procedure) does not.

Both perform actions, but functions allow you to use their result elsewhere in your code, while subroutines/sub procedures simply execute and return control.

Difference :

Example:

// Function: returns an int
int Add(int a, int b)
{
    return a + b;
}

// Sub-procedure: returns nothing (void)
void PrintSum(int a, int b)
{
    Console.WriteLine(a + b);
}
↥ back to top

Q. What is the difference between string and StringBuilder in C#?

In C#, string and StringBuilder both handle text, but string is immutable, and StringBuilder is mutable. This means that when you modify a string, a new string object is created, while with StringBuilder, you can modify the object in place without creating new objects.

1. string:

Example:

string s = "Hello";
s += " World"; // Creates a new string object

2. StringBuilder:

Example:

using System.Text;

StringBuilder sb = new StringBuilder("Hello");
sb.Append(" World"); // Modifies the existing object
string result = sb.ToString();

When to use which?

↥ back to top

Q. What is difference between late binding and early binding in C#?

In C#, early binding (also known as static binding) resolves method calls at compile time, while late binding (also known as dynamic binding) resolves method calls at runtime.

Early binding offers better performance and type safety due to compile-time checking, while late binding provides more flexibility but with potential runtime overhead, especially when using reflection, according to various sources.

Early Binding:

Example:

// Early binding example
MyClass obj = new MyClass();
obj.MyMethod(); // Compiler knows about MyMethod at compile time

Late Binding:

Example:

// Late binding using dynamic
dynamic obj = GetSomeObject();
obj.MyMethod(); // Resolved at runtime
↥ back to top

Q. What is Indexer in C#?

An indexer in C# is a special type of property that allows objects of a class or struct to be indexed just like arrays, using the square bracket [] syntax. Indexers enable you to access elements in an object using an index, making custom classes behave like collections.

Example:

public class SampleCollection
{
    private string[] data = new string[5];

    // Indexer declaration
    public string this[int index]
    {
        get { return data[index]; }
        set { data[index] = value; }
    }
}

// Usage
var collection = new SampleCollection();
collection[0] = "Hello";
Console.WriteLine(collection[0]); // Output: Hello
↥ back to top

Q. What are the differences between Object, Var and Dynamic type?

In C#, object, var and dynamic are three different ways to declare variables, each with distinct behaviors and use cases.

1. object

Example:

object obj = "Hello";
// Console.WriteLine(obj.Length); // Error: 'object' does not contain 'Length'
Console.WriteLine(((string)obj).Length); // OK after casting

2. var:

Example:

var message = "Hello"; // message is string
// message = 123; // Error: cannot assign int to string
Console.WriteLine(message.Length); // OK

3. dynamic:

Example:

dynamic value = "Hello";
Console.WriteLine(value.Length); // OK at runtime
value = 123;
// Console.WriteLine(value.Length); // Runtime error: 'int' does not contain 'Length'

Differences:

Feature object var dynamic
Type Resolution Compile time Compile time (inferred) Runtime
Type Safety Yes (with casting) Yes No
Flexibility High (but verbose) Medium Highest
Member Access Requires casting Direct (after inference) Direct (runtime)
Use Case General base type, APIs When type is obvious or anonymous types Interop, dynamic scenarios
↥ back to top

Q. What is the difference between managed and unmanaged code?

Managed code is code that runs under the control of the .NET Common Language Runtime (CLR). The CLR provides services such as automatic memory management (garbage collection), type safety, exception handling, and security. Examples include C# and VB.NET code compiled for the .NET runtime.

Unmanaged code is code that runs directly on the operating system, outside the control of the CLR. It is responsible for its own memory management and resource cleanup. Examples include code written in C or C++ and compiled to native machine code, as well as COM components and Win32 API calls.

Key Differences:

Managed Code Unmanaged Code
Runs under CLR (.NET runtime) Runs directly on OS
Automatic memory management (GC) Manual memory management
Type safety and security checks No built-in type safety
Exception handling by CLR Must handle exceptions manually
Platform-independent (via CLR) Platform-dependent

Summary:

Managed code is safer and easier to maintain, while unmanaged code offers more control and performance but requires careful resource management.

↥ back to top

Q. What is an Object Pool in C#?

An Object Pool in C# is a creational design pattern that improves performance by reusing objects instead of repeatedly creating and destroying them. It involves maintaining a pool of pre-initialized objects, readily available for use when needed. This reduces memory allocation and garbage collection overhead, leading to faster execution, especially when dealing with frequently created and destroyed objects.

Benefits:

Example:

// Simple generic object pool example
public class ObjectPool<T> where T : new()
{
    private readonly Stack<T> _objects = new Stack<T>();

    public T GetObject()
    {
        return _objects.Count > 0 ? _objects.Pop() : new T();
    }

    public void ReturnObject(T item)
    {
        _objects.Push(item);
    }
}

// Usage
var pool = new ObjectPool<StringBuilder>();
StringBuilder sb = pool.GetObject();
sb.Append("Hello, Object Pool!");
pool.ReturnObject(sb);

.NET Built-in Support:

.NET Core provides System.Buffers.ObjectPool<T> and ArrayPool<T> for pooling arrays and other objects.

↥ back to top

Q. Explain the difference between lazy and eager evaluation in C#?

In C#, lazy evaluation defers the execution of code until its result is actually needed, while eager evaluation executes code immediately upon encountering it

1. Eager Evaluation: Values or expressions are computed immediately when they are assigned or called.

Example: Standard variable assignments and most method calls.

int x = GetValue(); // GetValue() is called immediately

2. Lazy Evaluation: Computation is deferred until the value is actually needed (accessed for the first time).

Example: Using Lazy<T>, IEnumerable<T> with yield return, or LINQ queries.

Lazy<int> lazyValue = new Lazy<int>(() => GetValue());
// GetValue() is not called until lazyValue.Value is accessed
IEnumerable<int> GetNumbers()
{
    yield return 1;
    yield return 2;
}
// Numbers are generated as you iterate

Difference

Aspect Eager Evaluation Lazy Evaluation
When evaluated Immediately On first use (on demand)
Example int x = GetValue(); Lazy<int> x = ...;
Use cases Always-needed values Expensive/optional values
LINQ .ToList() (immediate) .Where() (deferred)
↥ back to top

Q. Mention the two major categories that distinctly classify the variables of C# programs.

In C# programs, variables are primarily categorized into value types and reference types. Value types directly store the variable's value in memory, while reference types store a memory address (reference) to the value's location.

graph TD
    A["C# Variable Types"] --> B["Value Types"]
    A --> C["Reference Types"]
    B --> D["Stack Memory"]
    C --> E["Heap Memory"]
    D --> F["int, bool, float\nenum, struct\nDateTime, Guid"]
    E --> G["string, object\narray, class\ndelegate, interface"]
    B --> H["Copy Semantics\nChanges do NOT affect original"]
    C --> I["Reference Semantics\nChanges affect all references"]

Value Types:

These store the actual data directly in the memory location of the variable (Stack). Examples include int, bool, float, enum, and struct types. When a value type variable is copied, a new copy of the data is created, so changes to one variable don't affect others.

Reference Types:

These store a memory address to the location where the actual data is stored (Heap). Examples include string, object, array, and class types. When a reference type variable is copied, the copy contains the same memory address, meaning both variables point to the same data. Therefore, changes to the data through one reference will be reflected in the other.

↥ back to top

Q. What is checked and unchecked block?

In C#, the checked and unchecked blocks are used to control how the runtime handles arithmetic overflow for integral types (like int, long, etc.).

Example:

int max = int.MaxValue;

try
{
    // Checked block: will throw OverflowException
    checked
    {
        int result = max + 1;
    }
}
catch (OverflowException)
{
    Console.WriteLine("Overflow detected!");
}

// Unchecked block: will not throw, wraps around
unchecked
{
    int result = max + 1;
    Console.WriteLine(result); // Output: -2147483648
}

When to use:

You can also use the checked and unchecked keywords as expressions:

int result = checked(max + 1);    // Throws OverflowException
int result2 = unchecked(max + 1); // Wraps around
↥ back to top

Q. What is the difference between typeOf() and sizeOf()?

In C#, typeof() and sizeof() are two different operators used for different purposes:

1. typeof() Operator:

Example:

Type t = typeof(int); // Gets the Type object for int
Console.WriteLine(t.FullName); // Output: System.Int32

2. sizeof() Operator:

Example:

int size = sizeof(int); 
Console.WriteLine(size); // Output: 4

Summary Table:

Operator Purpose Returns Usage Example
typeof Get type metadata Type object typeof(int)
sizeof Get size in bytes (value types) Integer (bytes) sizeof(int)

Note:

↥ back to top

Q. What is widening and Narrowing conversion in C#?

Widening and narrowing conversions in C# refer to how values are converted between different data types, especially numeric types.

Widening Conversion (Implicit Conversion):

Example: int to long, float to double.

int a = 100;
long b = a;      // Widening: int to long (implicit)
float f = a;     // Widening: int to float (implicit)

Console.WriteLine(b); // Output: 100
Console.WriteLine(f); // Output: 100

Narrowing Conversion (Explicit Conversion):

Example: double to int, long to short.

double x = 123.45;
int y = (int)x;   // Narrowing: double to int (explicit), y = 123

long big = 1000;
short small = (short)big; // Narrowing: long to short (explicit)

Console.WriteLine(y); // Output: 123
Console.WriteLine(small); // Output: -31072
↥ back to top

Q. How to view an Assembly?

To view an assembly in C#, you can inspect its metadata, types, and IL code using several tools:

1. Using ILDASM (IL Disassembler):

ILDASM is a tool provided with the .NET SDK to view the contents of an assembly (DLL or EXE).

Steps:

  1. Open the Developer Command Prompt for Visual Studio.
  2. Run:
    ildasm YourAssembly.dll
    
  3. The ILDASM window will open, allowing you to browse namespaces, classes, methods, and view IL code.

2. Using dotPeek or ILSpy (Third-Party Tools):

3. Using Visual Studio:

4. Using Reflection in Code:

You can use reflection to inspect an assembly programmatically:

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        Assembly asm = Assembly.LoadFrom("MyAssembly.dll");
        foreach (Type type in asm.GetTypes())
        {
            Console.WriteLine(type.FullName);
        }
    }
}
↥ back to top

Q. What are MultiLingual Applications?

MultiLingual Applications are software applications designed to support multiple languages, allowing users to interact with the application in their preferred language. In C#, this is typically achieved using resource files (.resx) and the .NET localization framework.

Key Points:

How it works:

Example:

Suppose you have two resource files:

You can load the correct string at runtime:

using System.Globalization;
using System.Resources;

ResourceManager rm = new ResourceManager("Namespace.Resources", typeof(Program).Assembly);
CultureInfo ci = new CultureInfo("fr"); // or "en"

string greeting = rm.GetString("Greeting", ci);

Console.WriteLine(greeting); // Output depends on selected culture
↥ back to top

Q. Can you describe the process of code compilation in .NET?

In .NET, code compilation involves several stages, including translating source code into Common Intermediate Language (CIL) and then executing it using the Common Language Runtime (CLR) with the Just-In-Time (JIT) compiler.

The compiler (like Roslyn for C#) initially converts the high-level source code into CIL, a CPU-independent set of instructions. This CIL code, along with metadata, is stored in an assembly (PE format). When the program runs, the CLR uses the JIT compiler to convert the CIL into machine code, which is then executed on the specific CPU.

Process of Code Compilation

  1. Source Code to Intermediate Language (IL):
    • The Roslyn compiler (dotnet build / csc) compiles C# source code into Common Intermediate Language (CIL/IL).
    • The compiled IL, along with metadata, is stored in assemblies (.dll or .exe).
    • In .NET 10, the compiler supports C# 14 features such as the field keyword, extension members, and params enhancements.
  2. Assembly Loading:
    • The CoreCLR runtime loads required assemblies when the application starts.
  3. Just-In-Time (JIT) Compilation:
    • The JIT compiler translates IL to native machine code method-by-method on first call.
    • .NET 8/9/10 improvements: Tiered Compilation and Dynamic PGO recompile hot methods with full optimizations at runtime automatically.
  4. Native AOT (Ahead-of-Time, .NET 7+):
    • With PublishAot=true, the entire app is compiled to a self-contained native binary at build time — no JIT or runtime required at deployment.
  5. Execution:
    • The CPU executes the native code. JIT-compiled code is cached for subsequent calls.

Summary Diagram:

flowchart TD
    A["Source Code\n(C# 14 / .cs files)"] -->|"Roslyn Compiler\ndotnet build"| B["IL Code + Metadata\n(Assembly .dll / .exe)"]
    B --> C["CoreCLR loads assembly"]
    C --> D{Compilation mode?}
    D -->|Default JIT| E["JIT Compiler\nmethod-by-method\non first call"]
    D -->|"Native AOT\n(PublishAot=true)"| F["Whole-app compiled\nto native binary\nat build time"]
    E --> G["Tiered Compilation\n+ Dynamic PGO\n(hot path recompiled)"]
    G --> H["Native Machine Code\n(cached in memory)"]
    F --> H
    H --> I["CPU Execution"]

    style A fill:#4a90d9,color:#fff
    style B fill:#7b68ee,color:#fff
    style E fill:#e8732a,color:#fff
    style F fill:#27ae60,color:#fff
    style I fill:#2c3e50,color:#fff
↥ back to top

Q. Can you return multiple values from a function in C#?

Yes, you can return multiple values from a function in C#. There are several common ways to achieve this:

1. Using Tuples (preferred, C# 7+):

Tuples allow you to return multiple values of different types in a single return statement.

(string Name, int Age) GetPerson()
{
    return ("Pradeep", 30);
}

// Usage
var person = GetPerson();
Console.WriteLine(person.Name); // Pradeep
Console.WriteLine(person.Age);  // 30

// Deconstruction (C# 7+)
var (name, age) = GetPerson();
Console.WriteLine($"{name} is {age}"); // Pradeep is 30

2. Using Out Parameters:

The Out parameters allow a function to modify the values of variables passed as arguments. This is a way to “return” additional values indirectly.

void GetValues(out int a, out int b)
{
    a = 10;
    b = 20;
}

int x, y;
GetValues(out x, out y);
Console.WriteLine(x); // 10
Console.WriteLine(y); // 20

3. Using a Custom Class or Struct

You can define a custom class or struct to encapsulate multiple values and return an instance of that type

class Result
{
    public int Sum { get; set; }
    public int Product { get; set; }
}

// Example usage in a Main method
public class Program
{
    static Result Calculate(int a, int b)
    {
      return new Result { Sum = a + b, Product = a * b };
    }

    public static void Main()
    {
        var result = Calculate(3, 4);
        Console.WriteLine(result.Sum);     // 7
        Console.WriteLine(result.Product); // 12
    }
}

4. Using a Arrays or Lists

If the values to be returned are of the same type, you can return an array or a list.

public int[] GetValues()
{
    return new int[] { 10, 20, 30 };
}
↥ back to top

Q. In how many ways you can pass parameters to a method?

You can pass parameters to a method in C# in several ways:

1. By Value (default):

The method receives a copy of the argument. Changes inside the method do not affect the original variable.

Void MyMethod(int x) {
    x = 10 // Only modifies the local copy
}

2. By Reference (ref):

The method receives a reference to the original variable. The variable must be initialized before passing.

Void MyMethod(int x) {
    x = 10 // Modifies the original value
}

3. Output Parameter (out):

The method can assign a value to the parameter and return it to the caller. The variable does not need to be initialized before passing.

Void MyMethod(out int x) {
    x = 10 // Must assign a value before method ends
}

4. Parameter Array (params):

Allows passing a variable number of arguments as an array.

Void MyMethod(params int[] numbers) {
    foreach(int n in numbers) {
        Console.WriteLine(n);
    }
}

5. Optional Parameters:

Parameters with default values that can be omitted when calling the method.

Void MyMethod(int x = 5) {
    Console.WriteLine(x);
}

6. Named Parameters:

Allows specifying parameters by name when calling the method, allowing arguments to be passed in any order.

Void MyMethod(int x, int y) {
    Console.WriteLine(x + y);
}

// call using named parameters
MyMethod(y: 10, x: 5);

7. in Parameters (Read-only Reference):

Passes by reference but ensures the method cannot modify the value.

Void MyMethod(in int x) {
    Console.WriteLine(x); // Cannot modify x
}

// call using named parameters
MyMethod(y: 10, x: 5);

Summary Table:

Way Keyword Description
By Value (default) Passes a copy of the value
By Reference ref Passes a reference to the variable
Output Parameter out Passes a reference, must assign in method
Parameter Array params Passes variable number of arguments
Optional Parameter = value Allows omitting arguments with defaults
Named Parameter name: val Specify argument by name
In Parameter in Passes a reference to the variable(cannot modify the value)
↥ back to top

Q. What are variables in C# and how are they declared?

A variable in C# is a named storage location that holds a value of a specific type. Variables must be declared before use and can be declared as local, instance, static, or constant.

1. Local variables — declared inside a method:

int age = 25;          // explicitly typed
var name = "Pradeep"; // implicitly typed (compiler infers string)

2. Instance variables — fields of a class:

public class Person
{
    public string Name;   // instance field
    public int Age = 0;   // with default value
}

3. Static variables — shared across all instances:

public class Counter
{
    public static int Count = 0; // shared by all instances
}

4. Constants — immutable compile-time values:

const double Pi = 3.14159;
// Pi = 3.14; // Compile error — cannot reassign

5. Variable scope:

void Example()
{
    int x = 10; // x is scoped to this method

    if (x > 5)
    {
        int y = 20; // y is scoped to this block
        Console.WriteLine(x + y); // Output: 30
    }
    // Console.WriteLine(y); // Error: y is out of scope
}

6. Multiple declaration:

int a = 1, b = 2, c = 3; // declare and initialize multiple variables
↥ back to top

Q. What is the basic structure of a C# program?

A C# program is built around classes and methods. With C# 9+ Top-Level Statements, you can also write programs without explicit class or Main boilerplate.

1. Traditional program structure (all versions):

using System; // Import namespace

namespace MyApp
{
    class Program
    {
        static void Main(string[] args) // Entry point
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

2. Top-level statements (C# 9+) — preferred for small programs:

No class or Main required. The compiler generates them automatically.

using System;

Console.WriteLine("Hello, World!"); // Valid C# 9+ program

3. Key structural elements:

Element Description
using Imports a namespace to use its types without full qualification
namespace Logical grouping of related classes and types
class Blueprint for objects; contains fields, properties, methods
static void Main Entry point — where execution begins
args Command-line arguments passed to the program

4. File-scoped namespace (C# 10+) — reduces nesting:

using System;

namespace MyApp; // No braces needed

class Program
{
    static void Main() => Console.WriteLine("Hello!");
}
↥ back to top

Q. What are access modifiers in C# and how do they control visibility?

Access modifiers in C# control the visibility and accessibility of types and their members. C# has six access modifiers:

Modifier Accessibility
public Accessible from anywhere
private Accessible only within the same class (default for members)
protected Accessible within the class and derived classes
internal Accessible within the same assembly (default for top-level types)
protected internal Accessible within the same assembly or from derived classes
private protected (C# 7.2) Accessible within the class and derived classes in the same assembly

Example:

public class BankAccount
{
    public string Owner { get; set; }        // accessible everywhere
    private decimal _balance;               // accessible only inside this class
    protected string AccountType = "Savings"; // accessible in derived classes
    internal int BranchCode = 101;           // accessible within the same assembly

    public void Deposit(decimal amount)
    {
        if (amount > 0)
            _balance += amount; // private field accessed within the class
    }

    public decimal GetBalance() => _balance;
}

var account = new BankAccount();
account.Owner = "Pradeep";   // OK — public
// account._balance = 100;   // Error — private
account.Deposit(500);
Console.WriteLine(account.GetBalance()); // Output: 500
↥ back to top

Q. What is string interpolation in C# and how is it used?

String interpolation (introduced in C# 6) provides a concise syntax to embed expressions directly inside string literals using the $ prefix. It is the preferred way to format strings in modern C#.

1. Basic interpolation:

string name = "Pradeep";
int age = 28;

string message = $"Name: {name}, Age: {age}";
Console.WriteLine(message); // Output: Name: Pradeep, Age: 28

2. Expressions inside {}:

int a = 10, b = 5;
Console.WriteLine($"Sum: {a + b}, Product: {a * b}"); // Output: Sum: 15, Product: 50

3. Format specifiers:

double price = 1234.567;
Console.WriteLine($"Price: {price:C2}");  // Output: Price: $1,234.57 (currency)
Console.WriteLine($"Price: {price:F1}");  // Output: Price: 1234.6 (1 decimal)
Console.WriteLine($"Hex: {255:X}");       // Output: Hex: FF

4. Multi-line with $@ or @$ (verbatim interpolated string):

string path = "C:\\Users";
string msg = $@"Hello {name},
Your path is: {path}";
Console.WriteLine(msg);

5. Raw interpolated string (C# 11+):

string json = $$"""{ "name": ,  "age":  }""";

Console.WriteLine(json);

Comparison with alternatives:

Method Example
Concatenation "Hello " + name
string.Format string.Format("Hello {0}", name)
Interpolation (preferred) $"Hello {name}"
StringBuilder For repeated modifications in loops
↥ back to top

# 2. OPERATORS


Q. What are operators available in C#?

C# operators are special symbols that perform operations on operands. They are categorized into several types: arithmetic, comparison, logical, bitwise, assignment, and others.

The different types of operators in C# are:

1. Arithmetic Operators

These operators perform standard arithmetic operations on numeric values.

Example:

int a = 10, b = 3;

Console.WriteLine(a + b); // Output: 13
Console.WriteLine(a - b); // Output: 7
Console.WriteLine(a * b); // Output: 30
Console.WriteLine(a / b); // Output: 3
Console.WriteLine(a % b); // Output: 1

2. Relational (Comparison) Operators

These operators compare two values and return a boolean result (true or false).

Example:

int a = 5, b = 10;

Console.WriteLine(a == b) // Output: False
Console.WriteLine(a < b) // Output: True

3. Logical Operators

These operators perform logical operations on boolean expressions.

Example:

bool isAdult = true;
bool hasID = false;

Console.WriteLine(isAdult && hasID); // Output: False
Console.WriteLine(isAdult || hasID); // Output: True

4. Assignment Operators

These operators assign values to variables.

Example:

int x = 5;
x += 3; // x = x + 3

Console.WriteLine(x); // Output: 8

5. Bitwise Operators

These operators work directly on the binary representation of numbers.

Example:

int a = 5;
int b = 3;

Console.WriteLine(a & b); // Output: 1 (0001)
Console.WriteLine(a | b); // Output: 7 (0111)

6. Conditional (Ternary) Operator

Example:

int age = 18;
String result = (age >= 18) ? "Adult" : "Minor";

Console.WriteLine(result); // Output: Adult

7. Null-Coalescing Operators

Used to handle null values:

Example:

string name = null;
string displayName = name ?? "Guest";

Console.WriteLine(displayName); // Output: Guest

8. Null-Conditional Operator

Example:

object?.Member // If object is null, the expression returns null instead od throwing an exception.

9. Type Operators

Used for type checking and casting:

Example:

object obj = "Hello World";

if(obj is string)
{
    Console.WriteLine("It\'s a string!");
}

10. Other Operators

Example:

// Simple Lambda Expression
Func<int, int> square = x => x * x;

Console.WriteLine(square(5)); // Output: 25
↥ back to top

Q. What is the purpose of the nameof operator in C#?

The nameof operator in C# is used to obtain the simple (unqualified) string name of a variable, type, or member. It is evaluated at compile time and helps make code safer and easier to maintain, especially when referring to member names in exceptions, logging, data binding, or attributes.

Purpose and Benefits:

Typical use cases:

Example:

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

    public void PrintName()
    {
        Console.WriteLine(nameof(FirstName)); // Output: FirstName
    }
}
↥ back to top

Q. What is difference between const and readonly in C#?

In C#, both const and readonly are used to define values that cannot be changed after initialization, but they have important differences:

Feature const readonly
When assigned At compile time At runtime (in constructor or declaration)
Type Only primitive types, string, enum Any type (including reference types)
Scope Implicitly static (class-level) Can be instance-level or static
Value changes Cannot be changed anywhere Can be assigned once per instance
Usage For values known at compile time For values known only at runtime

Const:

Example:

public class MyClass
{
    public const double Pi = 3.14159;
}

Readonly:

Example:

public class MyClass
{
    public readonly int Id;

    public MyClass(int id) {
        Id = id; // Allowed in constructor
    }
}
↥ back to top

Q. How you would use a bitwise operator in C#?

Bitwise operators in C# are used to perform bit level operations on integer types like int, uint, long, ulong, bytes, etc. These operators treat their operands as a sequence of bits rather than as decimal, hexadecimal, or octal numbers.

Overview:

Operator Symbol Description
AND & Sets each bit to 1 if both bits are 1
OR   Sets each bit to 1 if at least one of the corresponding bits is 1, otherwise 0.
XOR ^ Sets each bit to 1 if only one of two bits is 1
NOT ~ Inverts all the bits
Left Shift « Shifts bits to the left
Right Shift » Shifts bits to the right

Typical use cases:

Example:

int a = 5;      // 0101 in binary
int b = 3;      // 0011 in binary

// Bitwise AND
int and = a & b; // 0001 = 1

// Bitwise OR
int or = a | b;  // 0111 = 7

// Bitwise XOR
int xor = a ^ b; // 0110 = 6

// Bitwise NOT
int notA = ~a;   // Inverts all bits

// Left shift
int leftShift = a << 1; // 1010 = 10

// Right shift
int rightShift = a >> 1; // 0010 = 2

Console.WriteLine($"AND: {and}, OR: {or}, XOR: {xor}, NOT: {notA}, <<: {leftShift}, >>: {rightShift}");
↥ back to top

Q. Explain the use of the as operator in C# and the best way to use it?

The as operator in C# is used for safe type casting. It attempts to cast an object to a specified type and returns null if the conversion fails, instead of throwing an exception (unlike a direct cast).

Syntax:

object obj = "hello";
string str = obj as string; // str is "hello"

When to Use the as Operator:

Best Practices:

1. Always check for null after using as:

object obj = GetObject();
MyClass mc = obj as MyClass;
if (mc != null)
{
    mc.DoSomething();
}
else
{
    // Handle the failed cast
}

2. Use as for performance when you need to both check and cast:

3. Do not use as with value types (except nullable):

Example:

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

object obj = new Dog();

Dog dog = obj as Dog;
if (dog != null)
{
    dog.Bark(); // Output: Woof!
}
else
{
    Console.WriteLine("Not a Dog");
}
↥ back to top

Q. What is the use of Null Coalescing Operator (??) in C#?

The null coalescing operator(??) in C# is used to provide a default value when dealing nullable types or potentially null expressions. It helps to write cleaner and more concise code by avoiding explicit null checks.

Example:

string userAge = null;
string age = userAge ?? 18;

Console.WriteLine(userAge); // Output: 18
↥ back to top

Q. What is difference between “is” and “as” operator in C#?

In C#, the is operator and the as operator are both used for type checking and type conversion, but they serve different purposes. The is operator checks if an object is of a specific type, returning a boolean value (true or false). The as operator attempts to convert an object to a specified type, returning the converted object if the conversion is successful, or null if it's not.

1. is Operator:

Example:

object obj = "hello";
if (obj is string)
{
    Console.WriteLine("obj is a string");
}

2. as Operator:

Example:

object obj = "hello";
string str = obj as string;
if (str != null)
{
    Console.WriteLine($"String value: {str}");
}

Use Case:

↥ back to top

Q. What are nullable types in C#?

In C#, nullable types cover two distinct concepts:

1. Nullable Value Types (T? / Nullable<T>) — all .NET versions:

Allow value types (e.g., int, bool, DateTime) to represent null, useful for optional database fields or missing data.

Key members:

int? score = null;

if (score.HasValue)
    Console.WriteLine($"Score: {score.Value}");
else
    Console.WriteLine("Score is not set."); // Output: Score is not set.

int fallback = score.GetValueOrDefault(-1); // -1

2. Nullable Reference Types (C# 8+, .NET Core 3+):

Enable compile-time null safety for reference types. Enabled project-wide with <Nullable>enable</Nullable> in the .csproj (default in .NET 6+ projects).

// Without nullable context: string can be null silently (old behavior)
// With nullable context:
string nonNullable = "Pradeep"; // Cannot be null — compiler warns if you try
string? nullable = null;        // Explicitly nullable — must check before use

int length = nullable?.Length ?? 0; // Safe with null-conditional + null-coalescing
Console.WriteLine(length); // Output: 0

3. Null-coalescing operators (C# 8+):

string? input = null;
string result = input ?? "default";      // "default"
input ??= "assigned if null";            // ??= assigns only if left side is null
Console.WriteLine(input); // Output: assigned if null

4. Null-forgiving operator ! (C# 8+):

Suppresses the nullable warning when you know a value isn't null:

string? value = GetMaybeNull();
int len = value!.Length; // tells compiler "trust me, not null"
↥ back to top

Q. What is Type Casting and what are its types in C#?

Type casting in C# is the process of converting a variable from one data type to another. This is often necessary when working with different types of data, such as converting an int to a double, or casting a base class reference to a derived class.

There are two main types of type casting in C#:

1. Implicit Casting

Examples:

int num = 100;
double d = num; // Implicit casting: int to double

2. Explicit Casting

Examples:

double d = 123.45;
int num = (int)d; // Explicit casting: double to int (fractional part lost)

3. Other Casting Types

Example:

int x = 10;
object obj = x;      // Boxing
int y = (int)obj;    // Unboxing

Example:

object obj = "hello";
    string str = obj as string; // str is "hello"
    if (obj is string) { /* true */ }

Example:

string str = "123";
int num = Convert.ToInt32(str);

Example:

int result;
bool success = int.TryParse("456", out result);
↥ back to top

Q. What is the difference between == operator and .Equals() method?

The == operator and .Equals() method are both used to compare objects in C#, but they behave differently depending on the type being compared:

1. == Operator:

2. .Equals() Method:

Key Differences:

Aspect == Operator .Equals() Method
Reference Types Reference equality (unless overloaded) Reference equality (unless overridden)
Value Types Value equality Value equality (overridden)
Overridable Yes (operator overloading) Yes (method override)
Null Handling Safe (returns false if either is null) Throws if called on null instance

Example:

class Person {
    public string Name;
    public override bool Equals(object obj) =>
        obj is Person p && Name == p.Name;
    // == is not overloaded, so default is reference equality
}

var p1 = new Person { Name = "Pradeep" };
var p2 = new Person { Name = "Pradeep" };

Console.WriteLine(p1 == p2);        // False (different references)
Console.WriteLine(p1.Equals(p2));   // True  (same value)

Summary:

↥ back to top

Q. What is short-circuit evaluation in C#?

Short-circuit evaluation in C# is a performance optimization technique used with logical operators like && and ||. It means that the second operand in a logical expression is evaluated only if necessary.

1. Logical AND (&&):

Example:

string s = null;
if (s != null && s.Length > 0)
{
    Console.WriteLine("String is not empty.");
}

Here, s.Length > 0 is only checked if s != null is true, preventing a possible exception.

**2. Logical OR (   ):**

Example:

bool result = (x == 0) || (10 / x > 1);

If x is 0, the first condition is true, so the second part is not evaluated, again avoiding a divide-by-zero exception.

Benefits:

↥ back to top

Q. List some different ways for equality check in .Net?

In C#, there are several ways to perform equality checks depending on the type of objects comparison. Here are some common ways to check for equality in .NET:

1. == Operator

Example:

int a = 5, b = 5;
bool result = a == b; // true

string s1 = "hello", s2 = "hello";
bool isEqual = s1 == s2; // true (string overloads ==)

2. .Equals() Method

Example:

object o1 = "Hello";
object o2 = "Hello";

bool isEqual = o1.Equals(o2); // true

3. Object.ReferenceEquals()

Example:

object o1 = new object();
object o2 = o1;

bool isEqual = ReferenceEquals(o1, o2); // true

4. Object.Equals(a, b)

Example:

bool isEqual = Object.Equals(objA, objB);

5. IEquatable<T>.Equals()

Example:

public class Person : IEquatable<Person>
{
    public string Name;
    public bool Equals(Person other) => Name == other?.Name;
}

6. SequenceEqual() (for collections)

Example:

var arr1 = new[] { 10, 20, 30 };
var arr2 = new[] { 10, 20, 30 };

bool isEqual = arr1.SequenceEqual(arr2); // true

7. StructuralComparisons.StructuralEqualityComparer

Example:

var arr1 = new[] { 1, 2 };
var arr2 = new[] { 1, 2 };

bool isEqual = StructuralComparisons.StructuralEqualityComparer.Equals(arr1, arr2); // true

8. EqualityComparer<T>.Default.Equals()

Example:

bool isEqual = EqualityComparer<string>.Default.Equals("Hi", "Hi");

Summary Table:

Method Use Case
== Value types, overloaded types
.Equals() Value/reference, can override
ReferenceEquals() Reference equality only
Object.Equals(a, b) Safe, handles nulls
IEquatable<T>.Equals() Custom types, performance
SequenceEqual() Collections/arrays
StructuralEqualityComparer Arrays, tuples, structural types
EqualityComparer<T>.Default.Equals() generic code
↥ back to top

Q. What is difference between static, readonly, and constant in C#?

In C#, static, readonly, and const are modifiers used to define how variables behave in terms of initialization, memory allocation, and mutability. Here's a breakdown of the differences:

1. const(Constant)

Example:

public const double Pi = 3.14159;

2. readonly

Example:

public readonly int id;

public MyClass(int id) { 
    this.id = id; 
}

3. static

Example:

public static int Counter = 0;

Summary: |Feature | const | readonly | static | |————–|———-|—————|——–| |Compile-time |Yes | No | No | |Runtime |No | Yes | Yes | |Instance-based|No | Yes | No | |static |Implicitly| Optional | Yes | |Mutable |No | No(after init)| Yes(if not readonly)|

↥ back to top

Q. How to loop through an enum in C#?

To loop through an enum in C#, you can use the Enum.GetValues() method, which returns an array of the enum's values.

Example:

enum Days
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

class Program
{
    static void Main()
    {
        foreach (Days day in Enum.GetValues(typeof(Days)))
        {
            Console.WriteLine(day);
        }
    }
}

Output:

Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
↥ back to top

Q. How to set default value to Property in C#?

You can set a default value for a property in C# in several ways, depending on the type of property and the context.

1. Auto-Implemented Property with Default Values:

2. Using a Constructor:

3. Using Read-Only Properties (init-only):

public class Person
{
    public string Name { get; init; } = "Unknown";
}

4. Default values with Nullable Types:

Summary:

↥ back to top

Q. How to convert int to enum in C#?

You can convert an integer to an enum type in C# using a simple cast. This is useful when you have an integer value (for example, from a database or user input) and want to work with it as an enum.

Example:

public class Program
{
    enum Status
    {
        Pending = 0,
        Approved = 1,
        Rejected = 2
    }
            
    public static void Main(string[] args)
    {
        int value = 1;
        Status status = (Status)value;

        Console.WriteLine(status); // Output: Approved
    }
}

Note:

if (Enum.IsDefined(typeof(Status), value))
{
    Status status = (Status)value;
    Console.WriteLine(status)
}
else
{
    Console.WriteLine("Invalid status value");
}
↥ back to top

Q. What is BigInteger Data Type in C#?

The BigInteger data type in C# is a structure provided by the System.Numerics namespace that allows you to work with arbitrarily large integers—much larger than the built-in numeric types like int or long. Unlike these fixed-size types, BigInteger can represent numbers of any size and precision, limited only by the available system memory.

Key Points:

Example:

using System;
using System.Numerics;

class Program
{
    static void Main()
    {
        BigInteger big = BigInteger.Parse("123456789012345678901234567890");
        BigInteger result = big * 2;

        Console.WriteLine(result); // Output: 246913578024691357802469135780
    }
}

Note:

↥ back to top

Q. How to convert String to Enum in C#?

To convert a string to an enum in C#, use the Enum.Parse() or Enum.TryParse() method. Enum.Parse() throws an exception if the conversion fails, while Enum.TryParse() returns a boolean indicating success.

This is useful when you have a string value (e.g., from user input or a file) and want to convert it to a strongly-typed enum value.

1. Using Enum.Parse():

Example:

public enum Status
{
    Active,
    Inactive,
    Pending
}

string input = "Active";
Status status = (status)Enum.Parse(typeof(Status), input);

2. Using Enum.TryParse():

Example:

public enum Status 
{
    Active,
    Inactive,
    Pending
}

string input = "Inactive";

if(Enum.TryParse(input, out Status status)) 
{
    Console.WriteLine($"Parsed successfully: {status}");
} 
else
{
    Console.WriteLine("Invalid enum value.");
}

3. Case-Insensitive Parsing:

Example:

Enum.TryParse("pending", ignoreCase: true, out Status status);

Notes:

↥ back to top

Q. How to convert an Object to JSON in C#?

In C#, you can convert an object to JSON string using the System.Text.Json namespace or Newtonsoft.Json (also known as Json.NET) library. The most common and modern approach is with System.Text.Json.

1. Using System.Text.Json:

You can convert an object to a JSON string in C# using the built-in System.Text.Json namespace:

Example:

using System.Text.Json;

var person = new { Name = "Pradeep", Age = 30 };
string json = JsonSerializer.Serialize(person);

Console.WriteLine(json); // Output: {"Name":"Pradeep","Age":30}

2. Using Newtonsoft.Json:

Install-Package Newtonsoft.Json

Example:

using Newtonsoft.Json;

var person = new { Name = "Pradeep", Age = 30 };
string json = JsonConvert.SerializeObject(person);

Console.WriteLine(json); // Output: {"Name":"Pradeep","Age":30}

Note:

↥ back to top

Q. How to convert JSON String to Object in C#?

To convert a JSON string to an object in C#, you typically use either the built-in System.Text.Json namespace or the popular third-party library Newtonsoft.Json (Json.NET).

1. Using System.Text.Json:

You can convert a JSON string to an object in C# using the built-in System.Text.Json namespace:

Example:

using System.Text.Json;

string json = "{\"Name\":\"Pradeep\",\"Age\":30}";

// Define a matching class
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Deserialize the JSON string to an object
Person person = JsonSerializer.Deserialize<Person>(json);

Console.WriteLine(person.Name); // Output: Pradeep
Console.WriteLine(person.Age);  // Output: 30

2. Using Newtonsoft.Json (Json.NET):

First, install the NuGet package:
Install-Package Newtonsoft.Json

Example:

using Newtonsoft.Json;

string json = "{\"Name\":\"Pradeep\",\"Age\":30}";

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

Person person = JsonConvert.DeserializeObject<Person>(json);

Console.WriteLine(person.Name); // Output: Pradeep
Console.WriteLine(person.Age);  // Output: 30

Note:

↥ back to top

Q. How to Pass or Access Command-line Arguments in C#?

In C#. you can access command-line arguments using the Main method's parameter, typicallydefined as a string[] args.

Example:

using System;

class Program
{
    static void Main(string[] args) 
    {
        Console.WriteLine("Number of arguments:" + args.Length);
        foreach (string arg in args)
        {
            Console.WriteLine("Argument: " + arg);
        }
    }
}

Running the Program

MyApp.exe firstArg secondArg

Output

Number of arguments: 2
Argument: firstArg
Argument: secondArg

2. Alternative Access

Q. How to convert date object to string in C#?

To convert a date object (DateTime) to a string in C#, use the ToString() method. You can specify a format string to control the output.

1. Default Format:

DateTime now = DateTime.Now;
string dateString = now.ToString();

2. Custom Format:

string formatted = now.ToString("yyyy-MM-dd HH:mm:ss");

3. Culture-Specific Format:

string cultureFormatted = now.ToString("D", new CultureInfo("fr-FR"));

Common formats:

Summary:
Use dateTime.ToString() for default, or dateTime.ToString("format") for custom string output.

↥ back to top

Q. How to combine two arrays without duplicate values in C#?

To combine two arrays without duplicate values in C#, you can use the Union method from LINQ, which returns the set union of two sequences (removing duplicates).

Example:

using System;
using System.Linq;

class Program
{
    static void Main()
    {
        int[] array1 = { 10, 20, 30 };
        int[] array2 = { 30, 50, 10 };

        int[] combinedArray = array1.Union(array2).ToArray();

        Console.WriteLine("Combined Array: " + string.Join(", ", combinedArray)); // Output: 10, 20, 30, 50
    }
}

Explanation:

↥ back to top

Q. How to convert string to int in C#?

To convert a string to an int in C#, you can use one of the following methods:

1. int.Parse()

This method throws an exception if the string is not a valid integer.

string str = "123";
int number = int.Parse(str); // number = 123

2. int.TryParse()

This is the safest method. It returns true if the conversion is successful, otherwise false.

string str = "123";
int number;
bool isSuccess = int.TryParse(str, out number);

if (isSuccess)
{
    Console.WriteLine("Conversion successful:" + number);
}
else
{
    Console.WriteLine("Invalid Input");
}

3. Convert.ToInt32()

This method throws an exception if the string is not a valid number, but it handles null by returning 0.

string str = "123";
int number = Convert.ToInt32(str); // number = 123

Recommendation:

Use int.TryParse() for user input or when the string may not be a valid integer, as it avoids exceptions.

Summary Table:

Method Throws on invalid input Handles null Usage scenario
int.Parse() Yes No Trusted input
int.TryParse() No Yes User/untrusted input
Convert.ToInt32() Yes Yes (returns 0) Nullable input
↥ back to top

Q. What is boxing and unboxing?

In C#, boxing and unboxing are processes that allow value types (like int, float, bool, double, struct, etc.) to be treated as reference types (like object).

graph LR
    subgraph Stack
        A["int num = 42"]
    end
    subgraph Heap
        B["object obj\n””——————————\n”  42      ”\n”””——————————"]
    end
    subgraph Stack2["Stack"]
        C["int n = 42"]
    end
    A -->|"Boxing\nobject obj = num"| B
    B -->|"Unboxing\nint n = (int)obj"| C

1. Boxing:

Example:

int num = 42;
object obj = num; // Boxing: num is copied into obj as an object

2. Unboxing:

Unboxing is the reverse process: converting a reference type back to a value type.

Example:

object obj = 42;
int num = (int)obj; // Unboxing: obj is converted back to int

Notes:

↥ back to top

Q. What effect does boxing and unboxing have on performance?

Boxing and unboxing can negatively impact performance in C#.

Boxing is the process of converting a value type (like int, double, or a struct) to a reference type (object). This involves allocating memory on the heap and copying the value, which is more expensive than working with value types on the stack.

Unboxing is the reverse: extracting the value type from the object. This requires a type check and copying the value back from the heap to the stack.

Performance Effects:

1. Memory Allocation:

2. Garbage collection:

3. CPU overhead:

4. Cache Misses:

Example:

int x = 42;
object obj = x;      // Boxing (heap allocation)
int y = (int)obj;    // Unboxing (type check + copy)

When to Avoid Boxing/Unboxing:

↥ back to top

Q. What is the difference between == and ReferenceEquals in C#?

The == and ReferenceEquals in C# are both used for comparisons, but they serve different purposes:

Example:

object a = new string("hello");
object b = new string("hello");

Console.WriteLine(a == b); // True, because string overrides == for value equality

Example:

object a = new string("hello");
object b = String.Copy(a);

Console.WriteLine(object.ReferenceEquals(a, b)); // False, different objects in memory

Summary:

↥ back to top

Q. How does operator overloading work in C#?

In C#, operator overloading allows developers to extend the functionality of operators (like +, -, *, etc.) to work with user-defined data types (classes and structs). This makes your objects behave more like built-in types, improving readability and usability.

Example:

/**
* + Operator Overloading Example
*/
Public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    // Overload the + operator
    public static Point operator +(Point a, Point b) 
    {
        return new Point(a.X + b.X, a.Y + b.Y);
    }
}

// Usage
Point p1 = new Point(1, 2);
Point p2 = new Point(3, 4);
Point result = p1 + p2; // Uses the overload + operator

Key Points:

↥ back to top

Q. What is the “=>” operator in C#? Where is it used?

The => operator in C# is called the lambda operator or goes to operator. It is used to define lambda expressions, which are anonymous functions that can contain expressions or statements and can be used to create delegates or expression tree types.

Syntax:

(parameter) => expression

Where is it used?

1. Lambda Expressions: Used to define inline functions, especially with LINQ, delegates, and events.

Examples:

Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(2, 3)); // Output: 5

2. LINQ Queries: Commonly used in LINQ to filter, project, or transform data. Example:

var evens = numbers.Where(n => n % 2 == 0);

3. Event Handlers: Can be use to define event handlers inline. Example:

button.Click += (sender, e) => { Console.WriteLine("Button clicked!"); };

4. Expression Trees: In advanced scenarios, lambda expressions can be complied into expression trees for dynamic query generation. Example:

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        // Define an expression tree for a Lambda: x => x*x
        Expression<Fun<int, int>> squareExpr = x => x * x;

        // Print the expression
        Console.WriteLine("Expression: " + squareExpr);

        // Compile and invoke the expression 
        Fun<int, int> square = squareExpr.Compile();
        Console.WriteLine("Result of square(5): "+ square(5));
    }
}

Summary:
The => operator is used to define inline functions (lambdas) and concise member implementations, making code more readable and expressive, especially in LINQ and functional programming scenarios.

↥ back to top

Q. What is the null-conditional operator (?.) and how does it differ from the null-coalescing operator (??)?

The null-conditional operator (?.) and the null-coalescing operator (??) are both used in C# to simplify working with potentially null values, but they serve different purposes:

1. Null-Conditional Operator (?.):

The null-conditional operator (?.) allows you to safely access a member or method of an object that might be null. If the object is null, the expression returns null instead of throwing a NullReferenceException.

Example:

Person person = null;
string name = person?.Name; // name is null, no exception thrown

2. Null-Coalescing Operator (??)

The ?? operator provides a default value when the left hand operand is null.

Example:

string name = person?.Name ?? "Unknown"; // If person or Name is null, name is "Unknown"

Usage Together: You can combine both:

int? length = person?.Name?.Length ?? 0; // If person or Name is null, length is 0
↥ back to top

Q. What is the purpose of the default literal in C#?

The default literal in C# (introduced in C# 7.1) provides a concise way to represent the default value of a type without explicitly specifying the type. It is written simply as default (without a type in parentheses).

Examples: Without default literal (older style)

// Before C# 7.1
int number = default(int); // 0
string text = default(string) // null

Example: With default literal (modern style)

// After C# 7.1
int number = default; // 0
string text = default; // null

In generic methods:

public T GetDefaultValue<T>()
{
    return default;
}
↥ back to top

Q. Can you explain the is not pattern introduced in C# 9.0?

The is not pattern introduced in C# 9.0 is a concise way to check if an object is not of a certain type or does not match a pattern.

Syntax:

Instead of writing:

if (!(obj is string)) 
{  
    Console.WriteLine("Obj is not string");
}

You can now write:

if (obj is not string) 
{  
    Console.WriteLine("Obj is not string");
}

This improves readability and reduces the need for extra parentheses.

Example:

object value = 42;
if (value is not string)
{
    Console.WriteLine("Not a string!"); // Output: Not a string!
}

Example: Using Pattern Matching

You can also use it with more complex patterns:

if (person is not Employee { IsActive: true }) {
    // person is either not an Employee or not active
}

Benefits:

↥ back to top

Q. What is operator precedence in C# and how does it affect expressions?

Operator precedence determines the order in which operators are evaluated in an expression when multiple operators appear together. Operators with higher precedence are evaluated first.

Precedence table (high -> low):

Priority Operators Description
1 (), [], ., ?., ! (null-forgiving) Primary
2 ++, --, +, -, ~, ! (unary), (T) Unary
3 *, /, % Multiplicative
4 +, - Additive
5 <<, >> Shift
6 <, >, <=, >=, is, as Relational / type
7 ==, != Equality
8 & Bitwise AND
9 ^ Bitwise XOR
10 \| Bitwise OR
11 && Logical AND
12 \|\| Logical OR
13 ?? Null-coalescing
14 ?: Conditional (ternary)
15 =, +=, -=, *=, ??=, etc. Assignment

Example — precedence affects the result:

int result1 = 2 + 3 * 4;       // 14 (* before +)
int result2 = (2 + 3) * 4;     // 20 (parentheses override)
bool check  = 5 > 3 && 2 < 4;  // true (&& after comparisons)

Console.WriteLine(result1); // Output: 14
Console.WriteLine(result2); // Output: 20
Console.WriteLine(check);   // Output: True

Tip: Use parentheses () to make intent explicit and avoid subtle bugs.

int x = 10;
bool y = x > 5 || x < 3 && x != 7; // && evaluated before ||
bool z = (x > 5 || x < 3) && x != 7; // different result with parentheses

Console.WriteLine(y); // Output: True
Console.WriteLine(z); // Output: True (same here, but intent is clear)
↥ back to top

Q. What is the difference between pre-increment (++i) and post-increment (i++) in C#?

Both ++i (pre-increment) and i++ (post-increment) add 1 to a variable, but they differ in when the incremented value is returned.

Example:

int a = 5;
Console.WriteLine(++a); // Output: 6 (incremented before use)
Console.WriteLine(a);   // Output: 6

int b = 5;
Console.WriteLine(b++); // Output: 5 (used before increment)
Console.WriteLine(b);   // Output: 6

Practical difference in expressions:

int x = 3;
int y = ++x * 2; // x becomes 4 first, then y = 4 * 2 = 8
Console.WriteLine($"x={x}, y={y}"); // Output: x=4, y=8

int p = 3;
int q = p++ * 2; // q = 3 * 2 = 6 first, then p becomes 4
Console.WriteLine($"p={p}, q={q}"); // Output: p=4, q=6

In loops — both produce the same result:

for (int i = 0; i < 3; i++)  // i++ and ++i behave identically here
    Console.Write(i + " ");
// Output: 0 1 2

Same applies to decrement: --i (pre-decrement) and i-- (post-decrement).

↥ back to top

# 3. CONTROL FLOW


Q. What are the conditional statements available in C# and how are they used?

C# provides several conditional statements to control program execution based on conditions: if, else if, else, and switch.

1. if / else if / else:

int score = 75;

if (score >= 90)
{
    Console.WriteLine("Grade: A");
}
else if (score >= 75)
{
    Console.WriteLine("Grade: B");
}
else if (score >= 60)
{
    Console.WriteLine("Grade: C");
}
else
{
    Console.WriteLine("Grade: F");
}
// Output: Grade: B

2. switch statement:

int day = 3;

switch (day)
{
    case 1:
        Console.WriteLine("Monday");
        break;
    case 2:
        Console.WriteLine("Tuesday");
        break;
    case 3:
        Console.WriteLine("Wednesday");
        break;
    default:
        Console.WriteLine("Other day");
        break;
}
// Output: Wednesday

3. switch expression (C# 8+):

A concise, expression-based alternative to the switch statement.

int day = 3;
string dayName = day switch
{
    1 => "Monday",
    2 => "Tuesday",
    3 => "Wednesday",
    4 => "Thursday",
    5 => "Friday",
    _ => "Weekend"
};
Console.WriteLine(dayName); // Output: Wednesday
↥ back to top

Q. What are the loop constructs available in C# and when should each be used?

C# provides four main loop constructs: for, foreach, while, and do-while.

1. for loop — when the number of iterations is known:

for (int i = 0; i < 5; i++)
{
    Console.Write(i + " ");
}
// Output: 0 1 2 3 4

2. foreach loop — iterating over a collection:

string[] fruits = { "Apple", "Banana", "Cherry" };

foreach (string fruit in fruits)
{
    Console.WriteLine(fruit);
}
// Output: Apple  Banana  Cherry

3. while loop — when the number of iterations is unknown:

int count = 0;

while (count < 5)
{
    Console.Write(count + " ");
    count++;
}
// Output: 0 1 2 3 4

4. do-while loop — executes at least once:

int number = 0;

do
{
    Console.Write(number + " ");
    number++;
} while (number < 5);
// Output: 0 1 2 3 4

Comparison:

Loop Use When
for Known iteration count
foreach Iterating over a collection/array
while Condition checked before each iteration
do-while Body must execute at least once
↥ back to top

Q. What is the difference between break, continue, and return in loops?

These three keywords alter the normal flow of a loop or method:

break — exits the loop immediately:

for (int i = 0; i < 10; i++)
{
    if (i == 5)
        break;
    Console.Write(i + " ");
}
// Output: 0 1 2 3 4

continue — skips the current iteration and moves to the next:

for (int i = 0; i < 10; i++)
{
    if (i % 2 == 0)
        continue;
    Console.Write(i + " ");
}
// Output: 1 3 5 7 9

return — exits the entire method:

int FindFirst(int[] numbers, int target)
{
    for (int i = 0; i < numbers.Length; i++)
    {
        if (numbers[i] == target)
            return i; // exits the method immediately
    }
    return -1;
}

int[] arr = { 10, 20, 30, 40 };
Console.WriteLine(FindFirst(arr, 30)); // Output: 2

Summary:

Keyword Effect
break Exits the current loop or switch
continue Skips to the next loop iteration
return Exits the current method, optionally with a value
↥ back to top

Q. What is the ternary operator and how is it used in C#?

The ternary operator ? : is a concise shorthand for a simple if-else statement. It evaluates a condition and returns one of two values.

Syntax: condition ? valueIfTrue : valueIfFalse

Example:

int age = 20;
string result = age >= 18 ? "Adult" : "Minor";
Console.WriteLine(result); // Output: Adult

Nested ternary (use sparingly for readability):

int score = 75;
string grade = score >= 90 ? "A"
             : score >= 75 ? "B"
             : score >= 60 ? "C"
             : "F";
Console.WriteLine(grade); // Output: B

Null-coalescing operator ?? (related):

Returns the left-hand operand if it is not null; otherwise returns the right-hand operand.

string? name = null;
string displayName = name ?? "Guest";
Console.WriteLine(displayName); // Output: Guest

Null-coalescing assignment ??= (C# 8+):

string? value = null;
value ??= "Default";
Console.WriteLine(value); // Output: Default
↥ back to top

Q. What is pattern matching in C# and how does it enhance control flow?

Pattern matching allows you to test an expression against a pattern and execute code based on the result. C# 8–14 significantly expanded pattern matching capabilities.

1. Type pattern:

object obj = 42;

if (obj is int number)
{
    Console.WriteLine($"Integer: {number}"); // Output: Integer: 42
}

2. Relational and logical patterns (C# 9+):

int temperature = 35;

string description = temperature switch
{
    < 0  => "Freezing",
    < 15 => "Cold",
    < 25 => "Mild",
    < 35 => "Warm",
    _    => "Hot"
};
Console.WriteLine(description); // Output: Hot

3. Property pattern:

public record Person(string Name, int Age);

var person = new Person("Alice", 17);

string category = person switch
{
    { Age: < 13 }        => "Child",
    { Age: < 18 }        => "Teenager",
    { Age: < 65 }        => "Adult",
    _                    => "Senior"
};
Console.WriteLine(category); // Output: Teenager

4. List pattern (C# 11+):

int[] numbers = { 1, 2, 3 };

string result = numbers switch
{
    [1, 2, 3]    => "Exact match",
    [1, ..]      => "Starts with 1",
    _            => "No match"
};
Console.WriteLine(result); // Output: Exact match
↥ back to top

Q. What is the goto statement in C# and when should it be used?

The goto statement transfers control to a labeled statement elsewhere in the same method. Its most common and accepted use in C# is within switch statements to fall through to another case.

Syntax:

goto labelName;
// ...
labelName:
    // code

Example — goto in a switch statement:

int option = 1;

switch (option)
{
    case 1:
        Console.WriteLine("Option 1 selected");
        goto case 3; // falls through to case 3
    case 2:
        Console.WriteLine("Option 2 selected");
        break;
    case 3:
        Console.WriteLine("Common handler");
        break;
}
// Output:
// Option 1 selected
// Common handler

Example — goto to exit nested loops:

for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 3; j++)
    {
        if (i == 1 && j == 1)
            goto done;
        Console.WriteLine($"i={i}, j={j}");
    }
}
done:
Console.WriteLine("Exited loops");

Note: Avoid goto for general control flow as it reduces readability. Prefer break, continue, or refactoring into methods.

↥ back to top

Q. What is the difference between while and do-while loops?

Both loops repeat a block of code while a condition is true, but they differ in when the condition is checked.

Example — condition is false from the start:

int x = 10;

// while: body never executes
while (x < 5)
{
    Console.WriteLine("while: " + x);
}
// (no output)

// do-while: body executes once regardless
do
{
    Console.WriteLine("do-while: " + x);
} while (x < 5);
// Output: do-while: 10

Practical use case — input validation:

string input;
do
{
    Console.Write("Enter a non-empty value: ");
    input = Console.ReadLine();
} while (string.IsNullOrWhiteSpace(input));

Console.WriteLine($"You entered: {input}");

Summary:

Feature while do-while
Condition check Before each iteration After each iteration
Minimum executions 0 (may never run) 1 (always runs at least once)
Best for Condition may fail from start Must run at least once (e.g., menus, validation)
↥ back to top

Q. How does the foreach loop work with IEnumerable<T>?

The foreach loop in C# works with any type that implements IEnumerable or IEnumerable<T>. Internally, the compiler calls GetEnumerator() and repeatedly calls MoveNext() / Current to iterate.

Compiler translation of foreach:

foreach (var item in collection)
    Console.WriteLine(item);

// Equivalent to:
var enumerator = collection.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        var item = enumerator.Current;
        Console.WriteLine(item);
    }
}
finally
{
    (enumerator as IDisposable)?.Dispose();
}

Example — iterating common collections:

// Array
string[] fruits = { "Apple", "Banana", "Cherry" };
foreach (string fruit in fruits)
    Console.WriteLine(fruit);

// List<T>
var numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers)
    Console.WriteLine(n);

// Dictionary<K,V>
var scores = new Dictionary<string, int> { ["Alice"] = 90, ["Bob"] = 85 };
foreach (var (name, score) in scores)  // deconstruction (C# 7+)
    Console.WriteLine($"{name}: {score}");

Custom IEnumerable<T> with yield return:

IEnumerable<int> GetEvenNumbers(int max)
{
    for (int i = 0; i <= max; i += 2)
        yield return i; // lazily produced
}

foreach (int n in GetEvenNumbers(10))
    Console.Write(n + " "); // Output: 0 2 4 6 8 10

Note: You cannot modify the collection being iterated inside a foreach — this throws an InvalidOperationException at runtime.

↥ back to top

Q. How do you use multiple case labels and fall-through in a switch statement?

C# switch does not fall through by default (unlike C/C++). You must use break, return, or goto case explicitly. However, you can stack multiple case labels on a single block.

1. Multiple case labels for the same block:

int day = 6;

switch (day)
{
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        Console.WriteLine("Weekday");
        break;
    case 6:
    case 7:
        Console.WriteLine("Weekend");
        break;
    default:
        Console.WriteLine("Invalid day");
        break;
}
// Output: Weekend

2. goto case for explicit fall-through:

int code = 1;

switch (code)
{
    case 1:
        Console.WriteLine("Code 1 — running extra logic");
        goto case 3; // explicitly jump to case 3
    case 2:
        Console.WriteLine("Code 2");
        break;
    case 3:
        Console.WriteLine("Shared handler for codes 1 and 3");
        break;
}
// Output:
// Code 1 — running extra logic
// Shared handler for codes 1 and 3

3. switch expression with multiple patterns (C# 8+):

int day = 6;
string type = day switch
{
    1 or 2 or 3 or 4 or 5 => "Weekday",  // or pattern (C# 9+)
    6 or 7                => "Weekend",
    _                     => "Invalid"
};
Console.WriteLine(type); // Output: Weekend

4. Pattern matching with when guards:

int score = 85;
string grade = score switch
{
    >= 90          => "A",
    >= 75 and < 90 => "B",  // and pattern (C# 9+)
    >= 60          => "C",
    _              => "F"
};
Console.WriteLine(grade); // Output: B
↥ back to top

# 3. CLASSES


Q. What is object-oriented programming?

Object-oriented programming (OOP) in C# is a programming paradigm based on the concept of “objects”, which are instances of classes. OOP enables developers to structure software in a modular way by organizing code into reusable components.

mindmap
  root((OOP))
    Encapsulation
      Bundles data and methods
      Restricts direct access
      Access modifiers
    Inheritance
      Reuse base class members
      IS-A relationship
      Supports polymorphism
    Polymorphism
      Method overriding
      Method overloading
      Runtime dispatch
    Abstraction
      Hides implementation details
      Abstract classes
      Interfaces

Key principles:

↥ back to top

Q. What is a constructor in C# and what are its different types?

A constructor is a special method that is automatically called when an object is instantiated. It initializes the object's state. Constructors have the same name as the class and no return type.

Types of constructors in C# (.NET 10 / C# 14):

1. Default (Parameterless) Constructor:

Automatically provided by the compiler if no constructor is defined.

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

var p = new Person(); // default constructor

2. Parameterized Constructor:

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

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

var p = new Person("Pradeep", 30);

3. Primary Constructor (C# 12+):

Parameters declared directly on the class — concise and idiomatic in .NET 8+.

public class Employee(string name, int id)
{
    public string Name { get; } = name;
    public int Id { get; } = id;
    public override string ToString() => $"{Id}: {Name}";
}

var emp = new Employee("Pradeep", 101);
Console.WriteLine(emp); // Output: 101: Pradeep

4. Copy Constructor:

Creates a new object as a copy of an existing object.

public class Point
{
    public int X, Y;
    public Point(int x, int y) { X = x; Y = y; }
    public Point(Point other) { X = other.X; Y = other.Y; } // copy
}

5. Static Constructor:

Called once before any static members are accessed or any instance is created. Cannot have parameters or access modifiers.

public class Config
{
    public static readonly string AppName;

    static Config()
    {
        AppName = "MyApp"; // runs once, automatically
        Console.WriteLine("Static constructor called");
    }
}

6. Private Constructor:

Used in Singleton patterns or factory methods to prevent direct instantiation.

public class Singleton
{
    private static readonly Singleton _instance = new();
    private Singleton() { }
    public static Singleton Instance => _instance;
}

7. Constructor Chaining (this() / base()):

public class Shape
{
    public string Color { get; }
    public Shape(string color) { Color = color; }
}

public class Circle : Shape
{
    public double Radius { get; }

    public Circle(double radius) : this(radius, "Red") { }

    public Circle(double radius, string color) : base(color)
    {
        Radius = radius;
    }
}

var c = new Circle(5.0);
Console.WriteLine($"{c.Color} circle, radius={c.Radius}"); // Red circle, radius=5
↥ back to top

Q. Can you explain the use of the “this” keyword in C#?

The this keyword refers to the current instance of a class. It has several uses:

Use Purpose
Disambiguate Distinguish between a field and a parameter with the same name
Constructor chaining Call another constructor in the same class via this(...)
Pass self as argument Pass the current instance to a method or event
Extension methods First parameter of an extension method refers to this
// 1. Disambiguate field vs parameter
public class Person
{
    private string name;
    private int age;

    public Person(string name, int age)
    {
        this.name = name; // 'this.name' = field; 'name' = parameter
        this.age  = age;
    }
}

// 2. Constructor chaining — delegate to another constructor
public class Order
{
    public int    Id       { get; }
    public string Product  { get; }
    public int    Quantity { get; }

    public Order(int id, string product) : this(id, product, 1) { }

    public Order(int id, string product, int quantity)
    {
        Id       = id;
        Product  = product;
        Quantity = quantity;
    }
}

var o1 = new Order(1, "Laptop");       // quantity defaults to 1
var o2 = new Order(2, "Phone", 3);
Console.WriteLine($"{o1.Product} x{o1.Quantity}"); // Laptop x1
Console.WriteLine($"{o2.Product} x{o2.Quantity}"); // Phone x3

// 3. Pass current instance as argument
public class Button
{
    public event Action<Button>? Clicked;
    public void Click() => Clicked?.Invoke(this); // passes self
}

// 4. Extension method — 'this' marks the extended type
public static class StringExtensions
{
    public static string Shorten(this string value, int maxLength) =>
        value.Length <= maxLength ? value : value[..maxLength] + "...";
}

Console.WriteLine("Hello, World!".Shorten(5)); // Hello...

// 5. Fluent builder — return 'this' for method chaining
public class QueryBuilder
{
    private readonly List<string> _parts = [];

    public QueryBuilder Select(string cols) { _parts.Add($"SELECT {cols}"); return this; }
    public QueryBuilder From(string table)  { _parts.Add($"FROM {table}");  return this; }
    public QueryBuilder Where(string cond)  { _parts.Add($"WHERE {cond}");  return this; }
    public string Build() => string.Join(" ", _parts);
}

string sql = new QueryBuilder()
    .Select("*")
    .From("Products")
    .Where("Price > 100")
    .Build();
Console.WriteLine(sql); // SELECT * FROM Products WHERE Price > 100
↥ back to top

Q. How are static constructors executed in a Parent/Child class hierarchy?

Static constructors are type-initializers — each runs exactly once, the first time its own class is used (accessed or instantiated). The execution order follows a simple rule: the static constructor of the base class runs before the static constructor of the derived class.

public class Base
{
    public static string Config;

    static Base()
    {
        Config = "Base initialized";
        Console.WriteLine("Base static constructor");
    }

    public Base() => Console.WriteLine("Base instance constructor");
}

public class Derived : Base
{
    public static string Extra;

    static Derived()
    {
        Extra = "Derived initialized";
        Console.WriteLine("Derived static constructor");
    }

    public Derived() => Console.WriteLine("Derived instance constructor");
}

// First use of Derived — triggers type initialization
var d = new Derived();
Console.WriteLine(Base.Config);
Console.WriteLine(Derived.Extra);

Output:

Base static constructor
Derived static constructor
Base instance constructor
Derived instance constructor
Base initialized
Derived initialized

Key rules:

↥ back to top

Q. If a base class has overloaded constructors, can you enforce a call from an inherited constructor to a specific base constructor?

Yes. Use the base(...) initializer to chain to a specific base constructor. This is evaluated before the derived constructor body runs.

public class Vehicle
{
    public string Make  { get; }
    public string Model { get; }
    public int    Year  { get; }

    public Vehicle(string make, string model)
        : this(make, model, DateTime.UtcNow.Year) { }

    public Vehicle(string make, string model, int year)
    {
        Make  = make;
        Model = model;
        Year  = year;
        Console.WriteLine($"Vehicle({make}, {model}, {year})");
    }
}

public class ElectricVehicle : Vehicle
{
    public int BatteryKwh { get; }

    // Explicitly calls Vehicle(make, model) — two-param base constructor
    public ElectricVehicle(string make, string model, int batteryKwh)
        : base(make, model)
    {
        BatteryKwh = batteryKwh;
        Console.WriteLine($"ElectricVehicle battery={batteryKwh} kWh");
    }

    // Explicitly calls Vehicle(make, model, year) — three-param base constructor
    public ElectricVehicle(string make, string model, int year, int batteryKwh)
        : base(make, model, year)
    {
        BatteryKwh = batteryKwh;
        Console.WriteLine($"ElectricVehicle battery={batteryKwh} kWh");
    }
}

var ev1 = new ElectricVehicle("Tesla", "Model 3", 82);
// Vehicle(Tesla, Model 3, 2026)
// ElectricVehicle battery=82 kWh

var ev2 = new ElectricVehicle("Tesla", "Cybertruck", 2023, 123);
// Vehicle(Tesla, Cybertruck, 2023)
// ElectricVehicle battery=123 kWh

Rules:

↥ back to top

Q. How do you access the constructor of one class from another class?

There are several ways to invoke or chain constructors across classes:

// 1. base() — call a specific base class constructor from a derived constructor
public class Animal
{
    public string Name { get; }
    public Animal(string name) { Name = name; }
}

public class Dog : Animal
{
    public string Breed { get; }
    public Dog(string name, string breed) : base(name) // calls Animal(string)
    {
        Breed = breed;
    }
}

var dog = new Dog("Rex", "Labrador");
Console.WriteLine($"{dog.Name}{dog.Breed}"); // Rex — Labrador

// 2. this() — call another constructor in the SAME class
public class Point
{
    public int X { get; }
    public int Y { get; }
    public int Z { get; }

    public Point(int x, int y) : this(x, y, 0) { }       // delegates to 3-param ctor
    public Point(int x, int y, int z) { X = x; Y = y; Z = z; }
}

// 3. Factory method — control instantiation from outside the class
public class Connection
{
    private Connection(string connectionString) { /* init */ }

    public static Connection Create(string connStr) => new Connection(connStr);
    public static Connection CreateDefault()       => new Connection("Server=localhost;");
}

var conn = Connection.Create("Server=prod;Database=MyDb;");

// 4. Activator.CreateInstance — dynamic instantiation via reflection
object instance = Activator.CreateInstance(typeof(Dog), "Buddy", "Poodle")!;
Console.WriteLine(((Dog)instance).Name); // Buddy

// 5. Dependency Injection (Microsoft.Extensions.DependencyInjection)
// The DI container resolves and calls constructors automatically
// services.AddScoped<IRepository, SqlRepository>();
// Constructor of SqlRepository is called by the container when resolved
↥ back to top

Q. What is the use of static constructors?

A static constructor (also called a type initializer) is used to initialize static fields or perform one-time setup logic that runs before any static member is accessed or any instance of the class is created. It is declared with static and no access modifier or parameters.

Common use cases:

// 1. Initialize static fields that require computation
public class MathConstants
{
    public static readonly double GoldenRatio;
    public static readonly double NaturalLog2;

    static MathConstants()
    {
        GoldenRatio = (1 + Math.Sqrt(5)) / 2;
        NaturalLog2 = Math.Log(2);
        Console.WriteLine("MathConstants initialized");
    }
}

Console.WriteLine(MathConstants.GoldenRatio); // 1.618...

// 2. Load configuration or resources once
public class AppConfig
{
    public static readonly Dictionary<string, string> Settings;

    static AppConfig()
    {
        // Load from environment / file on first use — only once
        Settings = new Dictionary<string, string>
        {
            ["AppName"] = Environment.GetEnvironmentVariable("APP_NAME") ?? "MyApp",
            ["Version"] = "1.0.0",
        };
    }
}

// 3. Register types / set up factories
public class SerializerFactory
{
    private static readonly Dictionary<string, Func<string, object>> _parsers;

    static SerializerFactory()
    {
        _parsers = new()
        {
            ["json"] = data => System.Text.Json.JsonDocument.Parse(data),
            ["csv"]  = data => data.Split(','),
        };
    }

    public static object Parse(string format, string data) =>
        _parsers.TryGetValue(format, out var parser)
            ? parser(data)
            : throw new NotSupportedException(format);
}

// 4. Guarantee thread-safe singleton initialization (CLR ensures this automatically)
public class Singleton
{
    public static readonly Singleton Instance;

    static Singleton()
    {
        Instance = new Singleton();
        Console.WriteLine("Singleton created");
    }

    private Singleton() { }
}

Key properties:

↥ back to top

Q. Can you explain the difference between a static and an instance method in C#?

  Static Method Instance Method
Belongs to The type itself An instance (object) of the type
Access Called via ClassName.Method() Called via objectRef.Method()
this keyword Not available Available — refers to the current object
Instance members Cannot access directly Can access all instance members
Static members Can access Can access
Memory One copy per type Logically one per instance (code shared, state per instance)
Overridable Cannot be virtual/override Can be virtual, abstract, override
public class Counter
{
    // Static field — shared across all instances
    private static int _totalCreated = 0;

    // Instance field — each object has its own copy
    private int _count = 0;
    public string Name { get; }

    public Counter(string name)
    {
        Name = name;
        _totalCreated++;           // modify shared state
    }

    // Instance method — operates on this specific object\'s _count
    public void Increment() => _count++;
    public void Decrement() => _count--;
    public int  GetCount()  => _count;

    // Static method — no 'this', accesses only static members
    public static int GetTotalCreated() => _totalCreated;
    public static Counter Create(string name) => new Counter(name); // factory
}

var a = new Counter("A");
var b = new Counter("B");

a.Increment(); a.Increment(); a.Increment(); // a._count = 3
b.Increment();                               // b._count = 1

Console.WriteLine(a.GetCount());             // 3
Console.WriteLine(b.GetCount());             // 1
Console.WriteLine(Counter.GetTotalCreated()); // 2 (shared across all instances)

// Static utility classes — all static methods, no instance needed
public static class MathHelper
{
    public static double CircleArea(double radius)  => Math.PI * radius * radius;
    public static double HypotenuseLeg(double a, double b) => Math.Sqrt(a * a + b * b);
}

Console.WriteLine(MathHelper.CircleArea(5));       // 78.54...
Console.WriteLine(MathHelper.HypotenuseLeg(3, 4)); // 5

When to choose:

↥ back to top

Q. What is an abstract class in C# and when is it used?

An abstract class is a class that cannot be instantiated directly. It acts as a base class providing shared implementation while forcing derived classes to implement specific members via abstract methods.

Key characteristics:

Example (.NET 10):

public abstract class Animal
{
    public string Name { get; }

    protected Animal(string name) => Name = name;

    // Abstract: must be overridden by derived classes
    public abstract string MakeSound();

    // Concrete: shared implementation
    public void Describe() =>
        Console.WriteLine($"{Name} says: {MakeSound()}");
}

public class Dog(string name) : Animal(name)
{
    public override string MakeSound() => "Woof!";
}

public class Cat(string name) : Animal(name)
{
    public override string MakeSound() => "Meow!";
}

Animal[] animals = [new Dog("Rex"), new Cat("Whiskers")];
foreach (var animal in animals)
    animal.Describe();
// Output:
// Rex says: Woof!
// Whiskers says: Meow!

When to use abstract classes:

↥ back to top

Q. What is the difference between an abstract class and an interface?

Both define contracts for derived types, but they differ in usage, capabilities, and design intent.

classDiagram
    class AbstractClass {
        <<abstract>>
        +fields: allowed
        +constructors: allowed
        +concreteMethod()
        +abstractMethod()*
        -privateMembers: allowed
    }
    class Interface {
        <<interface>>
        +fields: NOT allowed
        +constructors: NOT allowed
        +abstractMethod()*
        +defaultMethod() C#8+
        +staticMethod() C#8+
    }
    class ConcreteClass {
        +abstractMethod()
    }
    class AnotherClass {
        +abstractMethod()
    }
    AbstractClass <|-- ConcreteClass : extends (single only)
    Interface <|.. ConcreteClass : implements (multiple allowed)
    Interface <|.. AnotherClass : implements
Feature Abstract Class Interface (C# 8+)
Instantiation Cannot be instantiated Cannot be instantiated
Multiple inheritance Single base class only A class can implement many interfaces
Fields / State Can have fields and state No fields (only properties/methods)
Constructors Can have constructors Cannot have constructors
Access modifiers Supports all modifiers Members public by default
Default implementations Yes (concrete methods) Yes (C# 8+ default interface methods)
Static members Yes Yes (C# 8+)
Use case Shared base for related types Capability contract (unrelated types)

Abstract class example:

public abstract class Logger
{
    private readonly string _prefix = "[LOG]";
    public abstract void Write(string message);
    public void Info(string msg) => Write($"{_prefix} INFO: {msg}");
}

Interface with default implementation (C# 8+):

public interface ILogger
{
    void Write(string message);
    void Info(string msg) => Write($"[LOG] INFO: {msg}"); // default impl
}

Guideline: Use an interface to define a capability shared across unrelated types. Use an abstract class when related types share code and state.

↥ back to top

Q. What is reflection in .NET and how would you use it?

Reflection is the ability to inspect and interact with type metadata (classes, methods, properties, attributes) at runtime using the System.Reflection namespace. It enables dynamic loading, instantiation, and invocation without knowing types at compile time.

Common uses: Serialization frameworks, dependency injection containers, ORMs, test runners, and source generators.

Example — inspecting a type at runtime:

using System.Reflection;

public class Product
{
    public required string Name { get; init; }
    public decimal Price { get; init; }
    public void Display() => Console.WriteLine($"{Name}: {Price:C}");
}

// Inspect type
Type type = typeof(Product);
Console.WriteLine($"Type: {type.Name}");

foreach (var prop in type.GetProperties())
    Console.WriteLine($"  Property: {prop.Name} ({prop.PropertyType.Name})");

foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
    Console.WriteLine($"  Method: {method.Name}");

Example — dynamic instantiation and method invocation:

object instance = Activator.CreateInstance(typeof(Product),
    new object[] { }) ?? throw new InvalidOperationException();

// Set properties via reflection
PropertyInfo? nameProp = typeof(Product).GetProperty("Name");
nameProp?.SetValue(instance, "Laptop");

// Invoke method
MethodInfo? display = typeof(Product).GetMethod("Display");
display?.Invoke(instance, null); // Output: Laptop: $0.00

** .NET 10 note — prefer Source Generators over Reflection:**

Reflection has runtime overhead and is incompatible with Native AOT. In modern .NET, prefer:

↥ back to top

Q. What is a sealed class in C# and why is it used?

A sealed class is a class that cannot be inherited. Applying the sealed modifier prevents other classes from deriving from it.

Why use it:

Example:

public sealed class ImmutablePoint
{
    public int X { get; }
    public int Y { get; }

    public ImmutablePoint(int x, int y) => (X, Y) = (x, y);
    public double DistanceTo(ImmutablePoint other)
        => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));
    public override string ToString() => $"({X}, {Y})";
}

// var p = new DerivedPoint(); // Compile error — cannot inherit from sealed class

Sealed methods in unsealed classes:

You can seal a specific override to stop further overriding while keeping the class inheritable:

public class Base
{
    public virtual void Render() => Console.WriteLine("Base.Render");
}

public class Derived : Base
{
    public sealed override void Render() => Console.WriteLine("Derived.Render"); // no further override
}

Note: In .NET, many BCL types like string, StringBuilder, and HttpClient are sealed for performance and security.

↥ back to top

Q. What are the benefits of using a sealed class in C#?

1. Performance (JIT / Native AOT devirtualization):

When the JIT compiler or Native AOT knows a class is sealed, it can replace virtual method dispatch with direct calls — eliminating the vtable lookup overhead.

public sealed class PriceCalculator
{
    public decimal Calculate(decimal basePrice, decimal taxRate)
        => basePrice * (1 + taxRate);
}

var calc = new PriceCalculator();
// JIT devirtualizes Calculate() — compiled as a direct call, no vtable
Console.WriteLine(calc.Calculate(100m, 0.18m)); // Output: 118

2. Security & design correctness:

Prevents derived classes from overriding behaviour in ways that break invariants or security contracts.

public sealed class JwtTokenValidator
{
    private readonly string _secret;
    public JwtTokenValidator(string secret) => _secret = secret;

    public bool Validate(string token)
    {
        // Cannot be overridden and weakened by a subclass
        return token.StartsWith("valid"); // simplified
    }
}

3. Signals clear design intent:

Tells consumers of your API: “This type is complete — do not extend it.”

4. Supports pattern matching optimisation:

The C# compiler and JIT can generate exhaustiveness checks and optimise switch expressions when the type hierarchy is closed (via sealed).

public abstract class Shape { }
public sealed class Circle(double Radius) : Shape;
public sealed class Rectangle(double W, double H) : Shape;

double Area(Shape s) => s switch
{
    Circle c    => Math.PI * c.Radius * c.Radius,
    Rectangle r => r.W * r.H,
    _           => throw new ArgumentOutOfRangeException()
};
↥ back to top

Q. Is it possible for a sealed class to be used as a base class in C#?

No. A sealed class cannot be used as a base class. Attempting to inherit from it produces a compile-time error.

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

// Compile error: 'FileLogger' cannot derive from sealed type 'Logger'
// public class FileLogger : Logger { }

Why: The entire purpose of sealed is to prevent inheritance. The compiler enforces this as an error, not a warning.

Workaround — use composition instead of inheritance:

public class FileLogger
{
    private readonly Logger _logger = new Logger(); // compose, don\'t inherit

    public void Log(string path, string message)
    {
        File.AppendAllText(path, message + Environment.NewLine);
        _logger.Log(message); // delegate to sealed class
    }
}

Note: Many .NET BCL types are sealed for this reason — string, StringBuilder, HttpClient, DateTime. You extend their behaviour via extension methods or wrapper classes, not inheritance.

↥ back to top

Q. Is it possible for a sealed class in C# to define virtual methods?

No. A sealed class cannot declare new virtual methods. Since the class cannot be inherited, virtual methods would serve no purpose and the compiler rejects them.

// Compile error: 'SealedClass.Method()' cannot be virtual because 'SealedClass' is sealed
// public sealed class SealedClass
// {
//     public virtual void Method() { }
// }

However, a sealed class can override a virtual method from a base class, and it can mark that override as sealed override to stop further overriding (though once the class itself is sealed, no further derivation is possible anyway).

public abstract class Animal
{
    public virtual string Speak() => "...";
}

public sealed class Cat : Animal
{
    // OK: overriding a virtual method from the base class
    public override string Speak() => "Meow";
}

var cat = new Cat();
Console.WriteLine(cat.Speak()); // Output: Meow
↥ back to top

Q. Is it possible for a non-child class to define sealed methods in C#?

No. The sealed modifier on a method is only valid on an override method — it means “stop allowing further overriding of this inherited virtual method”. A class that is not part of an inheritance chain cannot use sealed on a method.

// Compile error: 'MyClass.Method()' cannot be sealed because it is not an override
// public class MyClass
// {
//     public sealed void Method() { }  // ERROR
// }

Valid use — sealed override in a derived class:

public class Base
{
    public virtual void Render() => Console.WriteLine("Base.Render");
}

public class Derived : Base
{
    // sealed override: stops any further class from overriding Render()
    public sealed override void Render() => Console.WriteLine("Derived.Render");
}

public class LeafDerived : Derived
{
    // Compile error: cannot override sealed member 'Derived.Render()'
    // public override void Render() { }
}

var d = new Derived();
d.Render(); // Output: Derived.Render

Summary: sealed on a method requires the method to be an override. It cannot be applied to brand-new methods or methods in classes that are not part of an inheritance chain.

↥ back to top

Q. How can abstract classes be used to implement the Template Method design pattern?

The Template Method pattern defines the skeleton of an algorithm in a base class, deferring specific steps to derived classes. The base class calls abstract (or virtual) methods in a fixed sequence — derived classes fill in the details without altering the overall flow.

Abstract classes are ideal here because they can have both the concrete template method (the skeleton) and abstract hook methods (the steps).

Example — Report generation pipeline (.NET 10):

public abstract class ReportGenerator
{
    // Template method — defines the fixed algorithm skeleton
    public void Generate()
    {
        FetchData();
        FormatData();
        Render();
        SendReport();
    }

    protected abstract void FetchData();   // must be implemented
    protected abstract void FormatData();  // must be implemented
    protected abstract void Render();      // must be implemented

    // Optional hook with default behaviour
    protected virtual void SendReport()
        => Console.WriteLine("Report saved locally.");
}

public class PdfReportGenerator : ReportGenerator
{
    protected override void FetchData()   => Console.WriteLine("Fetching data from SQL...");
    protected override void FormatData()  => Console.WriteLine("Formatting as PDF...");
    protected override void Render()      => Console.WriteLine("Rendering PDF...");
    protected override void SendReport()  => Console.WriteLine("Emailing PDF report.");
}

public class ExcelReportGenerator : ReportGenerator
{
    protected override void FetchData()  => Console.WriteLine("Fetching data from API...");
    protected override void FormatData() => Console.WriteLine("Formatting as Excel...");
    protected override void Render()     => Console.WriteLine("Rendering Excel file...");
    // Uses default SendReport() — saved locally
}

// Usage
ReportGenerator pdf = new PdfReportGenerator();
pdf.Generate();
Console.WriteLine();

ReportGenerator excel = new ExcelReportGenerator();
excel.Generate();

Output:

Fetching data from SQL...
Formatting as PDF...
Rendering PDF...
Emailing PDF report.

Fetching data from API...
Formatting as Excel...
Rendering Excel file...
Report saved locally.

Key takeaway: The base class (ReportGenerator) controls when each step runs. Subclasses control what each step does.

↥ back to top

Q. Can you give an example of how abstract classes can lead to implementation of anti-patterns such as the God Object pattern?

The God Object anti-pattern occurs when a single class takes on too many responsibilities. If a poorly-designed abstract base class accumulates abstractions for unrelated concerns, every subclass inherits this bloat — making the hierarchy rigid, hard to test, and hard to maintain.

Bad example — God Object abstract class:

// BAD: one abstract class handles authentication, logging, emailing, AND data access
public abstract class BaseService
{
    public abstract bool Authenticate(string username, string password);
    public abstract void LogActivity(string message);
    public abstract void SendEmail(string to, string body);
    public abstract IEnumerable<object> GetData(string query);
    public abstract void SaveData(object entity);
    public abstract void GenerateReport();
}

// Every derived class is forced to implement ALL of the above
public class UserService : BaseService
{
    public override bool Authenticate(string u, string p) => true;
    public override void LogActivity(string msg) => Console.WriteLine(msg);
    public override void SendEmail(string to, string body) { /* email logic */ }
    public override IEnumerable<object> GetData(string q) => Enumerable.Empty<object>();
    public override void SaveData(object e) { }
    public override void GenerateReport() { /* report logic */ }
}

Problems:

Good example — Segregated interfaces + abstract class per concern (SOLID):

public interface IAuthService   { bool Authenticate(string username, string password); }
public interface IEmailService  { void Send(string to, string body); }
public interface IDataRepository<T> { IEnumerable<T> GetAll(); void Save(T entity); }

public abstract class BaseUserService(IAuthService auth, IEmailService email)
{
    protected readonly IAuthService Auth = auth;
    protected readonly IEmailService Email = email;

    public abstract void OnUserRegistered(string username);
}

public class UserRegistrationService(IAuthService auth, IEmailService email)
    : BaseUserService(auth, email)
{
    public override void OnUserRegistered(string username)
    {
        Email.Send(username, "Welcome!");
        Console.WriteLine($"User {username} registered.");
    }
}

Key takeaway: Abstract classes should represent a single coherent abstraction. Use interfaces to compose unrelated capabilities rather than cramming them into one base class.

↥ back to top

Q. How can abstract classes be used to implement the Factory Method pattern?

The Factory Method pattern defines an abstract method for creating an object, letting subclasses decide which concrete type to instantiate. The base class orchestrates the workflow but delegates object creation to derived classes.

Example — Notification system (.NET 10):

// Product: the object being created
public abstract class Notification
{
    public abstract void Send(string recipient, string message);
}

public class EmailNotification : Notification
{
    public override void Send(string recipient, string message)
        => Console.WriteLine($"Email to {recipient}: {message}");
}

public class SmsNotification : Notification
{
    public override void Send(string recipient, string message)
        => Console.WriteLine($"SMS to {recipient}: {message}");
}

public class PushNotification : Notification
{
    public override void Send(string recipient, string message)
        => Console.WriteLine($"Push to {recipient}: {message}");
}

// Creator: defines the factory method and uses it in a template
public abstract class NotificationService
{
    // Factory method — subclasses decide what type to create
    protected abstract Notification CreateNotification();

    // Template: uses the factory method
    public void Notify(string recipient, string message)
    {
        var notification = CreateNotification(); // polymorphic creation
        notification.Send(recipient, message);
    }
}

// Concrete creators
public class EmailNotificationService : NotificationService
{
    protected override Notification CreateNotification() => new EmailNotification();
}

public class SmsNotificationService : NotificationService
{
    protected override Notification CreateNotification() => new SmsNotification();
}

public class PushNotificationService : NotificationService
{
    protected override Notification CreateNotification() => new PushNotification();
}

// Usage — client code depends on the abstract creator, not concrete types
NotificationService[] services =
[
    new EmailNotificationService(),
    new SmsNotificationService(),
    new PushNotificationService(),
];

foreach (var service in services)
    service.Notify("pradeep@example.com", "Your order has shipped!");

Output:

Email to pradeep@example.com: Your order has shipped!
SMS to pradeep@example.com: Your order has shipped!
Push to pradeep@example.com: Your order has shipped!

Key benefits:

↥ back to top

Q. What are the SOLID principles in C#?

SOLID is an acronym for five object-oriented design principles that lead to more maintainable, scalable, and testable software.

mindmap
  root((SOLID))
    S["S — Single Responsibility\nOne class, one reason to change"]
    O["O — Open / Closed\nOpen for extension,\nclosed for modification"]
    L["L — Liskov Substitution\nDerived types must be\nsubstitutable for base types"]
    I["I — Interface Segregation\nMany specific interfaces\nbetter than one general"]
    D["D — Dependency Inversion\nDepend on abstractions,\nnot concrete implementations"]

1. S — Single Responsibility Principle (SRP)

A class should have only one reason to change.

// Bad: one class does everything
public class Report { public void Generate() {} public void Save() {} public void Print() {} }

// Good: separate responsibilities
public class ReportGenerator { public string Generate() => "Report data"; }
public class ReportSaver { public void Save(string data, string path) => File.WriteAllText(path, data); }

2. O — Open/Closed Principle (OCP)

Open for extension, closed for modification. Use abstractions and polymorphism.

public abstract class Discount { public abstract decimal Apply(decimal price); }
public class SeasonalDiscount : Discount { public override decimal Apply(decimal p) => p * 0.9m; }
public class LoyaltyDiscount : Discount { public override decimal Apply(decimal p) => p * 0.85m; }

3. L — Liskov Substitution Principle (LSP)

Derived types must be substitutable for their base types without altering correctness.

public class Bird { public virtual void Fly() => Console.WriteLine("Flying"); }
public class Eagle : Bird { public override void Fly() => Console.WriteLine("Eagle soaring"); }
// Penguin cannot fly — violates LSP if it inherits Bird with Fly()

4. I — Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they don't use.

public interface IPrintable { void Print(); }
public interface IScannable { void Scan(); }

// Implement only what you need
public class SimplePrinter : IPrintable { public void Print() => Console.WriteLine("Printing..."); }
public class AllInOne : IPrintable, IScannable
{
    public void Print() => Console.WriteLine("Printing...");
    public void Scan() => Console.WriteLine("Scanning...");
}

5. D — Dependency Inversion Principle (DIP)

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

public interface IEmailSender { void Send(string to, string body); }
public class SmtpEmailSender : IEmailSender
{
    public void Send(string to, string body) => Console.WriteLine($"SMTP: {to} -> {body}");
}

public class OrderService(IEmailSender emailSender) // DI via primary constructor
{
    public void PlaceOrder(string item)
    {
        Console.WriteLine($"Order placed: {item}");
        emailSender.Send("customer@email.com", $"Your {item} is confirmed!");
    }
}

// Usage (.NET DI container)
// services.AddScoped<IEmailSender, SmtpEmailSender>();
// services.AddScoped<OrderService>();
↥ back to top

Q. How can abstract classes be used to implement the Dependency Inversion principle?

The Dependency Inversion Principle (DIP) states that high-level modules should depend on abstractions (abstract classes or interfaces), not on concrete implementations. Abstract classes act as the abstraction layer.

Example — payment processing (.NET 10):

// Abstraction (abstract class)
public abstract class PaymentProcessor
{
    public abstract bool ProcessPayment(decimal amount);
    public abstract void Refund(decimal amount);

    // Shared template logic
    public bool ExecuteTransaction(decimal amount)
    {
        Console.WriteLine($"Processing transaction: {amount:C}");
        return ProcessPayment(amount);
    }
}

// Low-level modules (concrete implementations)
public class StripeProcessor : PaymentProcessor
{
    public override bool ProcessPayment(decimal amount)
    { Console.WriteLine($"Stripe charge: {amount:C}"); return true; }
    public override void Refund(decimal amount)
        => Console.WriteLine($"Stripe refund: {amount:C}");
}

public class PayPalProcessor : PaymentProcessor
{
    public override bool ProcessPayment(decimal amount)
    { Console.WriteLine($"PayPal charge: {amount:C}"); return true; }
    public override void Refund(decimal amount)
        => Console.WriteLine($"PayPal refund: {amount:C}");
}

// High-level module depends on abstraction, NOT on Stripe or PayPal
public class OrderService(PaymentProcessor processor)
{
    public void PlaceOrder(string item, decimal price)
    {
        Console.WriteLine($"Order: {item}");
        processor.ExecuteTransaction(price);
    }
}

// Usage — swap processor without changing OrderService
var service = new OrderService(new StripeProcessor());
service.PlaceOrder("Laptop", 999m);
↥ back to top

Q. What is the difference between an abstract class and a concrete class in C#, and how does this relate to the Open-Closed principle?

Relationship to Open-Closed Principle (OCP): A system is open for extension (add new concrete subclasses) but closed for modification (the abstract base class never changes). You extend behavior by adding new subclasses rather than editing existing code.

// Abstract base — CLOSED for modification
public abstract class TaxCalculator
{
    public abstract decimal GetRate();        // must override
    public decimal Calculate(decimal price) => price * GetRate(); // fixed template
}

// Concrete classes — OPEN for extension (add without touching base)
public class UkTaxCalculator   : TaxCalculator { public override decimal GetRate() => 0.20m; }
public class UsTaxCalculator   : TaxCalculator { public override decimal GetRate() => 0.15m; }
public class IndianTaxCalculator : TaxCalculator { public override decimal GetRate() => 0.18m; }

// Usage
TaxCalculator[] calculators = [new UkTaxCalculator(), new UsTaxCalculator(), new IndianTaxCalculator()];
foreach (var c in calculators)
    Console.WriteLine($"{c.GetType().Name}: {c.Calculate(1000m):C}");
// Output:
// UkTaxCalculator: £200.00 ... etc.

Adding a CanadaTaxCalculator requires no changes to TaxCalculator or existing classes — that is OCP in action.

↥ back to top

Q. Can you give an example of how abstract classes can help to enforce the Interface Segregation principle?

The Interface Segregation Principle (ISP) says clients should not be forced to depend on methods they don't use. Abstract classes enforce ISP by providing small, focused abstractions — each abstract class defines only the operations relevant to one concern.

// Focused abstract classes (not one God class with 10 abstract methods)
public abstract class DataReader
{
    public abstract IEnumerable<string> Read(string source);
}

public abstract class DataWriter
{
    public abstract void Write(string destination, IEnumerable<string> data);
}

public abstract class DataTransformer
{
    public abstract IEnumerable<string> Transform(IEnumerable<string> input);
}

// Concrete implementations only inherit what they need
public class CsvReader : DataReader
{
    public override IEnumerable<string> Read(string path) =>
        File.ReadAllLines(path);
}

public class UpperCaseTransformer : DataTransformer
{
    public override IEnumerable<string> Transform(IEnumerable<string> input) =>
        input.Select(s => s.ToUpperInvariant());
}

public class ConsoleWriter : DataWriter
{
    public override void Write(string _, IEnumerable<string> data)
    {
        foreach (var line in data) Console.WriteLine(line);
    }
}

// Pipeline: each class only depends on its own focused abstraction
var reader      = new CsvReader();
var transformer = new UpperCaseTransformer();
var writer      = new ConsoleWriter();

var lines = reader.Read("data.csv");
var transformed = transformer.Transform(lines);
writer.Write("", transformed);

Key takeaway: CsvReader never knows about writing; ConsoleWriter never knows about reading — each abstract class is small and focused.

↥ back to top

Q. How can abstract classes be used to implement the Strategy design pattern?

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Abstract classes serve as the strategy contract, while concrete subclasses are the actual strategies.

Example — sorting strategies (.NET 10):

// Strategy contract
public abstract class SortStrategy
{
    public abstract void Sort(List<int> data);
    public void Execute(List<int> data)
    {
        Console.WriteLine($"Before: {string.Join(", ", data)}");
        Sort(data);
        Console.WriteLine($"After:  {string.Join(", ", data)}");
    }
}

// Concrete strategies
public class BubbleSortStrategy : SortStrategy
{
    public override void Sort(List<int> data)
    {
        // simplified bubble sort
        for (int i = 0; i < data.Count - 1; i++)
            for (int j = 0; j < data.Count - 1 - i; j++)
                if (data[j] > data[j + 1])
                    (data[j], data[j + 1]) = (data[j + 1], data[j]);
    }
}

public class LinqSortStrategy : SortStrategy
{
    public override void Sort(List<int> data)
    {
        var sorted = data.OrderBy(x => x).ToList();
        data.Clear();
        data.AddRange(sorted);
    }
}

// Context: accepts any strategy at runtime
public class Sorter(SortStrategy strategy)
{
    private SortStrategy _strategy = strategy;
    public void SetStrategy(SortStrategy strategy) => _strategy = strategy;
    public void Sort(List<int> data) => _strategy.Execute(data);
}

// Usage — swap strategies at runtime
var data = new List<int> { 5, 3, 8, 1, 9, 2 };
var sorter = new Sorter(new BubbleSortStrategy());
sorter.Sort(data);

data = [5, 3, 8, 1, 9, 2];
sorter.SetStrategy(new LinqSortStrategy());
sorter.Sort(data);
↥ back to top

Q. Is it possible to declare abstract methods as private in C#?

No. Abstract methods cannot be private. An abstract method must be overridden by a derived class, but a private member is not visible to derived classes — making it impossible to override.

The compiler enforces this with an error:

error CS0621: 'MyClass.Method()': virtual or abstract members cannot be private

Valid access modifiers for abstract methods:

Modifier Allowed on abstract method?
public … Yes
protected … Yes (most common)
internal … Yes
protected internal … Yes
private No — compile error
private protected No — compile error
public abstract class Shape
{
    public abstract double Area();       // … public
    protected abstract string Describe(); // … protected
    // private abstract void Init();    //  compile error
}
↥ back to top

Q. Is it possible for an abstract class to contain a main method in C#?

Yes. An abstract class can contain a static void Main (or static Task Main) method. The Main method is static, so it belongs to the type itself — not to any instance — and can coexist with abstract instance members.

public abstract class ApplicationBase
{
    // Abstract instance member — cannot instantiate ApplicationBase directly
    public abstract string GetAppName();

    // Concrete shared logic
    protected void PrintBanner()
        => Console.WriteLine($"=== {GetAppName()} ===");

    // Entry point — perfectly valid on an abstract class
    public static void Main(string[] args)
    {
        // Cannot do: new ApplicationBase() — it\'s abstract
        // But we can instantiate a concrete subclass:
        ApplicationBase app = new ConsoleApp();
        app.PrintBanner();
        Console.WriteLine("Main running in abstract class.");
    }
}

public class ConsoleApp : ApplicationBase
{
    public override string GetAppName() => "MyConsoleApp";
}
// Output:
// === MyConsoleApp ===
// Main running in abstract class.

Note: In modern .NET (C# 9+), top-level statements (Program.cs with no class) are the preferred entry point — you rarely put Main in any class, abstract or not.

↥ back to top

Q. Is it possible in C# that a class inherit from multiple abstract classes?

No. C# does not support multiple class inheritance — a class can inherit from only one base class (abstract or concrete). This is by design to avoid the “diamond problem”.

public abstract class Logger    { public abstract void Log(string msg); }
public abstract class Formatter { public abstract string Format(string msg); }

// Compile error: 'MyService' cannot have multiple base classes
// public class MyService : Logger, Formatter { }

Workaround — use multiple interfaces:

Interfaces (including default interface method implementations in C# 8+) allow a class to implement multiple contracts:

public interface ILogger    { void Log(string msg); }
public interface IFormatter { string Format(string msg); }

public class MyService : ILogger, IFormatter
{
    public void Log(string msg) => Console.WriteLine($"[LOG] {msg}");
    public string Format(string msg) => msg.ToUpperInvariant();
}

var svc = new MyService();
svc.Log(svc.Format("hello")); // Output: [LOG] HELLO

Summary: Single class inheritance + multiple interface implementation is the C# pattern for combining multiple contracts.

↥ back to top

Q. What is the difference between a sealed class and an unsealed class in C#?

Feature Sealed class Unsealed class
Inheritance Cannot be inherited Can be inherited
Virtual methods Cannot declare new virtual methods Can declare virtual methods
Performance JIT can devirtualize — faster calls Virtual dispatch overhead
Design intent Type is complete, extension forbidden Type is designed to be extended
Example in BCL string, HttpClient, DateTime Stream, Exception, DbContext
// Sealed — cannot inherit
public sealed class Circle
{
    public double Radius { get; }
    public Circle(double r) => Radius = r;
    public double Area() => Math.PI * Radius * Radius;
}

// Unsealed — can be inherited and extended
public class Shape
{
    public virtual double Area() => 0;
}

public class Rectangle(double w, double h) : Shape
{
    public override double Area() => w * h;
}

Shape s = new Rectangle(4, 5);
Console.WriteLine(s.Area()); // Output: 20

When to seal: Use sealed when you want to prevent unintended subclassing, improve performance, or express that the type is intentionally final.

↥ back to top

Q. How can you use a sealed class to prevent inheritance in C#?

Apply the sealed modifier to a class declaration. The compiler will then reject any attempt to derive from it.

public sealed class DatabaseConnection
{
    private readonly string _connectionString;

    public DatabaseConnection(string cs) => _connectionString = cs;

    public void Open()  => Console.WriteLine($"Opening: {_connectionString}");
    public void Close() => Console.WriteLine("Closing connection.");
}

// Compile error: cannot derive from sealed type 'DatabaseConnection'
// public class SqlConnection : DatabaseConnection { }

To seal only a specific method in an otherwise inheritable class, use sealed override:

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

public class ElectricCar : Vehicle
{
    // No subclass of ElectricCar can override StartEngine further
    public sealed override void StartEngine() => Console.WriteLine("Silent electric start");
}
↥ back to top

Q. Can we do Multiple inheritance with Abstract classes?

No. C# does not support multiple class inheritance — a class can have only one base class, whether abstract or concrete. This prevents the diamond-inheritance ambiguity problem.

public abstract class Flyable  { public abstract void Fly(); }
public abstract class Swimmable { public abstract void Swim(); }

// Compile error: cannot have multiple base classes
// public class Duck : Flyable, Swimmable { }

Solution — use multiple interfaces:

public interface IFlyable  { void Fly(); }
public interface ISwimmable { void Swim(); }

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

var duck = new Duck();
duck.Fly();   // Output: Duck flying
duck.Swim();  // Output: Duck swimming

A class can inherit from one abstract class and implement many interfaces simultaneously.

↥ back to top

Q. What is the difference between Abstract class and interface?

See the detailed comparison in What is the difference between an abstract class and an interface? above.

Quick summary:

Aspect Abstract Class Interface (C# 8+)
Inheritance Single only Implement many
State (fields) Yes No
Constructors Yes No
Default methods Yes (concrete methods) Yes (C# 8+ default impl)
Use when Related types share state/code Unrelated types share a capability
↥ back to top

Q. Why simple base class replace Abstract class?

A simple (concrete) base class can replace an abstract class when:

When to prefer a simple base class:

// All methods have defaults — no abstract needed
public class Logger
{
    public virtual void Log(string msg) => Console.WriteLine($"[INFO] {msg}");
    public virtual void Error(string msg) => Console.WriteLine($"[ERROR] {msg}");
}

public class FileLogger : Logger
{
    public override void Log(string msg) =>
        File.AppendAllText("app.log", msg + Environment.NewLine);
}

// Base class is usable on its own — fine as concrete
var log = new Logger();
log.Log("Starting app");

When abstract is the right choice: Use abstract when the base class cannot meaningfully function on its own and derived classes must provide implementation (e.g., Area() on Shape).

Rule of thumb: If the base class can stand alone and all methods have reasonable defaults ’ concrete base class. If the base class is incomplete without subclasses ’ abstract class.

↥ back to top

Q. What are nested classes and when to use them?

A nested class is a class defined inside another class. It has access to the outer class's private members and is logically tied to it.

When to use:

public class LinkedList<T>
{
    private Node? _head;

    // Nested class — only LinkedList needs to know about Node
    private class Node
    {
        public T Value;
        public Node? Next;
        public Node(T value) { Value = value; }
    }

    public void Add(T value)
    {
        var node = new Node(value);
        node.Next = _head;
        _head = node;
    }

    public void Print()
    {
        for (var n = _head; n != null; n = n.Next)
            Console.Write($"{n.Value} ");
        Console.WriteLine();
    }
}

var list = new LinkedList<int>();
list.Add(1); list.Add(2); list.Add(3);
list.Print(); // Output: 3 2 1
↥ back to top

Q. Can Nested class access outer class variables?

Yes, a nested class can access private and protected static members of the outer class directly. For instance members of the outer class, the nested class needs a reference to the outer instance.

public class Outer
{
    private static string _staticSecret = "outer-static";
    private string _instanceSecret = "outer-instance";

    public class Inner
    {
        public void ShowStatic()
            => Console.WriteLine(_staticSecret); // … direct access to outer static

        public void ShowInstance(Outer outer)
            => Console.WriteLine(outer._instanceSecret); // … via outer reference
    }
}

var inner = new Outer.Inner();
inner.ShowStatic();               // Output: outer-static
inner.ShowInstance(new Outer());  // Output: outer-instance

Key point: The nested class has special visibility into the outer class's private members — this is unlike a regular external class.

↥ back to top

Q. Can we have public, protected access modifiers in nested class?

Yes. A nested class can have any access modifier: public, protected, internal, private, protected internal, or private protected.

public class OuterClass
{
    // public nested — accessible from anywhere
    public class PublicNested
    {
        public void Hello() => Console.WriteLine("Public nested");
    }

    // protected nested — accessible only in OuterClass and its subclasses
    protected class ProtectedNested
    {
        public void Hello() => Console.WriteLine("Protected nested");
    }

    // private nested — only accessible within OuterClass
    private class PrivateNested
    {
        public void Hello() => Console.WriteLine("Private nested");
    }

    public void Demo()
    {
        new PublicNested().Hello();    // …
        new ProtectedNested().Hello(); // …
        new PrivateNested().Hello();   // …
    }
}

public class Derived : OuterClass
{
    public void Test()
    {
        new PublicNested().Hello();    // …
        new ProtectedNested().Hello(); // … — accessible via inheritance
        // new PrivateNested().Hello(); //  not accessible
    }
}

// From outside:
new OuterClass.PublicNested().Hello(); // …
// new OuterClass.ProtectedNested();   // 
↥ back to top

Q. Are private class members inherited to the derived class?

Private members are inherited but not accessible in derived classes. They exist in the derived object's memory layout but cannot be referenced by name in the derived class code.

public class Base
{
    private int _secret = 42;             // inherited but inaccessible
    protected int Protected = 100;        // inherited and accessible
    public string Name { get; set; } = "Base";
}

public class Derived : Base
{
    public void Show()
    {
        Console.WriteLine(Name);      // … public member
        Console.WriteLine(Protected); // … protected member
        // Console.WriteLine(_secret); //  compile error — private
    }
}

Note: You can access private base members indirectly via public or protected methods/properties of the base class.

↥ back to top

Q. Where is a protected class-level variable available?

A protected member is available:

  1. Within the same class where it is declared.
  2. In any derived class (regardless of assembly).

It is not accessible from unrelated classes or from outside the inheritance hierarchy.

public class Animal
{
    protected string Species = "Unknown"; // available here and in subclasses
}

public class Dog : Animal
{
    public void ShowSpecies()
        => Console.WriteLine(Species); // … accessible in derived class
}

// Outside the hierarchy:
var a = new Animal();
// Console.WriteLine(a.Species); //  compile error — not accessible here
↥ back to top

Q. Are private class-level variables inherited?

Yes, they are part of the object's memory, but not accessible by name in derived classes. See Are private class members inherited to the derived class? above for a full example.

Quick answer: private members are inherited (exist in memory) but not accessible (compile error if referenced) in derived classes.

↥ back to top

Q. Which class is at the top of .NET class hierarchy?

System.Object (object in C#) is at the top of the .NET class hierarchy. Every class, struct (when boxed), and record ultimately derives from System.Object.

// All of these inherit from System.Object:
object o    = new object();
string s    = "hello";     // string ’ object
int boxed   = 42;          // int ’ ValueType ’ object (when boxed)
object arr  = new int[5];  // Array ’ object

Console.WriteLine(typeof(string).BaseType);     // System.Object
Console.WriteLine(typeof(Exception).BaseType);  // System.Object
Console.WriteLine(42.GetType().BaseType);       // System.ValueType

// object provides: Equals(), GetHashCode(), ToString(), GetType(), MemberwiseClone()
Console.WriteLine(o.GetType()); // System.Object
↥ back to top

Q. What is the .NET collection class that allows an element to be accessed using a unique key?

Dictionary<TKey, TValue> is the primary class for key-based element access. It provides O(1) average-time lookups.

var capitals = new Dictionary<string, string>
{
    ["India"]   = "New Delhi",
    ["France"]  = "Paris",
    ["Japan"]   = "Tokyo",
};

// Access by key
Console.WriteLine(capitals["India"]); // Output: New Delhi

// Safe access
if (capitals.TryGetValue("France", out string? capital))
    Console.WriteLine(capital); // Output: Paris

// Iterate
foreach (var (country, city) in capitals)
    Console.WriteLine($"{country}: {city}");

Other key-based collections in .NET:

Type Use case
Dictionary<K,V> General mutable key-value store
ConcurrentDictionary<K,V> Thread-safe key-value store
FrozenDictionary<K,V> (.NET 8+) Immutable, read-optimised dictionary
SortedDictionary<K,V> Keys kept in sorted order
Hashtable Non-generic legacy (avoid in new code)
↥ back to top

Q. Explain the three services model commonly known as a three-tier application?

A three-tier architecture separates an application into three logical layers, each with a distinct responsibility:

graph TD
    U[" User / Browser / Client App"]
    U --> P

    subgraph Tier1["Presentation Layer (UI)"]
        P["Views, Controllers, API Endpoints\nInput validation, User interaction"]
    end

    P --> B

    subgraph Tier2["Business Logic Layer (BLL)"]
        B["Services, Business Rules\nWorkflows, Calculations, Validation"]
    end

    B --> D

    subgraph Tier3["Data Access Layer (DAL)"]
        D["Repositories, ORM\nDatabase queries, External APIs"]
    end

    D --> DB[("— Database\nSQL Server / PostgreSQL / etc.")]
Tier Also called Responsibility
Presentation Layer UI / Front-end User interaction, display, input validation
Business Logic Layer Application / BLL Business rules, workflows, calculations
Data Access Layer DAL / Persistence Database or external service communication

Example — ASP.NET Core Web API (.NET 10):

// 1. Data Access Layer (DAL)
public interface IProductRepository
{
    Task<Product?> GetByIdAsync(int id);
}

public class ProductRepository(AppDbContext db) : IProductRepository
{
    public async Task<Product?> GetByIdAsync(int id)
        => await db.Products.FindAsync(id);
}

// 2. Business Logic Layer (BLL)
public class ProductService(IProductRepository repo)
{
    public async Task<string> GetProductNameAsync(int id)
    {
        var product = await repo.GetByIdAsync(id)
            ?? throw new KeyNotFoundException($"Product {id} not found");
        return product.Name.ToUpperInvariant(); // business rule
    }
}

// 3. Presentation Layer (Controller / API endpoint)
[ApiController, Route("api/products")]
public class ProductsController(ProductService service) : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        var name = await service.GetProductNameAsync(id);
        return Ok(new { Name = name });
    }
}

Benefits: Independent scalability, testability, and maintainability of each tier.

↥ back to top

Q. Can you prevent your class from being inherited by another class?

Yes — apply the sealed modifier to the class:

public sealed class Singleton
{
    private static readonly Singleton _instance = new();
    private Singleton() { }
    public static Singleton Instance => _instance;

    public void DoWork() => Console.WriteLine("Working...");
}

// Compile error: cannot derive from sealed type 'singleton'
// public class MySingleton : Singleton { }
↥ back to top

Q. Can you allow a class to be inherited, but prevent the method from being over-ridden?

Yes — apply sealed on a specific override method, while keeping the class itself unsealed:

public class BaseLogger
{
    public virtual void Log(string msg) => Console.WriteLine($"Base: {msg}");
    public virtual void Error(string msg) => Console.WriteLine($"Error: {msg}");
}

public class FileLogger : BaseLogger
{
    // sealed override: subclasses of FileLogger cannot override Log()
    public sealed override void Log(string msg)
        => File.AppendAllText("app.log", msg + Environment.NewLine);

    // Error() is NOT sealed — can be overridden further
    public override void Error(string msg)
        => File.AppendAllText("error.log", msg + Environment.NewLine);
}

public class AdvancedFileLogger : FileLogger
{
    // public override void Log(string msg) { } //  compile error — sealed
    public override void Error(string msg) => Console.WriteLine($"ALERT: {msg}"); // …
}
↥ back to top

Q. When do you absolutely have to declare a class as abstract?

You must declare a class abstract when it contains one or more abstract members (methods, properties, indexers, or events with no implementation). The compiler requires this because an incomplete class cannot be instantiated.

// Must be abstract — it has an abstract member
public abstract class Shape
{
    public abstract double Area(); // no body — derived class MUST implement
    public void Describe() => Console.WriteLine($"Area = {Area():F2}");
}

// Compile error if not abstract but contains abstract member:
// public class BadShape { public abstract double Area(); } // 

Other scenarios where abstract is the right choice (design decision, not enforced):

↥ back to top

Q. What is the implicit name of the parameter that gets passed into the set method/property of a class?

The implicit parameter name is value. It holds the value being assigned to the property.

public class Person
{
    private string _name = string.Empty;

    public string Name
    {
        get => _name;
        set
        {
            // 'value' is the implicit parameter — holds what the caller assigns
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Name cannot be empty.");
            _name = value.Trim();
        }
    }
}

var p = new Person();
p.Name = "  Pradeep  "; // 'value' = "  Pradeep  "
Console.WriteLine(p.Name); // Output: Pradeep

In C# 13+, the field keyword is also available inside property accessors to reference the auto-generated backing field directly, but value remains the parameter name for the setter.

↥ back to top

Q. When you inherit a protected class-level variable, who is it available to?

A protected variable is available to:

It is not available to unrelated classes, even within the same assembly (use internal for that).

public class Base { protected int Value = 10; }

public class Child : Base
{
    public void Show() => Console.WriteLine(Value); // … accessible
}

public class Unrelated
{
    public void Test(Base b)
    {
        // Console.WriteLine(b.Value); //  not accessible from unrelated class
    }
}
↥ back to top

Q. What is the top .NET class that everything is derived from?

System.Object (alias object). See Which class is at the top of .NET class hierarchy? above.

↥ back to top

Q. When do you absolutely have to declare a class as abstract (as opposed to free-willed educated choice or decision based on UML diagram)?

You are forced by the compiler to use abstract when the class contains at least one abstract member (a member declared without a body). Without abstract on the class, the code will not compile.

// FORCED: contains abstract member ’ class must be abstract
public abstract class DataExporter //  required by compiler
{
    public abstract void Export(string data); //  forces class to be abstract
    public void Log(string msg) => Console.WriteLine(msg);
}

In all other cases, abstract is a design choice — you opt in to signal that the type is incomplete by intent.

↥ back to top

Q. Is it namespace class or class namespace?

The correct syntax is namespace then class — namespaces contain classes, not the other way around.

// Correct: namespace wraps the class
namespace MyApp.Services
{
    public class OrderService
    {
        public void Process() => Console.WriteLine("Processing order");
    }
}

// File-scoped namespace (C# 10+) — preferred modern style
namespace MyApp.Services;

public class OrderService
{
    public void Process() => Console.WriteLine("Processing order");
}

A class belongs to a namespace; a namespace cannot belong to a class.

↥ back to top

Q. What is the default Access Modifier for the members of the class?

The default access modifier for class members (fields, methods, properties, etc.) is private.

The default for the class itself (when declared directly in a namespace) is internal.

namespace MyApp;

class MyClass          // default: internal (visible only within the assembly)
{
    int _field;         // default: private
    void Method() { }  // default: private
    string Prop { get; set; } // default: private

    public int PublicField = 0; // explicitly public
}
Context Default modifier
Class in namespace internal
Class member private
Interface member (C# 8+) public
Enum member public
Struct member private
↥ back to top

Q. How to Call the Default constructor of one class with the parameterized constructor of same class?

Use constructor chaining with the this() keyword:

public class Person
{
    public string Name { get; }
    public int Age { get; }
    public string Country { get; }

    // Default constructor chains to the parameterized one with defaults
    public Person() : this("Unknown", 0, "India") { }

    // Partial parameterized chains to the full constructor
    public Person(string name) : this(name, 0, "India") { }

    // Full parameterized constructor — all others delegate here
    public Person(string name, int age, string country)
    {
        Name    = name;
        Age     = age;
        Country = country;
    }
}

var p1 = new Person();                    // Name=Unknown, Age=0, Country=India
var p2 = new Person("Pradeep");           // Name=Pradeep, Age=0, Country=India
var p3 = new Person("Pradeep", 30, "IN"); // Name=Pradeep, Age=30, Country=IN

Console.WriteLine($"{p1.Name}, {p1.Country}"); // Unknown, India
Console.WriteLine($"{p2.Name}, {p2.Country}"); // Pradeep, India
↥ back to top

Q. What is scope of a Protected Internal member variable of a C# class?

A protected internal member is accessible:

It is the most permissive combined modifier.

// Assembly A
public class Base
{
    protected internal string Data = "shared";
}

// Assembly A — unrelated class (same assembly ’ internal part grants access)
public class Unrelated
{
    public void Test(Base b) => Console.WriteLine(b.Data); // …
}

// Assembly B — derived class (protected part grants access)
public class Derived : Base
{
    public void Show() => Console.WriteLine(Data); // …
}

// Assembly B — unrelated class
public class External
{
    // Console.WriteLine(new Base().Data); //  not accessible
}
↥ back to top

Q. What is the difference between Virtual method and Abstract method?

Feature virtual method abstract method
Body Has a default implementation No body — derived class must implement
Class requirement Class can be concrete or abstract Class must be abstract
Override required Optional — derived class may override Mandatory — derived class must override
Instantiation Containing class can be instantiated Containing class cannot be instantiated
public abstract class Animal
{
    // abstract — NO body, MUST be overridden
    public abstract string MakeSound();

    // virtual — HAS a body, CAN be overridden
    public virtual string Describe()
        => $"I am a {GetType().Name} and I say {MakeSound()}";
}

public class Dog : Animal
{
    public override string MakeSound() => "Woof"; // required
    // Describe() not overridden — uses base implementation
}

public class Cat : Animal
{
    public override string MakeSound() => "Meow";   // required
    public override string Describe() => "I'm a cat. Meow!"; // optional
}

Animal[] animals = [new Dog(), new Cat()];
foreach (var a in animals)
    Console.WriteLine(a.Describe());
// Output:
// I am a Dog and I say Woof
// I'm a cat. Meow!
↥ back to top

Q. What is scope of a Internal member variable of a C# class?

An internal member is accessible from anywhere within the same assembly (project/DLL), but not from external assemblies.

// Assembly A (MyApp.dll)
public class Configuration
{
    internal string ConnectionString = "Server=localhost;"; // same assembly only
    public string AppName = "MyApp"; // accessible everywhere
}

// Same assembly — OK
public class DatabaseService
{
    public void Connect()
    {
        var config = new Configuration();
        Console.WriteLine(config.ConnectionString); // … same assembly
    }
}

// Assembly B — external project referencing MyApp.dll
// var cfg = new Configuration();
// Console.WriteLine(cfg.ConnectionString); //  not accessible externally

Tip: Use [assembly: InternalsVisibleTo("TestProject")] to expose internal members to a test assembly without making them public.

↥ back to top

Q. How would you implement multiple interfaces with the same method name in the same class?

Use explicit interface implementation to resolve the naming conflict:

public interface IDrawable { void Draw(); }
public interface IPrintable { void Draw(); } // same method name

public class Document : IDrawable, IPrintable
{
    // Explicit implementation — called only via the interface reference
    void IDrawable.Draw()  => Console.WriteLine("Drawing to screen");
    void IPrintable.Draw() => Console.WriteLine("Printing to paper");

    // Optional: a public method calling one of them
    public void Render() => ((IDrawable)this).Draw();
}

var doc = new Document();
doc.Render(); // Output: Drawing to screen

// Call via interface reference
IDrawable  drawable  = doc;
IPrintable printable = doc;

drawable.Draw();  // Output: Drawing to screen
printable.Draw(); // Output: Printing to paper

Note: Explicitly implemented members are not accessible directly on the class instance — only via a cast to the interface type.

↥ back to top

Q. How can you create a derived class object from a base class?

You cannot directly instantiate an abstract base class, but you can hold a derived class object in a base class reference (polymorphism):

public abstract class Animal
{
    public abstract string Sound();
}

public class Dog : Animal
{
    public override string Sound() => "Woof";
}

// Base class reference pointing to derived class object
Animal animal = new Dog(); // … upcasting (implicit)
Console.WriteLine(animal.Sound()); // Output: Woof

// Downcast when you need derived-specific members
if (animal is Dog dog)
    Console.WriteLine($"Dog instance: {dog.Sound()}");

With factory / virtual constructor pattern (.NET 10):

public abstract class Shape
{
    public abstract double Area();
    public static Shape CreateCircle(double r) => new Circle(r);
}

public sealed class Circle(double r) : Shape
{
    public override double Area() => Math.PI * r * r;
}

Shape s = Shape.CreateCircle(5);
Console.WriteLine(s.Area()); // Output: 78.54...
↥ back to top

Q. Which is the parent class of all classes which we create in C#?

System.Object (object) is the implicit parent of every class in C#. When you define a class without an explicit base, it implicitly inherits from object.

public class MyClass { } // implicitly: public class MyClass : object { }

var obj = new MyClass();
Console.WriteLine(obj.GetType());        // MyClass
Console.WriteLine(obj.ToString());       // MyClass
Console.WriteLine(obj.GetHashCode());    // some hash
Console.WriteLine(obj.Equals(new MyClass())); // False (reference equality by default)

Methods inherited from object: Equals(), GetHashCode(), ToString(), GetType(), MemberwiseClone(), Finalize().

↥ back to top

Q. What is the base class in .NET framework from which all the classes have been developed?

System.Object is the ultimate base class for all types in .NET. In .NET 10 (CoreCLR), this is the same — System.Object sits at the root of the entire type hierarchy.

// Verify any type\'s chain back to System.Object
Type t = typeof(HttpClient);
while (t != null)
{
    Console.WriteLine(t.FullName);
    t = t.BaseType!;
}
// Output (partial):
// System.Net.Http.HttpClient
// System.Object
↥ back to top

Q. How do you implement a custom attribute in C#?

Derive a class from System.Attribute, mark it with [AttributeUsage], and apply it with [...] syntax.

// 1. Define the attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class AuditAttribute : Attribute
{
    public string Author { get; }
    public string Version { get; }

    public AuditAttribute(string author, string version = "1.0")
    {
        Author  = author;
        Version = version;
    }
}

// 2. Apply the attribute
[Audit("Pradeep", "2.0")]
public class OrderService
{
    [Audit("Pradeep")]
    public void PlaceOrder(string item) => Console.WriteLine($"Order: {item}");
}

// 3. Read the attribute via reflection
var attr = typeof(OrderService).GetCustomAttribute<AuditAttribute>();
Console.WriteLine($"Author: {attr?.Author}, Version: {attr?.Version}");
// Output: Author: Pradeep, Version: 2.0

** Note for .NET 10 / Native AOT:** Reflection-based attribute reading works but is trimmed by the AOT compiler. Prefer source generators or [DynamicallyAccessedMembers] annotations when targeting Native AOT.

↥ back to top

Q. What are the System.String and System.Text.StringBuilder classes?

Both represent text, but with different performance characteristics:

Feature System.String System.Text.StringBuilder
Mutability Immutable — every change creates a new object Mutable — modifies in place
Thread safety Inherently safe (immutable) Not thread-safe
Performance Fine for few concatenations Efficient for many concatenations
Memory New allocation per modification Single buffer, grows as needed
Namespace System System.Text

String (immutable):

string s = "Hello";
s += " World"; // creates a new string object
Console.WriteLine(s); // Hello World

StringBuilder (mutable):

using System.Text;

var sb = new StringBuilder();
for (int i = 0; i < 5; i++)
    sb.Append($"Line {i}\n");

string result = sb.ToString();
Console.WriteLine(result);

Rule: Use string for a small number of operations. Use StringBuilder for loops or building large strings dynamically.

↥ back to top

Q. What are I/O classes in C#?

The System.IO namespace provides classes for reading from and writing to files, directories, and streams.

Key I/O classes:

Class Purpose
File Static methods for quick file operations
FileInfo Instance-based file metadata and operations
Directory Static methods for directory operations
StreamReader Read text from a stream
StreamWriter Write text to a stream
FileStream Low-level binary file stream
MemoryStream In-memory stream (no file system)
BinaryReader/Writer Read/write primitive types as binary
Path Platform-safe path manipulation

Example — modern async file I/O (.NET 10):

using System.IO;

// Write
await File.WriteAllTextAsync("output.txt", "Hello, .NET 10!");

// Read all lines
string[] lines = await File.ReadAllLinesAsync("output.txt");
foreach (var line in lines)
    Console.WriteLine(line);

// Stream-based read (large files)
await using var reader = new StreamReader("output.txt");
while (await reader.ReadLineAsync() is { } line)
    Console.WriteLine(line);

// Directory operations
string dir = Path.Combine("MyApp", "Logs");
Directory.CreateDirectory(dir);
Console.WriteLine(Directory.Exists(dir)); // True
↥ back to top

Q. Can you add extension methods to an existing static class?

No — you cannot add extension methods to a static class (i.e., you cannot extend a static class's API with new instance-style methods, because static classes cannot be used as a type that has instances or be referenced via this).

However, you can create extension methods in a static class to extend other types (including non-static classes):

// Extension methods LIVE IN a static class, but they extend OTHER types
public static class StringExtensions
{
    // Extends 'string' — not the static class itself
    public static bool IsPalindrome(this string s)
    {
        var reversed = new string(s.Reverse().ToArray());
        return s.Equals(reversed, StringComparison.OrdinalIgnoreCase);
    }
}

Console.WriteLine("racecar".IsPalindrome()); // True
Console.WriteLine("hello".IsPalindrome());   // False

Attempting to extend a static class itself:

public static class MathHelper { }

//  Cannot write: public static void NewMethod(this MathHelper m) { }
// MathHelper has no instances — 'this MathHelper' is meaningless
↥ back to top

Q. Can you create sealed abstract class in C#?

No. sealed and abstract are contradictory modifiers:

Combining them produces a compile error:

// Compile error: abstract and sealed cannot be combined
// public sealed abstract class MyClass { }

The closest valid pattern is a static class — it cannot be instantiated or inherited and can contain only static members:

public static class Utilities
{
    public static string Reverse(string s) => new string(s.Reverse().ToArray());
}
↥ back to top

Q. Can you inherit multiple interfaces?

Yes. A class (or struct) can implement any number of interfaces:

public interface IReadable  { string Read(); }
public interface IWritable  { void Write(string data); }
public interface ICloseable { void Close(); }

public class FileHandler : IReadable, IWritable, ICloseable
{
    private readonly List<string> _buffer = [];

    public string Read()           => string.Join("\n", _buffer);
    public void Write(string data) => _buffer.Add(data);
    public void Close()            => _buffer.Clear();
}

var fh = new FileHandler();
fh.Write("Hello");
fh.Write("World");
Console.WriteLine(fh.Read()); // Hello\nWorld
fh.Close();

An interface can also inherit from multiple interfaces:

public interface IReadWritable : IReadable, IWritable { }
↥ back to top

Q. What interface should your data structure implement to make the “Where” method work?

Your type must implement IEnumerable<T> (from System.Collections.Generic). LINQ extension methods including Where, Select, OrderBy etc. operate on any IEnumerable<T>.

using System.Collections;
using System.Collections.Generic;

public class NumberBag : IEnumerable<int>
{
    private readonly List<int> _numbers = [];

    public void Add(int n) => _numbers.Add(n);

    // Required by IEnumerable<int>
    public IEnumerator<int> GetEnumerator() => _numbers.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

var bag = new NumberBag();
foreach (var n in new[] { 1, 2, 3, 4, 5, 6 }) bag.Add(n);

// Where works because NumberBag implements IEnumerable<int>
var evens = bag.Where(n => n % 2 == 0).ToList();
Console.WriteLine(string.Join(", ", evens)); // Output: 2, 4, 6
↥ back to top

Q. Can we define methods as private in interface?

Yes — since C# 8 / .NET Core 3, interfaces can define private methods. Private interface methods can only be called from within the interface itself (typically from default implementations) and cannot be overridden by implementing classes.

public interface IGreeter
{
    void Greet(string name);

    // Private helper — only callable within this interface
    private string FormatName(string name)
        => name.Trim().ToUpperInvariant();

    // Default implementation calls the private helper
    void GreetFormal(string name)
        => Console.WriteLine($"Good day, {FormatName(name)}.");
}

public class EnglishGreeter : IGreeter
{
    public void Greet(string name) => Console.WriteLine($"Hello, {name}!");
    // GreetFormal comes from the default implementation
}

IGreeter g = new EnglishGreeter();
g.Greet("pradeep");        // Output: Hello, pradeep!
g.GreetFormal("pradeep");  // Output: Good day, PRADEEP.
↥ back to top

Q. If I want to change an interface, what's the best practice?

Changing a published interface is a breaking change — all implementing classes must be updated. The safest strategies:

1. Default interface methods (C# 8+) — non-breaking addition:

Add new methods with a default implementation. Existing implementors don't need to change.

public interface ILogger
{
    void Log(string message);

    // New method added without breaking existing implementations
    void LogWarning(string message) => Log($"[WARNING] {message}");
}

2. Interface versioning — create a new interface:

public interface ILogger    { void Log(string message); }
public interface ILogger2 : ILogger { void LogWarning(string message); }

// New implementations use ILogger2; old ones still work with ILogger

3. Use an abstract base class for extensible contracts:

If you anticipate change, an abstract class with virtual methods is easier to evolve without breaking consumers.

4. Avoid changing interface signatures if the interface is in a public NuGet package or shared library — use extension methods or wrapper interfaces instead.

↥ back to top

Q. Can we create instance of interface?

No. You cannot instantiate an interface directly with new. An interface is a contract — it has no concrete implementation or constructor.

// Compile error: cannot create an instance of an abstract type/interface
// ILogger logger = new ILogger();

What you can do: Create an instance of a class that implements the interface, and hold it in an interface-typed variable:

public interface ILogger { void Log(string msg); }
public class ConsoleLogger : ILogger
{
    public void Log(string msg) => Console.WriteLine(msg);
}

ILogger logger = new ConsoleLogger(); // … interface reference to concrete instance
logger.Log("Hello!"); // Output: Hello!

Exception: Anonymous types implementing interfaces via static methods (not common), or using mock frameworks in tests that generate proxy implementations at runtime.

↥ back to top

Q. Explain the differences between covariance and contravariance in C# for delegates and interfaces.

Covariance allows a more derived type to be used where a less derived type is expected (output positions — return types).
Contravariance allows a less derived type to be used where a more derived type is expected (input positions — parameter types).

Enabled with out (covariance) and in (contravariance) on generic type parameters.

Covariance (out) — return type can be more derived:

public class Animal { }
public class Dog : Animal { }

// IEnumerable<T> is covariant (out T)
IEnumerable<Dog> dogs = new List<Dog> { new Dog() };
IEnumerable<Animal> animals = dogs; // … Dog is more derived than Animal

// Func<T> is covariant in TResult
Func<Dog> getDog = () => new Dog();
Func<Animal> getAnimal = getDog; // … covariant

Contravariance (in) — parameter type can be less derived:

// Action<T> is contravariant (in T)
Action<Animal> processAnimal = a => Console.WriteLine("Processing animal");
Action<Dog> processDog = processAnimal; // … contravariant — can handle Dog via Animal handler

processDog(new Dog()); // Output: Processing animal

Custom covariant interface:

public interface IProducer<out T> { T Produce(); }

public class DogProducer : IProducer<Dog>
{
    public Dog Produce() => new Dog();
}

IProducer<Animal> producer = new DogProducer(); // … covariant

Summary:

Keyword Type parameter Allowed direction Example
out Return type More derived ’ base IEnumerable<Dog>IEnumerable<Animal>
in Parameter type Base ’ more derived Action<Animal>Action<Dog>
↥ back to top

Q. What is an abstraction?

Abstraction is the OOP principle of exposing only the relevant details of an object and hiding the internal complexity. It allows you to work with high-level concepts without knowing the underlying implementation.

In C#, abstraction is achieved through:

// Abstraction: caller uses IPayment without knowing Stripe or PayPal internals
public interface IPayment
{
    Task<bool> ChargeAsync(decimal amount, string token);
}

public class StripePayment : IPayment
{
    public async Task<bool> ChargeAsync(decimal amount, string token)
    {
        // Complex Stripe API logic hidden here
        Console.WriteLine($"Stripe: charging {amount:C} with token {token}");
        await Task.Delay(10); // simulate async call
        return true;
    }
}

// High-level code only knows IPayment — abstracted from Stripe details
public class CheckoutService(IPayment payment)
{
    public async Task Checkout(decimal total)
    {
        bool success = await payment.ChargeAsync(total, "tok_123");
        Console.WriteLine(success ? "Payment succeeded" : "Payment failed");
    }
}
↥ back to top

Q. Is it compulsory to implement Abstract methods?

Yes — any non-abstract class that inherits from an abstract class must implement all abstract methods. Failure to do so is a compile error.

public abstract class Shape
{
    public abstract double Area();    // must be implemented
    public abstract double Perimeter(); // must be implemented
    public virtual void Print() => Console.WriteLine($"Area={Area():F2}"); // optional
}

// Compile error — Area() and Perimeter() are not implemented:
// public class BadShape : Shape { }

// OK — all abstract members implemented:
public class Square(double side) : Shape
{
    public override double Area()      => side * side;
    public override double Perimeter() => 4 * side;
}

var sq = new Square(5);
sq.Print(); // Output: Area=25.00

Exception: If a derived class is itself abstract, it does not need to implement the abstract methods — the responsibility is passed to the next concrete class in the chain.

public abstract class AbstractDerived : Shape
{
    // Still abstract — no need to implement Area() or Perimeter() yet
    public abstract string Name();
}
↥ back to top

Q. What is the method MemberwiseClone() doing?

MemberwiseClone() is a protected method inherited from System.Object. It creates a shallow copy of the current object — all value-type fields are copied by value, but reference-type fields are copied by reference (both objects share the same referenced object).

public class Address
{
    public string City = "Mumbai";
}

public class Person
{
    public string Name = "Pradeep";
    public int Age = 30;
    public Address Location = new Address();

    public Person ShallowCopy() => (Person)MemberwiseClone();
}

var original = new Person();
var copy     = original.ShallowCopy();

// Value types are independent copies
copy.Name = "Ravi";
copy.Age  = 25;

// Reference types point to the SAME object (shallow copy!)
copy.Location.City = "Delhi";

Console.WriteLine(original.Name);          // Pradeep (independent copy)
Console.WriteLine(original.Location.City); // Delhi (shared reference!)

For a deep copy, implement ICloneable or manually copy each reference-type field.

↥ back to top

Q. Explain the difference between destructor, dispose and finalize method?

Concept Declaration Called by Use case
Destructor ~ClassName() { } GC (non-deterministic) Alias for Finalize() in C#
Finalize() protected override void Finalize() GC Last resort cleanup of unmanaged resources
Dispose() IDisposable.Dispose() Developer / using Deterministic cleanup of managed + unmanaged resources

Destructor / Finalize (non-deterministic — GC decides when):

public class ResourceHolder
{
    ~ResourceHolder() // Destructor — compiles to override Finalize()
    {
        Console.WriteLine("Finalizer called by GC");
        // Release unmanaged resources
    }
}

IDisposable / Dispose (deterministic — called explicitly or via using):

public class FileHandler : IDisposable
{
    private FileStream? _stream;
    private bool _disposed;

    public FileHandler(string path)
        => _stream = new FileStream(path, FileMode.OpenOrCreate);

    public void Dispose()
    {
        if (!_disposed)
        {
            _stream?.Dispose();
            _stream = null;
            _disposed = true;
            GC.SuppressFinalize(this); // prevent double-cleanup
        }
    }

    ~FileHandler() => Dispose(); // fallback if Dispose() not called
}

// Deterministic cleanup with using:
using var fh = new FileHandler("data.txt");
// Dispose() called automatically at end of scope

Best practice (.NET 10): Implement IDisposable for deterministic cleanup. Use IAsyncDisposable + await using for async resources. Always call GC.SuppressFinalize(this) in Dispose() if you have a finalizer.

↥ back to top

Q. Could you explain the difference between Func vs. Action vs. Predicate?

All three are built-in generic delegate types in System:

Type Signature Returns Use case
Action<T> void (T arg) void Perform an action, no return value
Func<T, R> R (T arg) R Transform/compute, returns a value
Predicate<T> bool (T arg) bool Test a condition (subset of Func)
// Action<T> — does something, returns nothing
Action<string> print = msg => Console.WriteLine(msg);
print("Hello Action"); // Output: Hello Action

// Func<T, TResult> — transforms and returns
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 4)); // Output: 7

Func<string, string> toUpper = s => s.ToUpperInvariant();
Console.WriteLine(toUpper("hello")); // Output: HELLO

// Predicate<T> — returns bool (used in List<T>.FindAll, RemoveAll, etc.)
Predicate<int> isEven = n => n % 2 == 0;
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evens = numbers.FindAll(isEven);
Console.WriteLine(string.Join(", ", evens)); // Output: 2, 4, 6

// Predicate<T> is equivalent to Func<T, bool>
Func<int, bool> isEvenFunc = n => n % 2 == 0;

Key takeaway: Predicate<T> is essentially Func<T, bool> — use Func in LINQ, and Predicate with older List<T> APIs.

↥ back to top

Q. What is a namespace and is it compulsory?

A namespace is a logical container that organises types (classes, interfaces, enums, etc.) and prevents naming conflicts between different libraries or code areas.

namespace MyApp.Services;  // file-scoped namespace (C# 10+)

public class OrderService { }
public class ProductService { }

Is it compulsory? No. Types declared without a namespace go into the global namespace and can be accessed without qualification. However, omitting namespaces is strongly discouraged in production code because:

Usage:

using MyApp.Services;

var svc = new OrderService(); // resolved via namespace

Global namespace access (when needed):

global::System.Console.WriteLine("Using global namespace");
↥ back to top

Q. What do you think about empty destructor?

An empty destructor is harmful and should be avoided. Even with no code, it has a negative performance impact:

Why it's harmful:

// BAD — empty destructor adds GC overhead with zero benefit
public class MyClass
{
    ~MyClass() { } //  Remove this
}

// GOOD — no destructor needed if there are no unmanaged resources
public class MyClass
{
    // No destructor — GC collects it efficiently in one cycle
}

Rule: Only add a destructor/finalizer if the class directly owns unmanaged resources (e.g., native handles, COM objects). And if you have a finalizer, always also implement IDisposable and call GC.SuppressFinalize(this) in Dispose().

↥ back to top

Q. What are the different types of “USING/HAS A” relationship?

“HAS-A” is a composition relationship — one class contains or uses an instance of another class. C# supports three levels of HAS-A:

1. Association — objects are related but have independent lifecycles.

// Teacher knows about a Student but doesn\'t own it
public class Teacher { public void Teach(Student s) => Console.WriteLine($"Teaching {s.Name}"); }
public class Student { public string Name { get; set; } = ""; }

2. Aggregation — a “whole-part” relationship where the part can exist independently.

// Department contains Employees, but Employees can exist without a Department
public class Employee { public string Name { get; set; } = ""; }
public class Department
{
    private readonly List<Employee> _employees = [];
    public void Add(Employee e) => _employees.Add(e);
}

3. Composition — a strong “whole-part” relationship; the part cannot exist without the whole.

// Engine only exists as part of a Car — created and destroyed with it
public class Car
{
    private readonly Engine _engine = new Engine(); // Car owns Engine\'s lifecycle

    public void Start() => _engine.Start();
}

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

Summary:

Relationship Lifecycle dependency Example
Association Independent Teacher ” Student
Aggregation Part survives whole Department ’ Employee
Composition Part dies with whole Car ’ Engine
↥ back to top

Q. Differentiate between Composition vs Aggregation vs Association?

See the USING/HAS A relationship question above for the full breakdown.

Quick differentiation:

// Association
public class Order { public void Process(Customer c) { } }

// Aggregation — customer exists outside the order
public class ShoppingCart
{
    public List<Customer> SharedCustomers { get; } = [];
}

// Composition — address is created and owned by customer
public class Customer
{
    private readonly Address _address; // owned, created here
    public Customer(string city) => _address = new Address(city);
}
↥ back to top

Q. What are circular references?

A circular reference occurs when two or more objects reference each other, forming a cycle (A ’ B ’ A, or A ’ B ’ C ’ A). In .NET, the GC handles circular references via tracing (mark-and-sweep), so they don't cause memory leaks in managed code by themselves.

However, circular references cause problems in:

public class Parent
{
    public Child? Child { get; set; }
    public string Name = "Parent";
}

public class Child
{
    public Parent? Parent { get; set; } // circular reference
    public string Name = "Child";
}

var parent = new Parent();
var child  = new Child();
parent.Child  = child;
child.Parent  = parent; // circle: parent ’ child ’ parent

// Serialization issue:
// JsonSerializer.Serialize(parent); // throws JsonException: cycle detected

// Fix with ReferenceHandler.Preserve or break the cycle:
var options = new System.Text.Json.JsonSerializerOptions
{
    ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve
};
string json = System.Text.Json.JsonSerializer.Serialize(parent, options);
Console.WriteLine(json);
↥ back to top

Q. What is weak reference in C#?

A weak reference (WeakReference<T>) allows you to hold a reference to an object without preventing the garbage collector from collecting it. If there are no other (strong) references to the object, the GC can reclaim its memory even though your weak reference still exists.

var data = new byte[1024 * 1024]; // 1 MB object
var weakRef = new WeakReference<byte[]>(data);

// data still has a strong reference, so it\'s alive
Console.WriteLine(weakRef.TryGetTarget(out _)); // True

// Remove the strong reference
data = null!;
GC.Collect(); // force GC for demo purposes

if (weakRef.TryGetTarget(out byte[]? recovered))
    Console.WriteLine("Still alive");
else
    Console.WriteLine("Collected by GC"); // likely after GC

Use cases:

↥ back to top

Q. Explain weak and strong references?

// Strong reference — object stays alive
var obj = new List<int> { 1, 2, 3 }; // strong

// Weak reference — object can be collected if no strong ref exists
var weak = new WeakReference<List<int>>(obj);

Console.WriteLine(weak.TryGetTarget(out _)); // True — obj is alive (strong ref exists)

obj = null!; // remove strong reference
GC.Collect();

Console.WriteLine(weak.TryGetTarget(out _)); // False — likely collected

Key point: As long as at least one strong reference exists, the GC will not collect the object. When all strong references are removed, the GC may collect it — and any WeakReference<T> pointing to it will return false from TryGetTarget.

↥ back to top

Q. How can you use Interfaces in C# to reduce coupling and improve maintainability?

Interfaces define contracts that decouple what a component does from how it does it. By depending on interfaces instead of concrete classes, your code becomes easier to test, extend, and maintain.

// Interface (contract)
public interface IEmailService
{
    Task SendAsync(string to, string subject, string body);
}

// Two concrete implementations — easily swappable
public class SmtpEmailService : IEmailService
{
    public async Task SendAsync(string to, string subject, string body)
    {
        Console.WriteLine($"SMTP ’ {to}: {subject}");
        await Task.CompletedTask;
    }
}

public class SendGridEmailService : IEmailService
{
    public async Task SendAsync(string to, string subject, string body)
    {
        Console.WriteLine($"SendGrid ’ {to}: {subject}");
        await Task.CompletedTask;
    }
}

// Consumer depends on IEmailService, NOT on Smtp or SendGrid
public class UserRegistrationService(IEmailService emailService)
{
    public async Task RegisterAsync(string email)
    {
        Console.WriteLine($"User {email} registered.");
        await emailService.SendAsync(email, "Welcome!", "Thanks for joining.");
    }
}

// Easy to swap: change the DI registration, not the service code
var service = new UserRegistrationService(new SmtpEmailService());
await service.RegisterAsync("user@example.com");

Benefits:

↥ back to top

Q. What happens if the inherited interfaces have conflicting method names?

When a class implements multiple interfaces with the same method signature, you can use explicit interface implementation to provide a separate implementation for each.

public interface ILogger  { void Log(string msg); }
public interface IAuditor { void Log(string msg); } // same method name

public class ActivityTracker : ILogger, IAuditor
{
    // Explicit: only callable via ILogger reference
    void ILogger.Log(string msg)  => Console.WriteLine($"[LOG]   {msg}");

    // Explicit: only callable via IAuditor reference
    void IAuditor.Log(string msg) => Console.WriteLine($"[AUDIT] {msg}");

    // Optional: a public method on the class itself
    public void TrackActivity(string activity)
    {
        ((ILogger)this).Log(activity);
        ((IAuditor)this).Log(activity);
    }
}

var tracker = new ActivityTracker();
tracker.TrackActivity("User login");
// Output:
// [LOG]   User login
// [AUDIT] User login

// Disambiguate via interface reference
ILogger  logger  = tracker;
IAuditor auditor = tracker;

logger.Log("Via ILogger");   // Output: [LOG]   Via ILogger
auditor.Log("Via IAuditor"); // Output: [AUDIT] Via IAuditor
↥ back to top

Q. What is the purpose of the volatile keyword in C#?

The volatile keyword indicates that a field may be modified by multiple threads concurrently. It instructs the compiler and runtime to always read from and write to the field's actual memory location, preventing CPU caching or instruction reordering optimisations that could cause stale reads.

public class SharedState
{
    private volatile bool _running = true; // ensures all threads see the latest value

    public void Stop() => _running = false;

    public void WorkLoop()
    {
        while (_running) // each iteration re-reads from memory
        {
            // do work
        }
        Console.WriteLine("Loop stopped.");
    }
}

var state = new SharedState();
var worker = Task.Run(state.WorkLoop);

await Task.Delay(100);
state.Stop(); // another thread sets _running = false
await worker;

When to use: Simple flag variables read/written by multiple threads where full lock or Interlocked is overkill. For compound operations or incrementing, use Interlocked instead.

↥ back to top

Q. What is the purpose of the checked keyword in C#?

The checked keyword enables overflow checking for arithmetic operations on integer types. Without checked, integer overflow wraps around silently; with checked, it throws an OverflowException.

int max = int.MaxValue; // 2,147,483,647

// Unchecked (default) — wraps around silently
int overflowed = max + 1;
Console.WriteLine(overflowed); // Output: -2147483648 (wrong!)

// Checked — throws OverflowException
try
{
    int result = checked(max + 1); //  throws OverflowException
}
catch (OverflowException ex)
{
    Console.WriteLine($"Overflow caught: {ex.Message}");
}

// Checked block — applies to all arithmetic in the block
checked
{
    int a = int.MaxValue;
    int b = a + 1; //  throws OverflowException
}

unchecked keyword explicitly suppresses overflow checking (useful when you want wraparound by design, e.g., hash code calculations):

int hash = unchecked(int.MaxValue + 1); // -2147483648 — intentional wrap
↥ back to top

Q. What is the purpose of the this keyword in C#?

The this keyword refers to the current instance of a class. It is used to:

  1. Disambiguate between fields and parameters with the same name.
  2. Chain constructors within the same class.
  3. Pass the current instance as an argument to a method.
  4. Invoke extension methods explicitly.
  5. Return the current instance (fluent builder pattern).
public class Builder
{
    private string _name = "";
    private int _age;

    // 1. Disambiguate field vs. parameter
    public Builder SetName(string name) { this._name = name; return this; }

    // 5. Return current instance (fluent API)
    public Builder SetAge(int age)  { _age = age; return this; }

    public override string ToString() => $"{_name}, {_age}";

    // 2. Constructor chaining
    public Builder() : this("Unknown", 0) { }
    public Builder(string name, int age) { _name = name; _age = age; }
}

var result = new Builder()
    .SetName("Pradeep")
    .SetAge(30)
    .ToString();

Console.WriteLine(result); // Output: Pradeep, 30

In primary constructors (C# 12+): this still refers to the current instance and can be used in method bodies.

↥ back to top

Q. What is the purpose of the base keyword in C#?

The base keyword refers to the base class of the current class. It is used to:

  1. Call a base class constructor from a derived class constructor.
  2. Call a base class method that has been overridden.
public class Vehicle
{
    protected string Model { get; }

    public Vehicle(string model)
    {
        Model = model;
        Console.WriteLine($"Vehicle created: {model}");
    }

    public virtual void Describe()
        => Console.WriteLine($"Vehicle: {Model}");
}

public class Car : Vehicle
{
    public int Doors { get; }

    // 1. Call base constructor
    public Car(string model, int doors) : base(model)
    {
        Doors = doors;
        Console.WriteLine($"Car created: {model}, {doors} doors");
    }

    // 2. Call overridden base method
    public override void Describe()
    {
        base.Describe(); // calls Vehicle.Describe()
        Console.WriteLine($"Doors: {Doors}");
    }
}

var car = new Car("Tesla Model 3", 4);
car.Describe();
// Output:
// Vehicle created: Tesla Model 3
// Car created: Tesla Model 3, 4 doors
// Vehicle: Tesla Model 3
// Doors: 4
↥ back to top

Q. What is the purpose of the params keyword in C#?

The params keyword allows a method to accept a variable number of arguments of the same type. The caller can pass a comma-separated list, an array, or nothing at all.

C# 13 enhancement: params now works with any collection type (IEnumerable<T>, Span<T>, ReadOnlySpan<T>, List<T>, etc.) — not just arrays.

Traditional params (all versions):

public int Sum(params int[] numbers)
    => numbers.Sum();

Console.WriteLine(Sum(1, 2, 3));           // Output: 6
Console.WriteLine(Sum(10, 20, 30, 40));    // Output: 100
Console.WriteLine(Sum());                  // Output: 0
Console.WriteLine(Sum(new[] { 5, 5, 5 })); // Output: 15

params ReadOnlySpan<T> (C# 13 / .NET 9+) — zero allocation:

public static double Average(params ReadOnlySpan<double> values)
{
    if (values.IsEmpty) return 0;
    double sum = 0;
    foreach (var v in values) sum += v;
    return sum / values.Length;
}

Console.WriteLine(Average(10.0, 20.0, 30.0)); // Output: 20

Rules:

↥ back to top

Q. What is the purpose of the yield keyword in C#?

The yield keyword enables lazy iterator methods that return elements one at a time on demand. See the detailed answer in the yield with iterator example section below.

Quick example:

public IEnumerable<int> Squares(int n)
{
    for (int i = 1; i <= n; i++)
        yield return i * i;
}

foreach (var s in Squares(5))
    Console.Write(s + " "); // Output: 1 4 9 16 25
↥ back to top

Q. What is the async and await keyword in C#?

async and await are keywords that enable asynchronous programming in C# without blocking the calling thread. They are built on top of Task and Task<T> (and ValueTask/ValueTask<T> for low-allocation scenarios).

sequenceDiagram
    participant Caller
    participant AsyncMethod
    participant ThreadPool
    participant IO as "I/O / Network"

    Caller->>AsyncMethod: await FetchDataAsync()
    AsyncMethod->>IO: Start async I/O operation
    AsyncMethod-->>Caller: Return control (thread freed)
    Note over Caller,ThreadPool: Thread is free to do other work
    IO-->>ThreadPool: I/O completes
    ThreadPool->>AsyncMethod: Resume after await
    AsyncMethod-->>Caller: Return result

Basic example:

public async Task<string> FetchDataAsync(string url)
{
    using var client = new HttpClient();
    string content = await client.GetStringAsync(url);
    return content;
}

// Caller
string data = await FetchDataAsync("https://api.example.com/data");
Console.WriteLine(data);

IAsyncEnumerable<T> — async streaming (C# 8+):

Stream data asynchronously without loading everything into memory.

public async IAsyncEnumerable<int> GenerateAsync()
{
    for (int i = 1; i <= 5; i++)
    {
        await Task.Delay(100); // simulate async work
        yield return i;
    }
}

await foreach (var value in GenerateAsync())
    Console.Write(value + " "); // Output: 1 2 3 4 5

Task.WhenAll and Task.WhenAny:

// Run multiple async tasks in parallel
var t1 = FetchDataAsync("https://api1.example.com");
var t2 = FetchDataAsync("https://api2.example.com");

string[] results = await Task.WhenAll(t1, t2);

ValueTask<T> for performance (.NET 5+):

Use when a method often completes synchronously (avoids Task heap allocation).

public async ValueTask<int> GetCachedValueAsync(string key)
{
    if (_cache.TryGetValue(key, out int val))
        return val; // synchronous fast-path, no heap alloc
    return await LoadFromDbAsync(key);
}

Cancellation support:

public async Task ProcessAsync(CancellationToken ct = default)
{
    for (int i = 0; i < 100; i++)
    {
        ct.ThrowIfCancellationRequested();
        await DoWorkAsync(i, ct);
    }
}
↥ back to top

Q. What is the difference between override and new keywords?

Both override and new affect how a derived class method relates to a base class method, but they behave very differently at runtime.

Aspect override new
Requires virtual Yes — base method must be virtual/abstract No — works on any base method
Runtime dispatch Polymorphic (dynamic) — actual type decides Static — reference type decides
Intent Replace the base implementation Hide the base method (method hiding)
Best practice Use for intentional polymorphism Rarely needed; often a design smell

override — polymorphic dispatch (runtime type wins):

public class Animal
{
    public virtual string Speak() => "...";
}

public class Dog : Animal
{
    public override string Speak() => "Woof"; // replaces Animal.Speak
}

Animal a = new Dog();
Console.WriteLine(a.Speak()); // Output: Woof (Dog\'s version — runtime type wins)

new — method hiding (reference type wins):

public class Cat : Animal
{
    public new string Speak() => "Meow"; // hides Animal.Speak — NOT polymorphic
}

Animal a = new Cat();
Console.WriteLine(a.Speak()); // Output: ... (Animal\'s version — reference type wins!)

Cat c = new Cat();
Console.WriteLine(c.Speak()); // Output: Meow (Cat reference — Cat\'s version)

Key takeaway: Use override for true polymorphism. Use new only when you intentionally want to hide a base member without polymorphic dispatch — and document why.

↥ back to top

Q. Why do we need the out keyword?

The out keyword allows a method to return multiple values by passing parameters by reference. Unlike ref, the variable passed as out does not need to be initialised before the call — the method must assign a value to it before returning.

Primary use cases:

  1. Return multiple values from a single method (before tuples were preferred).
  2. The TryXxx pattern — return a bool and an output value simultaneously.
// Basic out parameter
public bool TryDivide(int a, int b, out int result)
{
    if (b == 0) { result = 0; return false; }
    result = a / b;
    return true;
}

// Caller — variable declared inline (C# 7+)
if (TryDivide(10, 2, out int quotient))
    Console.WriteLine(quotient); // Output: 5

if (!TryDivide(10, 0, out _)) // discard with _
    Console.WriteLine("Cannot divide by zero");

Built-in TryParse pattern (.NET):

string input = "42";
if (int.TryParse(input, out int value))
    Console.WriteLine($"Parsed: {value}"); // Output: Parsed: 42

// Discard when you only need the bool
bool isValid = int.TryParse("abc", out _); // false

Multiple out parameters:

public void GetMinMax(int[] arr, out int min, out int max)
{
    min = arr.Min();
    max = arr.Max();
}

GetMinMax(new[] { 3, 1, 4, 1, 5, 9 }, out int mn, out int mx);
Console.WriteLine($"Min={mn}, Max={mx}"); // Min=1, Max=9

Note: For new code, prefer tuples ((int min, int max) GetMinMax(...)) over multiple out parameters — they are more readable and don't require pre-declaration.

↥ back to top

Q. What is the params keyword in C#?

The params keyword lets a method accept a variable number of arguments without the caller needing to create an array explicitly. It must be the last parameter in the signature.

C# 13 / .NET 9+ enhancement: params now supports any collection type — ReadOnlySpan<T>, IEnumerable<T>, List<T>, etc. — not just arrays.

// Traditional params array (all .NET versions)
public static int Sum(params int[] numbers) => numbers.Sum();

Console.WriteLine(Sum(1, 2, 3));        // Output: 6
Console.WriteLine(Sum(10, 20));         // Output: 30
Console.WriteLine(Sum());               // Output: 0
Console.WriteLine(Sum([5, 5, 5]));      // Output: 15 (pass array directly)

// params ReadOnlySpan<T> (C# 13 / .NET 9+) — zero heap allocation
public static double Average(params ReadOnlySpan<double> values)
{
    if (values.IsEmpty) return 0;
    double total = 0;
    foreach (var v in values) total += v;
    return total / values.Length;
}

Console.WriteLine(Average(10.0, 20.0, 30.0)); // Output: 20

Rules:

↥ back to top

Q. What is the purpose of the yield keyword in C#? Provide an example of using it with an iterator?

The yield keyword is used inside an iterator method to return elements one at a time, enabling lazy evaluation. The method's state is preserved between calls, so execution resumes where it left off.

Key benefits:

Example — synchronous iterator:

public IEnumerable<int> EvenNumbers(int max)
{
    for (int i = 0; i <= max; i += 2)
    {
        Console.WriteLine($"Yielding {i}");
        yield return i;
    }
}

foreach (var n in EvenNumbers(10))
    Console.Write(n + " "); // Output: 0 2 4 6 8 10

Example — infinite sequence with yield:

public IEnumerable<int> Fibonacci()
{
    int a = 0, b = 1;
    while (true)
    {
        yield return a;
        (a, b) = (b, a + b);
    }
}

var first10 = Fibonacci().Take(10).ToList();
Console.WriteLine(string.Join(", ", first10));
// Output: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

Example — async iterator (C# 8+, .NET Core 3+):

public async IAsyncEnumerable<string> ReadLinesAsync(string path)
{
    await foreach (var line in File.ReadLinesAsync(path))
        yield return line.ToUpperInvariant();
}

await foreach (var line in ReadLinesAsync("data.txt"))
    Console.WriteLine(line);
↥ back to top

Q. What is the “yield” keyword used for in C#?

The yield keyword is used in an iterator method to lazily produce a sequence of values one at a time. See the full answer with examples in the yield with iterator section above.

Key points:

// Lazy sequence — elements generated only when consumed
public IEnumerable<int> Countdown(int from)
{
    for (int i = from; i >= 0; i--)
        yield return i;
    yield return -1; // sentinel — indicates sequence ended
}

foreach (var n in Countdown(3))
    Console.Write(n + " "); // Output: 3 2 1 0 -1

// Short-circuit: yield break stops iteration
public IEnumerable<int> TakeWhilePositive(IEnumerable<int> source)
{
    foreach (var item in source)
    {
        if (item <= 0) yield break;
        yield return item;
    }
}

var result = TakeWhilePositive([5, 3, 1, -2, 4]).ToList();
Console.WriteLine(string.Join(", ", result)); // Output: 5, 3, 1
↥ back to top

Q. What is the importance of “this” keyword?

The this keyword refers to the current instance of a class or struct.

Summary of uses:

Use Description
Disambiguate Distinguish between a field and a parameter with the same name
Constructor chaining this(...) calls another constructor in the same class
Pass current instance Pass this as an argument to other methods
Fluent API Return this from methods to enable method chaining
Extension methods The extended type's instance is accessible as this in the extension
public class Counter
{
    private int _count;

    // 1. Disambiguate field vs. parameter
    public Counter(int count) => this._count = count;

    // 4. Fluent API — return this
    public Counter Increment() { _count++; return this; }
    public Counter Add(int n)  { _count += n; return this; }

    public int Value => _count;

    // 2. Constructor chaining
    public Counter() : this(0) { } // delegates to Counter(int)
}

var result = new Counter()
    .Increment()
    .Increment()
    .Add(5)
    .Value;

Console.WriteLine(result); // Output: 7

this in primary constructors (C# 12+):

public class Order(int id, string customer)
{
    public int Id { get; } = id;
    public string Customer { get; } = customer;

    // this refers to the current instance in methods
    public Order WithCustomer(string newCustomer) => new Order(this.Id, newCustomer);
}

var o1 = new Order(1, "Alice");
var o2 = o1.WithCustomer("Bob");
Console.WriteLine(o2.Customer); // Output: Bob
↥ back to top

Q. What is the difference between ref and out keywords?

Both ref and out pass arguments by reference (the method receives a pointer to the original variable, not a copy), but they have different rules around initialization.

Aspect ref out
Must be initialized before call Yes — caller must assign first No — can be uninitialized
Method must assign before return No — method may or may not modify it Yes — method must assign before returning
Primary use Two-way data exchange Return multiple values from a method
Inline declaration (C# 7+) No Yes — out int result
Discard allowed No Yes — out _

ref — read AND write by caller and method:

public void Double(ref int value)
{
    value *= 2; // modifies the caller\'s variable
}

int x = 5;
Double(ref x);
Console.WriteLine(x); // Output: 10

out — write-only output; method must assign:

public bool TryParse(string s, out int result)
{
    if (int.TryParse(s, out result))
        return true;
    result = 0; // must assign before return
    return false;
}

// Inline declaration (C# 7+)
if (TryParse("42", out int value))
    Console.WriteLine(value); // Output: 42

// Discard when result not needed
if (TryParse("abc", out _))
    Console.WriteLine("valid");
else
    Console.WriteLine("invalid"); // Output: invalid

Side-by-side comparison:

// ref — x must be assigned before passing
int x = 10;
Multiply(ref x, 3);
Console.WriteLine(x); // Output: 30

static void Multiply(ref int n, int factor) => n *= factor;

// out — y does NOT need to be assigned before passing
GetSquare(5, out int y);
Console.WriteLine(y); // Output: 25

static void GetSquare(int n, out int result) => result = n * n;

Modern alternative — prefer tuples for multiple return values:

// Instead of multiple out parameters:
public (int Min, int Max) GetMinMax(int[] arr) =>
    (arr.Min(), arr.Max());

var (min, max) = GetMinMax([3, 1, 4, 1, 5, 9]);
Console.WriteLine($"Min={min}, Max={max}"); // Min=1, Max=9
↥ back to top

Q. What is a partial class in C#?

A partial class splits the definition of a class, struct, interface, or record across multiple files. All parts are combined into a single type at compile time.

Use cases:

Example:

// File: Order.cs
public partial class Order
{
    public int Id { get; init; }
    public string CustomerName { get; init; } = string.Empty;
}

// File: Order.Validation.cs
public partial class Order
{
    public bool IsValid() =>
        Id > 0 && !string.IsNullOrWhiteSpace(CustomerName);
}

// File: Order.Display.cs
public partial class Order
{
    public override string ToString() => $"Order #{Id} for {CustomerName}";
}

// Usage
var order = new Order { Id = 1, CustomerName = "Pradeep" };
Console.WriteLine(order);           // Output: Order #1 for Pradeep
Console.WriteLine(order.IsValid()); // Output: True

Partial methods (C# 9+):

Allows one part to declare a method signature and another to optionally implement it. If not implemented, calls are removed by the compiler.

public partial class DataProcessor
{
    public partial void OnDataLoaded(string data); // declaration
}

public partial class DataProcessor
{
    public partial void OnDataLoaded(string data) // implementation
        => Console.WriteLine($"Loaded: {data}");
}
↥ back to top

Q. What is a static class in C#?

A static class is a class that cannot be instantiated or inherited. All its members must also be static. It is sealed by default.

Use cases:

Example:

public static class MathHelper
{
    public const double GoldenRatio = 1.6180339887;

    public static double CircleArea(double radius) => Math.PI * radius * radius;

    public static int Clamp(int value, int min, int max)
        => Math.Max(min, Math.Min(max, value));
}

Console.WriteLine(MathHelper.CircleArea(5));   // Output: 78.53...
Console.WriteLine(MathHelper.Clamp(15, 0, 10)); // Output: 10

Extension method host (must be static class):

public static class StringExtensions
{
    public static bool IsEmail(this string s) =>
        s.Contains('@') && s.Contains('.');
}

Console.WriteLine("user@example.com".IsEmail()); // True

Key rules:

Rule Detail
Cannot be instantiated new MathHelper() is a compile error
Cannot be inherited Implicitly sealed
All members must be static Including nested types
Can have static constructor Runs once before first access
↥ back to top

Q. What are the different types of classes in C#?

C# supports several class types, each serving a distinct purpose:

Class type Description
Concrete class The default — can be instantiated directly
Abstract class Cannot be instantiated; may contain abstract members that derived classes must implement
Sealed class Cannot be inherited (e.g., string, StringBuilder)
Static class Cannot be instantiated or inherited; all members are static
Partial class Definition split across multiple files; combined at compile time
Generic class Parameterised by one or more type arguments (e.g., List<T>)
Record class Immutable reference type with value-based equality (C# 9+)
Nested class Declared inside another class

Examples:

// Concrete
public class Car { public string Model { get; set; } = ""; }

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

// Sealed
public sealed class Singleton
{
    public static Singleton Instance { get; } = new();
    private Singleton() { }
}

// Static
public static class MathUtils
{
    public static double Square(double x) => x * x;
}

// Generic
public class Repository<T> where T : class
{
    private readonly List<T> _store = [];
    public void Add(T item) => _store.Add(item);
    public IEnumerable<T> GetAll() => _store;
}

// Record (C# 9+) — immutable, value equality
public record class Product(string Name, decimal Price);

var p1 = new Product("Laptop", 999m);
var p2 = new Product("Laptop", 999m);
Console.WriteLine(p1 == p2); // True (value equality)

// Partial (split across files)
public partial class Order { public int Id { get; init; } }
public partial class Order { public bool IsValid() => Id > 0; }
↥ back to top

Q. Are private class members inherited to the derived class?

Yes — private members are inherited (they exist in memory), but they are not accessible in derived classes.

The derived class contains the private members of the base class as part of its object layout, but the compiler prevents direct access to them from derived class code.

public class Base
{
    private int _secret = 42;      // private field
    private void PrivateHelper()   // private method
        => Console.WriteLine("Base private method");

    public int GetSecret() => _secret;    // public accessor
    protected void CallHelper() => PrivateHelper(); // can call internally
}

public class Derived : Base
{
    public void Show()
    {
        // Console.WriteLine(_secret);  //  compile error — not accessible
        // PrivateHelper();             //  compile error — not accessible

        Console.WriteLine(GetSecret()); // … access via public method
        CallHelper();                   // … access via protected method
    }
}

var d = new Derived();
d.Show();
// Output:
// 42
// Base private method

Proof that private members exist in derived objects:

// Reflection can reveal them
var fields = typeof(Derived)
    .GetFields(System.Reflection.BindingFlags.NonPublic |
               System.Reflection.BindingFlags.Instance);

foreach (var f in fields)
    Console.WriteLine(f.Name); // Output: _secret (inherited but private)

Summary:

Member access Inherited (exists in memory)? Accessible in derived class?
public
protected
internal … (same assembly)
private  
↥ back to top

Q. What are partial classes?

A partial class splits a single class definition across multiple source files. The partial keyword is required on all parts. The compiler merges them into one class at compile time.

Use cases:

// File: Customer.cs  — hand-written
public partial class Customer
{
    public int Id { get; init; }
    public string Name { get; init; } = string.Empty;
    public string Email { get; init; } = string.Empty;
}

// File: Customer.Validation.cs — hand-written
public partial class Customer
{
    public bool IsValid() =>
        Id > 0 &&
        !string.IsNullOrWhiteSpace(Name) &&
        Email.Contains('@');
}

// File: Customer.g.cs — could be machine-generated
public partial class Customer
{
    public override string ToString() => $"[{Id}] {Name} <{Email}>";
}

// Usage — all parts are merged into one Customer type
var c = new Customer { Id = 1, Name = "Pradeep", Email = "p@example.com" };
Console.WriteLine(c);           // Output: [1] Pradeep <p@example.com>
Console.WriteLine(c.IsValid()); // Output: True

Partial methods (C# 9+ — must have access modifiers):

public partial class DataPipeline
{
    // Declared in one part — implementation is optional in older C#
    // In C# 9+, partial methods with access modifiers MUST be implemented
    public partial void OnProcessed(string data);
}

public partial class DataPipeline
{
    public partial void OnProcessed(string data)
        => Console.WriteLine($"Processed: {data}");
}

var pipeline = new DataPipeline();
pipeline.OnProcessed("record-1"); // Output: Processed: record-1

Rules:

↥ back to top

Q. What is the difference between a struct and a class in C#?

Both struct and class define types with members, but they have fundamental behavioral differences.

Feature class struct
Type kind Reference type (heap) Value type (stack / inline in object)
Null Can be null Cannot be null (unless Nullable<T>)
Assignment Copies the reference Copies the entire value
Inheritance Supports full inheritance Cannot inherit (only interfaces)
Default constructor Compiler generates one Auto-provided; custom allowed (C# 10+)
record syntax record class (C# 9+) record struct (C# 10+)
Performance Heap allocation + GC pressure Stack-allocated, GC-friendly

Class example (reference semantics):

public class PointClass { public int X; public int Y; }

var a = new PointClass { X = 1, Y = 2 };
var b = a;       // b references the same object
b.X = 99;
Console.WriteLine(a.X); // Output: 99 (same object)

Struct example (value semantics):

public struct PointStruct { public int X; public int Y; }

var a = new PointStruct { X = 1, Y = 2 };
var b = a;       // b is a copy
b.X = 99;
Console.WriteLine(a.X); // Output: 1 (original unchanged)

record struct (C# 10+) — immutable value type with equality:

public readonly record struct Vector2D(double X, double Y)
{
    public double Length => Math.Sqrt(X * X + Y * Y);
}

var v = new Vector2D(3, 4);
Console.WriteLine(v.Length); // Output: 5

Guidelines:

↥ back to top

Q. What is the difference between public, private, protected, and internal access modifiers?

Access modifiers control the visibility and accessibility of types and members in C#.

Modifier Accessible from
public Anywhere (same assembly + other assemblies)
private Only within the same class or struct
protected Same class + derived classes (any assembly)
internal Anywhere within the same assembly
protected internal Same assembly or derived classes in any assembly
private protected Same class + derived classes within the same assembly (C# 7.2+)
file (C# 11+) Only within the same source file

Example:

public class BankAccount
{
    private decimal _balance;          // only this class
    protected string OwnerId { get; }  // this class + subclasses
    internal string BranchCode { get; } // same assembly

    public BankAccount(string ownerId, string branchCode)
    {
        OwnerId = ownerId;
        BranchCode = branchCode;
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0) throw new ArgumentException("Must be positive");
        _balance += amount;
    }

    public decimal GetBalance() => _balance;
}

public class PremiumAccount : BankAccount
{
    public PremiumAccount(string ownerId) : base(ownerId, "PREM")
    {
        Console.WriteLine(OwnerId);    // OK: protected
        Console.WriteLine(BranchCode); // OK: internal (same assembly)
        // Console.WriteLine(_balance); // Error: private
    }
}

file modifier (C# 11+):

// Only usable within this .cs file — useful for source generators
file class InternalHelper
{
    public static void DoWork() => Console.WriteLine("Working...");
}
↥ back to top

Q. C# provides a default constructor for me. I write a constructor that takes a string as a parameter, but want to keep the no parameter one. How many constructors should I write?

You need to write 2 constructors explicitly.

When you add any constructor with parameters, the compiler stops generating the default (no-parameter) constructor automatically. To keep both, you must declare both explicitly:

public class Person
{
    public string Name { get; }
    public int Age { get; }

    // 1. Explicit no-parameter constructor (compiler no longer auto-generates this)
    public Person()
    {
        Name = "Unknown";
        Age  = 0;
    }

    // 2. Parameterized constructor
    public Person(string name)
    {
        Name = name;
        Age  = 0;
    }
}

var p1 = new Person();          // uses no-param constructor
var p2 = new Person("Pradeep"); // uses string constructor

Console.WriteLine(p1.Name); // Output: Unknown
Console.WriteLine(p2.Name); // Output: Pradeep

Tip: Use constructor chaining (this()) to avoid duplication:

public class Person
{
    public string Name { get; }
    public int Age { get; }

    public Person() : this("Unknown") { }            // chains to string ctor
    public Person(string name) : this(name, 0) { }   // chains to full ctor
    public Person(string name, int age) { Name = name; Age = age; }
}
↥ back to top

Q. Why do we need constructors?

A constructor is a special method that initialises an object's state when it is created. Without constructors, fields would be left at default values and the object might be in an invalid or inconsistent state.

Reasons we need constructors:

Purpose Description
Initialisation Set fields/properties to meaningful initial values
Validation Enforce invariants — reject invalid state at creation time
Dependency injection Receive required dependencies when the object is created
Encapsulation Control how an object is constructed
public class BankAccount
{
    public string Owner { get; }
    public decimal Balance { get; private set; }

    // Constructor enforces invariants
    public BankAccount(string owner, decimal initialDeposit)
    {
        if (string.IsNullOrWhiteSpace(owner))
            throw new ArgumentException("Owner name required");
        if (initialDeposit < 0)
            throw new ArgumentOutOfRangeException(nameof(initialDeposit), "Must be >= 0");

        Owner   = owner;
        Balance = initialDeposit;
    }

    public void Deposit(decimal amount) => Balance += amount;
}

var acc = new BankAccount("Pradeep", 1000m);
Console.WriteLine($"{acc.Owner}: {acc.Balance:C}"); // Pradeep: 1,000.00

// var bad = new BankAccount("", -100); //  throws at construction

Primary constructors (C# 12 / .NET 8+):

// Concise — parameters available throughout the class
public class Product(string name, decimal price)
{
    public string Name  { get; } = name;
    public decimal Price { get; } = price > 0 ? price
        : throw new ArgumentException("Price must be positive");
}

var p = new Product("Laptop", 999m);
Console.WriteLine($"{p.Name}: {p.Price:C}"); // Laptop: 999.00
↥ back to top

Q. In parent child which constructor fires first?

The base (parent) class constructor always fires first, before the derived (child) class constructor. This guarantees that the base part of the object is fully initialised before derived initialisation runs.

sequenceDiagram
    participant Client
    participant Child
    participant Parent
    participant GrandParent

    Client->>Child: new Child()
    Child->>Parent: implicit base()
    Parent->>GrandParent: implicit base()
    GrandParent-->>Parent: "1. GrandParent constructor"
    Parent-->>Child: "2. Parent constructor"
    Child-->>Client: "3. Child constructor"
public class GrandParent
{
    public GrandParent() => Console.WriteLine("1. GrandParent constructor");
}

public class Parent : GrandParent
{
    public Parent() => Console.WriteLine("2. Parent constructor");
}

public class Child : Parent
{
    public Child() => Console.WriteLine("3. Child constructor");
}

var c = new Child();
// Output:
// 1. GrandParent constructor
// 2. Parent constructor
// 3. Child constructor

With parameterized constructors and base():

public class Animal
{
    public string Name { get; }
    public Animal(string name)
    {
        Name = name;
        Console.WriteLine($"Animal created: {name}");
    }
}

public class Dog : Animal
{
    public string Breed { get; }
    public Dog(string name, string breed) : base(name) // base runs first
    {
        Breed = breed;
        Console.WriteLine($"Dog created: {breed}");
    }
}

var d = new Dog("Rex", "Labrador");
// Output:
// Animal created: Rex       base runs first
// Dog created: Labrador     derived runs second

Rule: The base() call (explicit or implicit) always executes before the body of the derived constructor.

↥ back to top

Q. What is the Constructor Chaining in C#?

Constructor chaining is calling one constructor from another in the same class using this(...) or from a derived class using base(...). It avoids code duplication and centralises initialisation logic.

this(...) — chain within the same class:

public class Order
{
    public int Id { get; }
    public string Customer { get; }
    public decimal Discount { get; }

    // Most specific constructor — all initialisation here
    public Order(int id, string customer, decimal discount)
    {
        Id       = id;
        Customer = customer;
        Discount = discount;
        Console.WriteLine($"Order {id} for {customer}, discount {discount:P}");
    }

    // Chains to the full constructor with defaults
    public Order(int id, string customer) : this(id, customer, 0m) { }

    // Chains further
    public Order(int id) : this(id, "Guest") { }
}

new Order(1);                  // Order 1 for Guest, discount 0%
new Order(2, "Pradeep");       // Order 2 for Pradeep, discount 0%
new Order(3, "Alice", 0.15m);  // Order 3 for Alice, discount 15%

base(...) — chain to parent constructor:

public class Vehicle
{
    public string Brand { get; }
    public Vehicle(string brand)
    {
        Brand = brand;
        Console.WriteLine($"Vehicle: {brand}");
    }
}

public class Car : Vehicle
{
    public int Doors { get; }
    public Car(string brand, int doors) : base(brand) // calls Vehicle(string)
    {
        Doors = doors;
        Console.WriteLine($"Car: {doors} doors");
    }
    public Car(string brand) : this(brand, 4) { } // chains to Car(string, int)
}

new Car("Tesla");         // Vehicle: Tesla ’ Car: 4 doors
new Car("BMW", 2);        // Vehicle: BMW   ’ Car: 2 doors
↥ back to top

Q. What are the different ways a method can be overloaded?

Method overloading means defining multiple methods in the same class with the same name but different parameter lists. The compiler picks the correct overload at compile time based on argument types.

Ways to overload:

Variation Example
Different number of parameters Add(int a) vs Add(int a, int b)
Different parameter types Print(int n) vs Print(string s)
Different parameter order Log(string msg, int level) vs Log(int level, string msg)
params vs explicit Sum(int a, int b) vs Sum(params int[] nums)

** NOT valid for overloading:**

public class Converter
{
    // Different number of parameters
    public string Format(int n) => $"{n}";
    public string Format(int n, string prefix) => $"{prefix}{n}";

    // Different parameter types
    public double Round(double value) => Math.Round(value);
    public decimal Round(decimal value) => Math.Round(value);

    // Different parameter order
    public string Build(string name, int age) => $"{name}, {age}";
    public string Build(int age, string name) => $"{age}: {name}";

    // params overload
    public int Sum(int a, int b) => a + b;
    public int Sum(params int[] nums) => nums.Sum();
}

var c = new Converter();
Console.WriteLine(c.Format(42));          // 42
Console.WriteLine(c.Format(42, "ID-")); // ID-42
Console.WriteLine(c.Round(3.567));        // 4
Console.WriteLine(c.Sum(1, 2));           // 3
Console.WriteLine(c.Sum(1, 2, 3, 4, 5)); // 15
↥ back to top

Q. When and why to use method overloading?

Use method overloading when you want to provide multiple convenient ways to call the same logical operation with different inputs — without forcing callers to remember different method names.

When to use:

Why it improves code:

// Logging API — callers don\'t need to know about formatting details
public class Logger
{
    public void Log(string message)
        => Console.WriteLine($"[INFO]  {message}");

    public void Log(string message, LogLevel level)
        => Console.WriteLine($"[{level}] {message}");

    public void Log(Exception ex)
        => Console.WriteLine($"[ERROR] {ex.GetType().Name}: {ex.Message}");

    public void Log(string message, Exception ex)
        => Console.WriteLine($"[ERROR] {message}: {ex.Message}");
}

public enum LogLevel { Info, Warning, Error }

var log = new Logger();
log.Log("Application started");
log.Log("High memory usage", LogLevel.Warning);
log.Log(new InvalidOperationException("State error"));
log.Log("Unhandled exception", new Exception("Boom"));

Prefer overloading over optional parameters when:

↥ back to top

Q. Is operator overloading supported in C#?

Yes. C# allows you to overload most built-in operators for custom types using the operator keyword on a public static method. This lets instances of your types work naturally with +, -, ==, <, etc.

Overloadable operators: +, -, *, /, %, ==, !=, <, >, <=, >=, !, ~, ++, --, &, |, ^, <<, >>, true, false

Example — Vector2D with operator overloading:

public readonly record struct Vector2D(double X, double Y)
{
    // Arithmetic
    public static Vector2D operator +(Vector2D a, Vector2D b) => new(a.X + b.X, a.Y + b.Y);
    public static Vector2D operator -(Vector2D a, Vector2D b) => new(a.X - b.X, a.Y - b.Y);
    public static Vector2D operator *(Vector2D v, double scalar) => new(v.X * scalar, v.Y * scalar);

    // Equality (record already provides == and != via value equality)
    public double Length => Math.Sqrt(X * X + Y * Y);

    public override string ToString() => $"({X}, {Y})";
}

var v1 = new Vector2D(1, 2);
var v2 = new Vector2D(3, 4);

Console.WriteLine(v1 + v2);   // Output: (4, 6)
Console.WriteLine(v2 - v1);   // Output: (2, 2)
Console.WriteLine(v1 * 3);    // Output: (3, 6)
Console.WriteLine(v1 == new Vector2D(1, 2)); // True (record equality)
Console.WriteLine(v1.Length); // Output: 2.236...

Comparison operators — must be overloaded in pairs:

public class Temperature : IComparable<Temperature>
{
    public double Celsius { get; }
    public Temperature(double c) => Celsius = c;

    public static bool operator <(Temperature a, Temperature b) => a.Celsius < b.Celsius;
    public static bool operator >(Temperature a, Temperature b) => a.Celsius > b.Celsius;
    public static bool operator ==(Temperature a, Temperature b) => a.Celsius == b.Celsius;
    public static bool operator !=(Temperature a, Temperature b) => a.Celsius != b.Celsius;

    public int CompareTo(Temperature? other) => Celsius.CompareTo(other?.Celsius);
    public override bool Equals(object? obj) => obj is Temperature t && t.Celsius == Celsius;
    public override int GetHashCode() => Celsius.GetHashCode();
}

var t1 = new Temperature(100);
var t2 = new Temperature(37);
Console.WriteLine(t1 > t2);  // True
Console.WriteLine(t1 == t2); // False

Note: && and || cannot be overloaded directly — overload true/false/&/| instead.

↥ back to top

Q. Can this be used within a static method?

No. this refers to the current instance of a class, but static methods do not belong to any instance — they belong to the type itself. Therefore, this has no meaning inside a static method and the compiler will produce an error.

public class Counter
{
    private int _count = 0;

    public void Increment() => _count++;  // instance method — 'this' available

    public static Counter Create()
    {
        // Console.WriteLine(this._count); //  Compile error: CS0026
        // 'this' is not valid in a static member
        return new Counter();              // … create a new instance instead
    }

    public static int Compare(Counter a, Counter b) =>
        a._count.CompareTo(b._count); // … work with explicit instances
}

var c = Counter.Create();
c.Increment();

Why: Static methods are resolved at compile time on the type, not a runtime instance. There is no object to which this could refer.

↥ back to top

Q. Can you declare an override method to be static if the original method is not static?

No. You cannot change the static/instance nature of a method when overriding. An override method must match the base method's signature exactly — same name, same parameter types, same staticness.

public class Base
{
    public virtual void Show() => Console.WriteLine("Base");
}

public class Derived : Base
{
    //  Compile error CS0106: cannot change 'virtual' to 'static' in override
    // public static override void Show() => Console.WriteLine("Derived");

    // … Correct override — non-static like the base
    public override void Show() => Console.WriteLine("Derived");
}

Summary of override rules:

Attempt Allowed?
virtualoverride (non-static)
virtual non-static ’ static override  
static method ’ static override (static methods cannot be virtual)
abstractoverride
↥ back to top

Q. Can you declare the override method static while the original method is non-static?

No — same answer as above. override requires the method to be non-static if the base method is non-static. Attempting to combine static and override on a method that overrides a virtual instance method is a compile error.

The only way to introduce a static method with the same name is to use new (method hiding), which is a completely separate method that is not polymorphic:

public class Base
{
    public virtual void Process() => Console.WriteLine("Base instance Process");
}

public class Derived : Base
{
    // … Hides (does NOT override) the base virtual method
    public new static void Process() => Console.WriteLine("Derived static Process");

    // … Still override the virtual instance method separately if needed
    public override void Process() => Console.WriteLine("Derived instance Process");
}

// Warning: hiding causes confusion — use with care and explicit casts:
Derived.Process();                   // Output: Derived static Process
Base b = new Derived();
b.Process();                         // Output: Derived instance Process (override wins)
↥ back to top

Q. Why can you have only one static constructor?

A static constructor initialises the type itself (not instances). Because there is exactly one copy of the type, it needs to be initialised exactly once — so only one static constructor is allowed and it takes no parameters.

public class AppConfig
{
    public static string ConnectionString { get; }
    public static int MaxRetries { get; }

    // One static constructor — no parameters, no access modifier
    static AppConfig()
    {
        ConnectionString = Environment.GetEnvironmentVariable("DB_CONN")
                           ?? "Server=localhost;Database=App";
        MaxRetries = 3;
        Console.WriteLine("AppConfig initialised once");
    }
}

// Accessed multiple times — static constructor runs only once
Console.WriteLine(AppConfig.ConnectionString); // "initialised once" printed here
Console.WriteLine(AppConfig.MaxRetries);       // no re-initialisation

Reasons only one is allowed:

  1. No parameters — you can't distinguish overloads without parameters.
  2. Single initialisation — the CLR guarantees it runs exactly once before any static member is first accessed.
  3. Thread safety — the CLR makes static constructor execution thread-safe automatically; multiple constructors would complicate this guarantee.
↥ back to top

Q. When does the static constructor fire?

The static constructor fires automatically, once, before the type is first used — either when a static member is first accessed or when the first instance of the class is created. You cannot call it directly.

public class Sensor
{
    public static string Model { get; }

    static Sensor()
    {
        Model = "SensorX-2000";
        Console.WriteLine("Static constructor ran");
    }

    public Sensor() => Console.WriteLine("Instance constructor ran");
}

// --- First static member access ---
Console.WriteLine(Sensor.Model);  // triggers static constructor
// Output:
// Static constructor ran
// SensorX-2000

// --- First instance creation (if static ctor not yet run) ---
var s = new Sensor();
// Output:
// Static constructor ran    runs first
// Instance constructor ran  then instance ctor

Timing guarantees:

↥ back to top

Q. When will the static constructor be called?

The static constructor is called by the CLR (not by developer code) and is guaranteed to run before the first use of the type — whichever of these occurs first:

  1. First access to a static member (field, property, or method)
  2. First instantiation of the class
public class Registry
{
    public static Dictionary<string, string> Entries { get; } = new();

    static Registry()
    {
        // Called before Entries is first accessed or Registry is first instantiated
        Entries["version"] = "1.0";
        Entries["env"]     = "production";
        Console.WriteLine("Registry loaded");
    }
}

// Triggered by first static member access:
Console.WriteLine(Registry.Entries["version"]);
// Output:
// Registry loaded
// 1.0

// Second access — static constructor does NOT run again:
Console.WriteLine(Registry.Entries["env"]); // Output: production

Key rules:

↥ back to top

Q. Can we declare a Public access modifier for a static constructor?

No. Static constructors cannot have any access modifier (public, private, protected, internal). The CLR controls when it is called; restricting or exposing access would be meaningless. Adding an access modifier is a compile error.

public class MyClass
{
    //  Compile error CS0515: access modifiers are not allowed on static constructors
    // public static MyClass() { }

    // … Correct — no access modifier
    static MyClass()
    {
        Console.WriteLine("Type initialised");
    }
}

Summary of static constructor rules:

Rule Value
Access modifier None (not allowed)
Parameters None (not allowed)
Return type None
Can be overloaded No — only one allowed
Called by CLR automatically
Called how many times Once per AppDomain
↥ back to top

Q. If we declare Main() and a static constructor in the same class, which one will be called first?

The static constructor fires first, before Main() executes.

When the CLR starts the application, it needs to load and initialise the entry-point class before calling Main(). Type initialisation (the static constructor) happens as part of that loading process.

public class Program
{
    static Program()
    {
        Console.WriteLine("1. Static constructor");
    }

    public static void Main(string[] args)
    {
        Console.WriteLine("2. Main method");
    }
}
// Output:
// 1. Static constructor
// 2. Main method

Why: The CLR guarantees that all static members are initialised before any method of the type runs. Since Main() is a static method of Program, the CLR initialises Program (runs its static constructor) before entering Main().

↥ back to top

Q. How does dependency inversion benefit? Show with an example.

The Dependency Inversion Principle (DIP) — the D in SOLID — states:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.

Benefits:

Without DIP (tightly coupled — bad):

// High-level module depends directly on low-level SqlServer class
public class OrderService
{
    private readonly SqlOrderRepository _repo = new SqlOrderRepository(); // hard dependency

    public void PlaceOrder(string item)
    {
        _repo.Save(item); // cannot swap to a different repo without changing OrderService
    }
}

With DIP (loosely coupled — good):

// 1. Abstraction (interface) — both layers depend on this
public interface IOrderRepository
{
    void Save(string item);
}

// 2. Low-level detail implements the abstraction
public class SqlOrderRepository : IOrderRepository
{
    public void Save(string item) => Console.WriteLine($"SQL: Saved '{item}'");
}

public class InMemoryOrderRepository : IOrderRepository
{
    private readonly List<string> _orders = [];
    public void Save(string item) { _orders.Add(item); Console.WriteLine($"Memory: Saved '{item}'"); }
}

// 3. High-level module depends on abstraction, not concrete class
public class OrderService(IOrderRepository repo) // injected via constructor
{
    public void PlaceOrder(string item) => repo.Save(item);
}

// Production — use SQL
var prodService = new OrderService(new SqlOrderRepository());
prodService.PlaceOrder("Laptop"); // SQL: Saved 'Laptop'

// Test — swap to in-memory without touching OrderService
var testService = new OrderService(new InMemoryOrderRepository());
testService.PlaceOrder("Monitor"); // Memory: Saved 'Monitor'

With ASP.NET Core DI (.NET 10):

builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
// Swap to InMemoryOrderRepository by changing ONE line — no code changes elsewhere
↥ back to top

Q. Will only Dependency Inversion solve the decoupling problem?

No. DIP is essential but not sufficient on its own. Full decoupling requires applying several complementary principles and patterns together.

What DIP solves:

What DIP alone does NOT solve:

Gap Solution
Too many responsibilities in one class SRP — Single Responsibility Principle
Open to breaking changes when extending OCP — Open/Closed Principle
Interfaces that are too broad (fat interfaces) ISP — Interface Segregation Principle
Subclasses violating base-class contracts LSP — Liskov Substitution Principle
Runtime coupling via events Observer / Event Aggregator pattern
Service location anti-pattern Constructor injection (not ServiceLocator.Get<T>())
Circular dependencies Mediator pattern or redesign

Example — DIP alone is not enough:

// DIP applied: depends on interface …
// BUT violates SRP: does ordering, emailing, AND logging in one class 
public class OrderService(IOrderRepository repo, IEmailService email, ILogger logger)
{
    public void PlaceOrder(string item)
    {
        repo.Save(item);            // ordering
        email.Send("Confirmation"); // emailing — should be separate concern
        logger.Log("Order placed"); // logging — cross-cutting concern
    }
}

Better — combine DIP + SRP + OCP:

// Separate concerns, each depending on abstractions
public class OrderService(IOrderRepository repo, IOrderEventPublisher events)
{
    public void PlaceOrder(string item)
    {
        repo.Save(item);
        events.Publish(new OrderPlacedEvent(item)); // decoupled via event
    }
}

// Email and logging react to the event — OrderService doesn\'t know about them

Takeaway: Apply all five SOLID principles together, use dependency injection frameworks (like .NET's built-in DI), and consider patterns like Mediator, Observer, and Event Aggregator for full decoupling.

↥ back to top

Q. What is the difference between a value type and a reference type in C#?

This is one of the most fundamental distinctions in C#. It affects memory layout, assignment behavior, equality semantics, and performance.

Feature Value type Reference type
Stored in Stack (or inline in containing object) Heap
Assignment Copies the value Copies the reference (both point to same object)
Default value Zero/false/\0 etc. null
Equality (default) Value-based Reference-based (same object?)
Can be null Only via Nullable<T> / T? Yes
Inheritance Inherits from ValueTypeobject Inherits from object
Examples int, double, bool, struct, enum, record struct class, string, array, interface, delegate, record class

Assignment behavior:

// Value type — assignment copies the value
int a = 10;
int b = a;
b = 99;
Console.WriteLine(a); // Output: 10  (a is unchanged — b got a copy)

// Reference type — assignment copies the reference
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1;   // both point to the same List
list2.Add(4);
Console.WriteLine(list1.Count); // Output: 4  (list1 was modified via list2)

Custom struct (value) vs class (reference):

public struct PointV { public int X, Y; }    // value type
public class  PointR { public int X, Y; }    // reference type

var sv = new PointV { X = 1, Y = 2 };
var sv2 = sv;  // full copy
sv2.X = 99;
Console.WriteLine(sv.X);  // Output: 1 — copy is independent

var rv = new PointR { X = 1, Y = 2 };
var rv2 = rv;  // reference copy
rv2.X = 99;
Console.WriteLine(rv.X);  // Output: 99 — same object!

Boxing — value type wrapped in reference:

int n = 42;
object boxed = n;     // boxing: value type ’ heap allocation
int unboxed = (int)boxed; // unboxing
Console.WriteLine(unboxed); // Output: 42

Modern readonly record struct (C# 10+) — value type with immutability and value equality:

public readonly record struct Money(decimal Amount, string Currency)
{
    public override string ToString() => $"{Amount:F2} {Currency}";
}

var m1 = new Money(100m, "USD");
var m2 = new Money(100m, "USD");
Console.WriteLine(m1 == m2);  // Output: True  (value equality)
Console.WriteLine(m1);        // Output: 100.00 USD
↥ back to top

Q. What is encapsulation in C# and why is it important?

Encapsulation is the OOP principle of bundling data (fields) and the methods that operate on that data into a single unit (class), and restricting direct access to internal state through access modifiers and properties.

Why it is important:

Example — without encapsulation (bad):

public class Temperature
{
    public double Celsius; // anyone can set any value
}

var t = new Temperature();
t.Celsius = -9999; // invalid, no protection

Example — with encapsulation (good):

public class Temperature
{
    private double _celsius;

    public double Celsius
    {
        get => _celsius;
        set
        {
            if (value < -273.15)
                throw new ArgumentOutOfRangeException(nameof(value), "Below absolute zero!");
            _celsius = value;
        }
    }

    public double Fahrenheit => _celsius * 9 / 5 + 32;

    public override string ToString() => $"{_celsius}°C / {Fahrenheit}°F";
}

var temp = new Temperature { Celsius = 100 };
Console.WriteLine(temp); // Output: 100°C / 212°F

Modern .NET — encapsulation with records and init:

// Immutable by design — state set once at construction
public record class Product
{
    public required string Name { get; init; }
    public required decimal Price { get; init; }

    // Validation via constructor
    public Product
    {
        if (Price < 0) throw new ArgumentException("Price cannot be negative");
    }
}

var p = new Product { Name = "Laptop", Price = 999m };
// p.Price = -1; // Compile error — init-only
↥ back to top

Q. How do you implement encapsulation in C#?

Encapsulation is implemented in C# through three main mechanisms:

  1. Access modifiers — restrict who can see or modify members.
  2. Properties — expose controlled read/write access to private fields, with optional validation.
  3. Methods — expose behaviour while hiding the internal steps.
public class BankAccount
{
    // 1. Private backing field — hidden from outside
    private decimal _balance;
    private readonly List<string> _transactions = [];

    public string Owner { get; }  // read-only property (init via constructor)

    public BankAccount(string owner, decimal initialDeposit)
    {
        if (string.IsNullOrWhiteSpace(owner))
            throw new ArgumentException("Owner required");
        Owner = owner;
        Deposit(initialDeposit); // use method, not direct field access
    }

    // 2. Property with validation — encapsulates the balance field
    public decimal Balance => _balance; // read-only externally

    // 3. Methods expose controlled behavior
    public void Deposit(decimal amount)
    {
        if (amount <= 0) throw new ArgumentException("Amount must be positive");
        _balance += amount;
        _transactions.Add($"+{amount:C}");
    }

    public void Withdraw(decimal amount)
    {
        if (amount <= 0) throw new ArgumentException("Amount must be positive");
        if (amount > _balance) throw new InvalidOperationException("Insufficient funds");
        _balance -= amount;
        _transactions.Add($"-{amount:C}");
    }

    public IReadOnlyList<string> GetHistory() => _transactions.AsReadOnly();
}

var acc = new BankAccount("Pradeep", 1000m);
acc.Deposit(500m);
acc.Withdraw(200m);

Console.WriteLine(acc.Balance); // Output: 1300
foreach (var t in acc.GetHistory())
    Console.WriteLine(t); // +£1,000.00, +£500.00, -£200.00

Modern approach — required + init properties (C# 11 / .NET 7+):

public class Product
{
    public required string Name  { get; init; }     // set once at construction
    public required decimal Price { get; init; }

    public string Description => $"{Name}: {Price:C}";
}

var p = new Product { Name = "Laptop", Price = 999m };
Console.WriteLine(p.Description); // Laptop: £999.00
// p.Price = 500m; //  compile error — init-only
↥ back to top

Q. Can you provide an example of encapsulation in C#?

Example — Temperature class with validated property:

public class Temperature
{
    private double _celsius;

    public double Celsius
    {
        get => _celsius;
        set
        {
            if (value < -273.15)
                throw new ArgumentOutOfRangeException(nameof(value),
                    "Temperature cannot be below absolute zero (-273.15°C)");
            _celsius = value;
        }
    }

    // Derived property — read-only, computed from internal state
    public double Fahrenheit => _celsius * 9.0 / 5.0 + 32;
    public double Kelvin     => _celsius + 273.15;

    public override string ToString() =>
        $"{_celsius:F1}°C / {Fahrenheit:F1}°F / {Kelvin:F2}K";
}

var t = new Temperature { Celsius = 100 };
Console.WriteLine(t); // Output: 100.0°C / 212.0°F / 373.15K

t.Celsius = -10;
Console.WriteLine(t.Fahrenheit); // Output: 14.0

try
{
    t.Celsius = -300; //  below absolute zero
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine(ex.Message); // Temperature cannot be below absolute zero
}

Key encapsulation points in this example:

↥ back to top

Q. What are the benefits of using encapsulation in object-oriented programming?

Benefit Explanation
Data protection Private fields cannot be set to invalid values from outside the class
Validation Property setters and constructors enforce invariants at the boundary
Maintainability Internal implementation can change without affecting external callers
Reduced coupling Consumers depend on the public API (interface), not internals
Testability Encapsulated classes are self-contained and easier to unit-test
Readability Clear public surface separates “what to use” from “how it works”

Example — changing internals without breaking callers:

// Version 1 — stores Celsius internally
public class Temperature
{
    private double _celsius;
    public double Celsius { get => _celsius; set => _celsius = value; }
}

// Version 2 — internally switched to Kelvin storage (implementation detail)
// Callers still use .Celsius — nothing breaks!
public class Temperature
{
    private double _kelvin; // changed internal representation

    public double Celsius
    {
        get => _kelvin - 273.15;
        set => _kelvin = value + 273.15; // validation can be added here
    }
}

// Caller — unchanged in both versions
var t = new Temperature { Celsius = 100 };
Console.WriteLine(t.Celsius); // Output: 100

The caller never knew about _celsius or _kelvin — encapsulation made the change invisible.

↥ back to top

Q. How does encapsulation help in maintaining code?

Encapsulation supports maintainability in four key ways:

1. Change-safe internals — You can refactor, optimise, or fix the internal implementation of a class without touching any of its consumers, as long as the public API stays the same.

2. Single point of change — Validation logic lives in one place (the property or constructor). Fix a bug once; every caller benefits.

3. Prevents invalid state propagation — Bugs caused by one part of the code corrupting another's data are stopped at the class boundary.

4. Self-documenting intent — A private field signals “implementation detail”; a public property signals “intended API”. Reading the public members is enough to understand how to use the class.

// Before: no encapsulation — Order total calculated in 12 different places
public class Order { public decimal Total; } // anyone writes to Total

// After: encapsulation — total is always correct, calculated in one place
public class Order
{
    private readonly List<decimal> _lineItems = [];

    public void AddItem(decimal price)
    {
        if (price < 0) throw new ArgumentException("Price cannot be negative");
        _lineItems.Add(price);
    }

    // Total is always consistent — derived from the single source of truth
    public decimal Total => _lineItems.Sum();
    public int ItemCount => _lineItems.Count;
}

var order = new Order();
order.AddItem(49.99m);
order.AddItem(19.99m);

Console.WriteLine(order.Total);     // Output: 69.98
Console.WriteLine(order.ItemCount); // Output: 2

// order.Total = 0; //  compile error — read-only, prevents accidental zeroing
↥ back to top

Q. What is the difference between encapsulation and abstraction in C#?

Both are OOP pillars and are often confused, but they address different concerns:

Aspect Encapsulation Abstraction
What it hides Internal data and state (how the object stores things) Internal complexity and implementation (how the object does things)
Achieved via Access modifiers, properties, private fields Abstract classes, interfaces, method signatures
Who benefits Protects the object from misuse Simplifies what the caller needs to know
Level Within a single class Across the design (class hierarchy, module boundary)
Question answered “Who can see/change this?” “What can I do with this?”

Encapsulation example — hiding state:

public class Stack<T>
{
    private readonly List<T> _items = []; // hidden — caller doesn\'t know it\'s a List

    public void Push(T item) => _items.Add(item);
    public T Pop()
    {
        if (_items.Count == 0) throw new InvalidOperationException("Stack is empty");
        var top = _items[^1];
        _items.RemoveAt(_items.Count - 1);
        return top;
    }
    public int Count => _items.Count;
}

Abstraction example — hiding how things work:

// Caller only knows "I can send notifications" — doesn\'t know SMTP vs push vs SMS
public interface INotificationService
{
    Task NotifyAsync(string userId, string message);
}

public class PushNotificationService : INotificationService
{
    public async Task NotifyAsync(string userId, string message)
    {
        // Complex push notification logic hidden here
        Console.WriteLine($"Push ’ {userId}: {message}");
        await Task.CompletedTask;
    }
}

// Consumer works with the abstraction, not the detail
public class AlertService(INotificationService notifier)
{
    public Task SendAlertAsync(string userId, string msg) =>
        notifier.NotifyAsync(userId, msg);
}

Together: Encapsulation protects the state inside PushNotificationService; abstraction hides what service is used from AlertService.

↥ back to top

Q. How can encapsulation be violated in C#?

Encapsulation is violated when internal state is exposed or bypassed in ways that allow invalid or unexpected modifications. Common violations:

1. Public fields — no validation possible:

//  Violation — direct public field
public class Circle { public double Radius; } // can be set to -1

// … Fix — property with validation
public class Circle
{
    private double _radius;
    public double Radius
    {
        get => _radius;
        set => _radius = value >= 0 ? value
               : throw new ArgumentException("Radius must be ≥ 0");
    }
}

2. Returning mutable collections directly:

//  Violation — caller can mutate internal list
public class Roster
{
    private readonly List<string> _names = ["Alice", "Bob"];
    public List<string> Names => _names; // exposes the internal list
}

var r = new Roster();
r.Names.Clear(); // corrupts internal state!

// … Fix — return read-only view
public IReadOnlyList<string> Names => _names.AsReadOnly();

3. Reflection — bypasses access modifiers (use only in exceptional scenarios):

public class Secret { private int _code = 42; }

var s = new Secret();
var field = typeof(Secret).GetField("_code",
    System.Reflection.BindingFlags.NonPublic |
    System.Reflection.BindingFlags.Instance);

field!.SetValue(s, 999); //  bypasses encapsulation via reflection

4. Overly broad access modifiers:

//  Making implementation details public/internal unnecessarily
public class PaymentProcessor
{
    public string _internalToken = "abc123"; // should be private
}

5. Mutable default property setters without validation:

//  Auto-property with public setter — no opportunity to validate
public class Person
{
    public int Age { get; set; } // can be set to -1 or 999
}

// … Validate in setter or use init + constructor validation
public class Person
{
    private int _age;
    public int Age
    {
        get => _age;
        set => _age = value is >= 0 and <= 150 ? value
               : throw new ArgumentOutOfRangeException(nameof(value));
    }
}
↥ back to top

Q. How do access modifiers (public, private, protected, internal) relate to encapsulation in C#?

Access modifiers are the primary tool for implementing encapsulation in C#. They define the visibility boundary of each member, controlling which code can read or modify internal state.

Modifier Visibility Encapsulation role
private Same class only Strongest encapsulation — fields should almost always be private
protected Same class + derived classes Shares internals with subclasses while hiding from the outside world
internal Same assembly Shares internals within a module/library, hidden from other assemblies
private protected Same class + derived (same assembly) Most restrictive combination
protected internal Derived classes (any) + same assembly Widest combined modifier
public Everywhere No encapsulation — intentionally exposed API surface

How they work together in a well-encapsulated class:

public class Employee
{
    // private — never exposed: internal state
    private decimal _salary;
    private readonly List<string> _auditLog = [];

    // public — the intentional API surface
    public string Name { get; }
    public string Department { get; private set; } // readable everywhere, settable here

    // protected — available to payroll sub-hierarchy
    protected decimal BaseSalary => _salary;

    // internal — visible within the HR assembly for reporting
    internal DateTime HireDate { get; }

    public Employee(string name, string department, decimal salary, DateTime hireDate)
    {
        Name       = name;
        Department = department;
        HireDate   = hireDate;
        SetSalary(salary); // use private method to enforce rules
    }

    // private method — implementation detail, not part of public API
    private void SetSalary(decimal value)
    {
        if (value < 0) throw new ArgumentException("Salary must be non-negative");
        _salary = value;
        _auditLog.Add($"{DateTime.UtcNow:u}: Salary set to {value:C}");
    }

    public void AdjustSalary(decimal percentage)
    {
        SetSalary(_salary * (1 + percentage / 100));
    }

    public IReadOnlyList<string> GetAuditLog() => _auditLog.AsReadOnly();
}

var emp = new Employee("Pradeep", "Engineering", 80_000m, DateTime.Today);
emp.AdjustSalary(10); // 10% raise

foreach (var entry in emp.GetAuditLog())
    Console.WriteLine(entry);

Rule of thumb: Start with private. Only widen the access modifier when there is a clear need.

↥ back to top

Q. Explain method hiding?

Method hiding occurs when a derived class defines a method with the same name and signature as a base class method using the new keyword. The derived method hides the base method rather than overriding it — it breaks the polymorphic chain.

Key difference from override:

public class Animal
{
    public virtual string Sound() => "...";  // virtual — intended for override
    public string Name()  => "Animal";        // non-virtual — hiding candidate
}

public class Dog : Animal
{
    public override string Sound() => "Woof"; // override — polymorphic
    public new string Name()  => "Dog";        // new — hides Animal.Name()
}

// ---- Polymorphism with override ----
Animal a1 = new Dog();
Console.WriteLine(a1.Sound()); // Output: Woof   Dog\'s version (override wins)

// ---- Method hiding with new ----
Animal a2 = new Dog();
Console.WriteLine(a2.Name()); // Output: Animal  reference type decides (base wins!)

Dog d = new Dog();
Console.WriteLine(d.Name());  // Output: Dog     derived reference ’ Dog\'s version

Why method hiding exists:

Warning: Method hiding breaks the Liskov Substitution Principle — code that holds a Dog via an Animal reference will call the base Name() unexpectedly. Prefer override for polymorphic behavior.

// Practical demonstration of the problem
void PrintName(Animal a) => Console.WriteLine(a.Name());

PrintName(new Animal()); // Output: Animal
PrintName(new Dog());    // Output: Animal   NOT Dog! Hiding breaks polymorphism
↥ back to top

Q. What is polymorphism?

Polymorphism (“many forms”) is the OOP ability for objects of different types to be treated as instances of a common base type, with behavior resolved at runtime based on the actual type.

C# supports two main forms:

1. Compile-time (static) polymorphism — method overloading:

Multiple methods with the same name but different parameter signatures.

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

var calc = new Calculator();
Console.WriteLine(calc.Add(1, 2));          // 3
Console.WriteLine(calc.Add(1.5, 2.5));      // 4
Console.WriteLine(calc.Add("Hello ", "World")); // Hello World

2. Runtime (dynamic) polymorphism — method overriding:

Derived classes override virtual or abstract methods; the correct implementation is chosen at runtime.

public abstract class Shape
{
    public abstract double Area();
    public void Print() => Console.WriteLine($"{GetType().Name} area: {Area():F2}");
}

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

public class Rectangle(double w, double h) : Shape
{
    public override double Area() => w * h;
}

Shape[] shapes = [new Circle(5), new Rectangle(4, 6)];
foreach (var shape in shapes)
    shape.Print();
// Output:
// Circle area: 78.54
// Rectangle area: 24.00

3. Pattern matching polymorphism (C# 8+):

Another modern way to handle type-based dispatch without inheritance:

double GetArea(object shape) => shape switch
{
    Circle c       => Math.PI * c.radius * c.radius,
    Rectangle r    => r.w * r.h,
    _              => throw new ArgumentException("Unknown shape")
};
↥ back to top

Q. Explain the four pillars of OOP in C#?

1. Encapsulation

Bundling data and behavior together while hiding internal details via access modifiers and properties.

public class BankAccount
{
    private decimal _balance;
    public void Deposit(decimal amount) { if (amount > 0) _balance += amount; }
    public decimal Balance => _balance; // read-only access
}

2. Abstraction

Exposing only relevant details and hiding complexity. Achieved via abstract classes, interfaces, and encapsulation.

public interface IPaymentGateway
{
    Task<bool> ChargeAsync(decimal amount, string cardToken);
}
// Caller only depends on the interface, not Stripe/PayPal internals

3. Inheritance

A class inherits members from a base class, promoting code reuse.

public class Vehicle
{
    public string Brand { get; }
    public Vehicle(string brand) => Brand = brand;
    public virtual string Describe() => $"{Brand} vehicle";
}

public class Car(string brand, int doors) : Vehicle(brand)
{
    public override string Describe() => $"{Brand} car with {doors} doors";
}

Vehicle v = new Car("Toyota", 4);
Console.WriteLine(v.Describe()); // Toyota car with 4 doors

4. Polymorphism

Objects of different types behave differently through a common interface (method overriding and overloading).

public abstract class Notification
{
    public abstract void Send(string message);
}

public class EmailNotification : Notification
{
    public override void Send(string message) =>
        Console.WriteLine($"Email: {message}");
}

public class SmsNotification : Notification
{
    public override void Send(string message) =>
        Console.WriteLine($"SMS: {message}");
}

Notification[] notifications = [new EmailNotification(), new SmsNotification()];
foreach (var n in notifications)
    n.Send("Your order is shipped!");
// Email: Your order is shipped!
// SMS: Your order is shipped!
↥ back to top

Q. What are fields in C# and how do they differ from properties?

A field is a variable declared directly inside a class or struct that holds data. A property is a member that wraps a field (or computes a value) with get/set accessors, enabling validation and encapsulation.

Field types:

Modifier Behaviour
(none) Instance field — one per object
static Shared across all instances
readonly Can only be assigned in declaration or constructor
const Compile-time constant — implicitly static
public class BankAccount
{
    // ” Fields ”——————————————————————————————————————————————————
    private decimal _balance;                      // instance field
    private static int _totalAccounts;             // static field
    private readonly string _accountNumber;        // readonly field
    private const decimal MinimumBalance = 0m;     // const field

    // ” Auto-implemented property (compiler generates backing field)
    public string Owner { get; set; }

    // ” Property with custom getter/setter using the private field
    public decimal Balance
    {
        get => _balance;
        private set
        {
            if (value < MinimumBalance)
                throw new ArgumentOutOfRangeException(nameof(value), "Balance cannot be negative.");
            _balance = value;
        }
    }

    // ” Init-only property (C# 9) — settable only during object initialisation
    public DateTime OpenedOn { get; init; } = DateTime.UtcNow;

    public BankAccount(string owner, string accountNumber, decimal initialDeposit)
    {
        Owner = owner;
        _accountNumber = accountNumber;   // OK — inside constructor
        Balance = initialDeposit;
        _totalAccounts++;
    }

    public void Deposit(decimal amount) => Balance += amount;

    public static int TotalAccounts => _totalAccounts;
}

var account = new BankAccount("Alice", "ACC-001", 500m);
account.Deposit(200m);
Console.WriteLine(account.Balance);          // 700
Console.WriteLine(BankAccount.TotalAccounts); // 1

// account.OpenedOn = DateTime.UtcNow;       //  init-only — compile error outside init
var account2 = new BankAccount("Bob", "ACC-002", 100m) { OpenedOn = new DateTime(2024, 1, 1) };
Console.WriteLine(BankAccount.TotalAccounts); // 2

Key differences — field vs property:

Aspect Field Property
Access control Single modifier Independent get/set modifiers
Validation Manual — direct assignment Encapsulated in set accessor
Interface support Cannot be declared in interface Can be declared in interface
Data binding Typically not bindable Bindable (WPF, Blazor, etc.)
Reflection FieldInfo PropertyInfo
↥ back to top

Q. What are records in C# and how do they differ from classes?

A record (C# 9+) is a reference type that is designed for immutable data models with value-based equality. Records automatically generate Equals, GetHashCode, ToString, and a positional deconstruct from their primary constructor.

Record variants:

Kind Syntax Type
record (class) record Person(string Name, int Age) Reference type
record struct record struct Point(int X, int Y) Value type
readonly record struct readonly record struct Point(int X, int Y) Immutable value type
// ” 1. Positional record with primary constructor ”————————————————
record Person(string FirstName, string LastName, int Age);

var p1 = new Person("Alice", "Smith", 30);
var p2 = new Person("Alice", "Smith", 30);

Console.WriteLine(p1 == p2);          // true  — value equality
Console.WriteLine(p1.Equals(p2));     // true
Console.WriteLine(p1);                // Person { FirstName = Alice, LastName = Smith, Age = 30 }

// ” 2. Non-destructive mutation with `with` expression ”———————————
var p3 = p1 with { Age = 31 };        // creates a new record; p1 is unchanged
Console.WriteLine(p3);               // Person { FirstName = Alice, LastName = Smith, Age = 31 }

// ” 3. Deconstruction ”———————————————————————————————————————————
var (first, last, age) = p1;
Console.WriteLine($"{first} {last}, {age}");  // Alice Smith, 30

// ” 4. Inheritance ”——————————————————————————————————————————————
record Employee(string FirstName, string LastName, int Age, string Department)
    : Person(FirstName, LastName, Age);

var emp = new Employee("Bob", "Jones", 25, "Engineering");
Console.WriteLine(emp);
// Employee { FirstName = Bob, LastName = Jones, Age = 25, Department = Engineering }

// ” 5. Custom members ”———————————————————————————————————————————
record Product(string Name, decimal Price)
{
    // Computed property
    public string Label => $"{Name} (${Price:F2})";

    // Custom validation via init accessor
    public decimal Price { get; init; } =
        Price >= 0 ? Price : throw new ArgumentOutOfRangeException(nameof(Price));
}

var prod = new Product("Widget", 9.99m);
Console.WriteLine(prod.Label);  // Widget ($9.99)

// ” 6. record struct (C# 10) ”————————————————————————————————————
record struct Coordinate(double Lat, double Lon);

var c1 = new Coordinate(51.5, -0.1);
var c2 = c1 with { Lon = -0.2 };
Console.WriteLine(c1 == c2);  // false

Records vs classes:

Feature class record
Equality Reference (by default) Value (auto-generated)
Immutability Manual init-only by default
ToString() Type name Property dump
with expression No Yes
Inheritance Yes Yes (record-to-record)
Deconstruction Manual Auto (positional)
Use case Mutable entities, services DTOs, value objects, event data
↥ back to top

# 4. INHERITANCE


Q. What is inheritance in C# and how does it work?

Inheritance allows a class (derived/child class) to acquire the members (fields, properties, methods) of another class (base/parent class) using the : syntax. It promotes code reuse and enables polymorphism.

classDiagram
    class Person {
        +string Name
        +int Age
        +virtual Describe() string
    }
    class Employee {
        +string Department
        +override Describe() string
    }
    class Manager {
        +int DirectReports
        +override Describe() string
    }
    class IDescribable {
        <<interface>>
        +Describe() string
    }

    Person <|-- Employee : inherits
    Employee <|-- Manager : inherits
    IDescribable <|.. Person : implements

Key rules:

Example (.NET 10 / C# 14):

public class Person(string name, int age)
{
    public string Name { get; } = name;
    public int Age { get; } = age;
    public virtual string Describe() => $"{Name}, age {Age}";
}

public class Employee(string name, int age, string department)
    : Person(name, age)
{
    public string Department { get; } = department;
    public override string Describe() =>
        $"{base.Describe()}, {Department} dept";
}

public class Manager(string name, int age, string department, int reports)
    : Employee(name, age, department)
{
    public int DirectReports { get; } = reports;
    public override string Describe() =>
        $"{base.Describe()}, manages {DirectReports} people";
}

Person[] people =
[
    new Person("Alice", 30),
    new Employee("Bob", 35, "Engineering"),
    new Manager("Carol", 45, "Engineering", 10),
];

foreach (var p in people)
    Console.WriteLine(p.Describe());
// Alice, age 30
// Bob, age 35, Engineering dept
// Carol, age 45, Engineering dept, manages 10 people

Constructor chaining with base:

public class Animal(string name)
{
    public string Name { get; } = name;
}

public class Dog(string name, string breed) : Animal(name)
{
    public string Breed { get; } = breed;
    public override string ToString() => $"{Name} ({Breed})";
}

var d = new Dog("Rex", "Labrador");
Console.WriteLine(d); // Output: Rex (Labrador)
↥ back to top

Q. What is the difference between single inheritance and multiple inheritance in C#?

Single inheritance means a class can inherit from exactly one base class. C# supports only single class inheritance.

Multiple inheritance (inheriting from more than one class simultaneously) is not supported for classes in C#. However, a class can implement multiple interfaces, which achieves a similar design goal.

Feature Single inheritance Multiple inheritance
Class Supported Not supported
Interface … (multiple interfaces allowed)
Diamond problem Not possible Avoided by design

Single class inheritance:

public class Animal
{
    public string Name { get; }
    public Animal(string name) => Name = name;
    public virtual string Describe() => $"Animal: {Name}";
}

public class Dog(string name) : Animal(name) // single base class
{
    public override string Describe() => $"Dog: {Name}";
}

var d = new Dog("Rex");
Console.WriteLine(d.Describe()); // Output: Dog: Rex

Multiple interface implementation (C# alternative to multiple inheritance):

public interface IFlyable  { void Fly(); }
public interface ISwimmable { void Swim(); }

// A class can implement multiple interfaces
public class Duck(string name) : Animal(name), IFlyable, ISwimmable
{
    public void Fly()  => Console.WriteLine($"{Name} is flying");
    public void Swim() => Console.WriteLine($"{Name} is swimming");
    public override string Describe() => $"Duck: {Name}";
}

var duck = new Duck("Donald");
duck.Fly();   // Output: Donald is flying
duck.Swim();  // Output: Donald is swimming

// Multiple interface references
IFlyable  flyer   = duck;
ISwimmable swimmer = duck;
flyer.Fly();   // Donald is flying
swimmer.Swim(); // Donald is swimming

Why C# avoids multiple class inheritance: The diamond problem — if two base classes define the same method, the compiler cannot determine which version to call. Interfaces (with default implementations in C# 8+) sidestep this via explicit interface implementation.

↥ back to top

Q. How can you use the “base” keyword in C# to call the base class constructor?

The base keyword in a constructor's initialiser list calls a specific base class constructor before the derived constructor body executes.

Syntax:

public DerivedClass(args) : base(args_for_base) { }

Example — calling parameterised base constructors:

public class Vehicle
{
    public string Brand { get; }
    public int Year { get; }

    public Vehicle(string brand, int year)
    {
        Brand = brand;
        Year  = year;
        Console.WriteLine($"Vehicle created: {Brand} ({Year})");
    }
}

public class Car : Vehicle
{
    public int Doors { get; }

    // Calls Vehicle(string, int) before Car\'s body runs
    public Car(string brand, int year, int doors) : base(brand, year)
    {
        Doors = doors;
        Console.WriteLine($"Car created: {Doors} doors");
    }
}

public class ElectricCar : Car
{
    public int RangeKm { get; }

    // Chains all the way up: Vehicle ’ Car ’ ElectricCar
    public ElectricCar(string brand, int year, int doors, int rangeKm)
        : base(brand, year, doors)
    {
        RangeKm = rangeKm;
        Console.WriteLine($"ElectricCar created: {RangeKm}km range");
    }
}

var ec = new ElectricCar("Tesla", 2025, 4, 500);
// Output (top-down, base first):
// Vehicle created: Tesla (2025)
// Car created: 4 doors
// ElectricCar created: 500km range

Console.WriteLine($"{ec.Brand}, {ec.Doors} doors, {ec.RangeKm}km");
// Output: Tesla, 4 doors, 500km

With primary constructors (C# 12):

public class Animal(string name)
{
    public string Name { get; } = name;
}

// Primary constructor + base call
public class Dog(string name, string breed) : Animal(name)
{
    public string Breed { get; } = breed;
    public override string ToString() => $"{Name} ({Breed})";
}

Console.WriteLine(new Dog("Rex", "Labrador")); // Output: Rex (Labrador)

base in method calls:

public class Logger
{
    public virtual void Log(string msg) => Console.WriteLine($"[LOG] {msg}");
}

public class TimestampLogger : Logger
{
    public override void Log(string msg)
    {
        base.Log(msg); // call base implementation first
        Console.WriteLine($"[{DateTime.UtcNow:HH:mm:ss}]");
    }
}

new TimestampLogger().Log("started");
// [LOG] started
// [14:32:01]
↥ back to top

Q. Can you explain polymorphism and how it is achieved in C#?

Polymorphism (“many forms”) is the ability for a single interface or method name to represent different behaviors depending on the runtime type of the object. C# supports three forms:

1. Compile-time polymorphism — method overloading:

public class Printer
{
    public void Print(int value)    => Console.WriteLine($"int: {value}");
    public void Print(string value) => Console.WriteLine($"string: {value}");
    public void Print(double value) => Console.WriteLine($"double: {value}");
}

var p = new Printer();
p.Print(42);      // int: 42
p.Print("Hello"); // string: Hello
p.Print(3.14);    // double: 3.14

2. Runtime polymorphism — method overriding (virtual/override):

public abstract class Shape
{
    public abstract double Area();
    public virtual  string Describe() => $"{GetType().Name}: Area = {Area():F2}";
}

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

public class Rectangle(double w, double h) : Shape
{
    public override double Area() => w * h;
}

public class Triangle(double b, double height) : Shape
{
    public override double Area() => 0.5 * b * height;
}

// All three accessed through Shape reference — runtime decides which Area() to call
Shape[] shapes = [new Circle(5), new Rectangle(4, 6), new Triangle(3, 8)];

foreach (var shape in shapes)
    Console.WriteLine(shape.Describe());
// Circle: Area = 78.54
// Rectangle: Area = 24.00
// Triangle: Area = 12.00

3. Interface polymorphism:

public interface IAnimal { string Sound(); }

public class Cat : IAnimal { public string Sound() => "Meow"; }
public class Dog : IAnimal { public string Sound() => "Woof"; }
public class Cow : IAnimal { public string Sound() => "Moo"; }

IAnimal[] animals = [new Cat(), new Dog(), new Cow()];
foreach (var a in animals)
    Console.WriteLine(a.Sound()); // Meow / Woof / Moo

4. Pattern matching polymorphism (C# 8+):

static double GetArea(Shape shape) => shape switch
{
    Circle c      => Math.PI * 5 * 5,  // type pattern
    Rectangle r   => 4 * 6,
    _             => throw new ArgumentException("Unknown shape")
};
↥ back to top

Q. How can you use inheritance and polymorphism together to achieve dynamic dispatch in C#?

Dynamic dispatch means the correct method implementation is selected at runtime based on the actual type of the object, not the declared reference type. It is achieved by combining inheritance (virtual/override) with base-type references.

public abstract class Notification
{
    public string Recipient { get; }
    protected Notification(string recipient) => Recipient = recipient;

    // virtual method — subclasses override; dispatch is dynamic
    public abstract Task SendAsync(string message);

    // Template method pattern — fixed algorithm, dynamic steps
    public async Task NotifyAsync(string message)
    {
        Console.WriteLine($"Preparing notification for {Recipient}...");
        await SendAsync(message); // dynamic dispatch — runtime type decides
        Console.WriteLine("Done.");
    }
}

public class EmailNotification(string recipient, string smtpServer)
    : Notification(recipient)
{
    public override Task SendAsync(string message)
    {
        Console.WriteLine($"[SMTP:{smtpServer}] Email ’ {Recipient}: {message}");
        return Task.CompletedTask;
    }
}

public class SmsNotification(string recipient, string phoneNumber)
    : Notification(recipient)
{
    public override Task SendAsync(string message)
    {
        Console.WriteLine($"[SMS:{phoneNumber}] ’ {Recipient}: {message}");
        return Task.CompletedTask;
    }
}

public class PushNotification(string recipient, string deviceToken)
    : Notification(recipient)
{
    public override Task SendAsync(string message)
    {
        Console.WriteLine($"[Push:{deviceToken}] ’ {Recipient}: {message}");
        return Task.CompletedTask;
    }
}

// Dynamic dispatch — all through the Notification base reference
Notification[] notifications =
[
    new EmailNotification("alice@example.com", "smtp.gmail.com"),
    new SmsNotification("Bob", "+1-555-1234"),
    new PushNotification("Carol", "tok_abc123"),
];

foreach (var n in notifications)
    await n.NotifyAsync("Your order has shipped!"); // runtime picks the right SendAsync

Output:

Preparing notification for alice@example.com...
[SMTP:smtp.gmail.com] Email ’ alice@example.com: Your order has shipped!
Done.
Preparing notification for Bob...
[SMS:+1-555-1234] ’ Bob: Your order has shipped!
Done.
Preparing notification for Carol...
[Push:tok_abc123] ’ Carol: Your order has shipped!
Done.

Key point: NotifyAsync is defined once in the base class. It calls SendAsync, which is dispatched dynamically to whichever derived class is actually stored in n at runtime. Adding a new notification type (e.g., SlackNotification) requires zero changes to NotifyAsync.

↥ back to top

Q. What is the purpose of the base keyword in C#, and how is it used in inheritance?

The base keyword provides access to members of the immediate base class from within a derived class. It has two main uses:

1. Call a base class constructor (base(...) in initialiser list):

public class Person(string name, int age)
{
    public string Name { get; } = name;
    public int Age { get; } = age;
}

public class Employee : Person
{
    public string Department { get; }

    // base(name, age) calls Person\'s primary constructor
    public Employee(string name, int age, string department)
        : base(name, age)
    {
        Department = department;
    }

    public override string ToString() => $"{Name} ({Age}) — {Department}";
}

Console.WriteLine(new Employee("Pradeep", 30, "Engineering"));
// Output: Pradeep (30) — Engineering

2. Call a base class method from an overriding method (base.Method()):

public class Logger
{
    public virtual void Log(string message)
        => Console.WriteLine($"[LOG] {message}");
}

public class AuditLogger : Logger
{
    private readonly List<string> _audit = [];

    public override void Log(string message)
    {
        base.Log(message);            // run the base behavior first
        _audit.Add(message);          // then add audit-specific logic
        Console.WriteLine($"[AUDIT] Recorded: {message}");
    }
}

new AuditLogger().Log("User logged in");
// [LOG] User logged in
// [AUDIT] Recorded: User logged in

3. Access base class properties:

public class Shape
{
    public string Color { get; set; } = "Black";
}

public class Circle : Shape
{
    public double Radius { get; }
    public Circle(double radius) => Radius = radius;

    public override string ToString() =>
        $"Circle(r={Radius}, color={base.Color})"; // base.Color
}

Console.WriteLine(new Circle(5) { Color = "Red" });
// Output: Circle(r=5, color=Red)

What base cannot do:

↥ back to top

Q. What are the differences between virtual methods and abstract methods in C#, and when each should be used?

Feature virtual abstract
Has a body … Yes — provides a default implementation No body (in non-default-interface scenarios)
Must be overridden Optional … Mandatory in concrete derived classes
Class requirement Can be in any non-sealed class Class must be abstract
Can be instantiated directly … (if class is concrete) Abstract class cannot be instantiated
Purpose Provide sensible default, allow customisation Define a contract with no default

virtual — default behavior, optionally overridden:

public class Animal
{
    // Provides a default — derived classes may or may not override
    public virtual string Sound() => "...";
    public virtual string Describe() => $"I am a {GetType().Name}";
}

public class Dog : Animal
{
    public override string Sound() => "Woof"; // overrides
    // Describe() not overridden — uses base default
}

public class Cat : Animal
{
    public override string Sound() => "Meow";
    public override string Describe() => "I am a mysterious cat"; // custom
}

Animal[] animals = [new Dog(), new Cat(), new Animal()];
foreach (var a in animals)
    Console.WriteLine($"{a.Describe()}{a.Sound()}");
// I am a Dog — Woof
// I am a mysterious cat — Meow
// I am a Animal — ...

abstract — no default, must be implemented:

public abstract class Shape
{
    // No default — every concrete shape must provide its own Area
    public abstract double Area();
    public abstract double Perimeter();

    // Can mix virtual with abstract in the same class
    public virtual string Describe() =>
        $"{GetType().Name}: Area={Area():F2}, P={Perimeter():F2}";
}

public class Circle(double radius) : Shape
{
    public override double Area()      => Math.PI * radius * radius;
    public override double Perimeter() => 2 * Math.PI * radius;
}

public class Square(double side) : Shape
{
    public override double Area()      => side * side;
    public override double Perimeter() => 4 * side;
}

Shape[] shapes = [new Circle(5), new Square(4)];
foreach (var s in shapes)
    Console.WriteLine(s.Describe());
// Circle: Area=78.54, P=31.42
// Square: Area=16.00, P=16.00

When to use each:

↥ back to top

Q. How do you handle constructor inheritance in C#, and what is the role of the base keyword in constructor inheritance?

Constructors are not inherited in C#. Each class must define its own constructors. However, a derived class constructor can (and often must) call a base class constructor using base(...) to initialise inherited members.

If no base(...) is specified, the compiler automatically calls the base class's parameterless constructor. If no such constructor exists, it is a compile error.

public class Vehicle
{
    public string Brand { get; }
    public int Year { get; }

    // No parameterless constructor — derived class MUST call base(...)
    public Vehicle(string brand, int year)
    {
        Brand = brand;
        Year  = year;
    }
}

public class Car : Vehicle
{
    public int Doors { get; }

    // Must chain to Vehicle(string, int) via base(...)
    public Car(string brand, int year, int doors)
        : base(brand, year)             //  base constructor called first
    {
        Doors = doors;
    }

    // Convenience overload — chains through Car\'s own constructors
    public Car(string brand, int year) : this(brand, year, 4) { }
}

public class ElectricCar : Car
{
    public int RangeKm { get; }

    public ElectricCar(string brand, int year, int doors, int rangeKm)
        : base(brand, year, doors)      //  calls Car ’ Vehicle
    {
        RangeKm = rangeKm;
    }
}

var ec = new ElectricCar("Tesla", 2025, 4, 500);
Console.WriteLine($"{ec.Brand} {ec.Year}, {ec.Doors}d, {ec.RangeKm}km");
// Output: Tesla 2025, 4d, 500km

Execution order — always base first:

public class A { public A() => Console.WriteLine("A"); }
public class B : A { public B() => Console.WriteLine("B"); }
public class C : B { public C() => Console.WriteLine("C"); }

new C();
// Output:
// A    grandparent first
// B
// C    derived last

Primary constructors (C# 12) with base:

public class Person(string name) { public string Name { get; } = name; }
public class Employee(string name, string dept) : Person(name)
{
    public string Department { get; } = dept;
}

var e = new Employee("Pradeep", "Engineering");
Console.WriteLine($"{e.Name}{e.Department}");
// Output: Pradeep — Engineering
↥ back to top

Q. What is the difference between inheritance and composition in C# and when should you use each one?

Inheritance models an “is-a” relationship: the derived class is a specialised version of the base class.

Composition models a “has-a” relationship: a class contains one or more objects of other types to delegate work to them.

Aspect Inheritance Composition
Relationship “is-a” “has-a”
Coupling Tight — derived depends on base internals Loose — depends on a public interface/contract
Flexibility Base changes affect all derived classes Composed behavior can be swapped at runtime
Reuse Reuse via subclassing Reuse via delegation
Polymorphism Achieved via virtual/override Achieved via interface + injection
Preferred when True specialisation hierarchy Behaviors that vary independently

Inheritance (use when “is-a” is genuine):

public class Animal(string name)
{
    public string Name { get; } = name;
    public virtual string Sound() => "...";
}

public class Dog(string name) : Animal(name)       // Dog IS-A Animal …
{
    public override string Sound() => "Woof";
}

Composition (prefer for behavior reuse):

// Behaviors defined as interfaces
public interface ILogger    { void Log(string msg); }
public interface IEmailSender { void Send(string to, string body); }

// Implementations (can be swapped)
public class ConsoleLogger : ILogger
{
    public void Log(string msg) => Console.WriteLine($"[LOG] {msg}");
}

public class SmtpEmailSender : IEmailSender
{
    public void Send(string to, string body) =>
        Console.WriteLine($"[SMTP] ’ {to}: {body}");
}

// OrderService HAS-A logger and emailer — not inheriting from them
public class OrderService(ILogger logger, IEmailSender emailSender)
{
    public void PlaceOrder(string item, string customerEmail)
    {
        logger.Log($"Order placed: {item}");
        emailSender.Send(customerEmail, $"Your order for {item} is confirmed!");
    }
}

var service = new OrderService(new ConsoleLogger(), new SmtpEmailSender());
service.PlaceOrder("Laptop", "pradeep@example.com");
// [LOG] Order placed: Laptop
// [SMTP] ’ pradeep@example.com: Your order for Laptop is confirmed!

Swapping behavior at runtime (composition wins):

// For tests — swap to a no-op logger without changing OrderService
public class NullLogger : ILogger { public void Log(string msg) { } }

var testService = new OrderService(new NullLogger(), new SmtpEmailSender());

Rule: Prefer composition over inheritance (Effective Java principle applies equally in C#). Use inheritance only when the “is-a” relationship is stable and meaningful across the hierarchy.

↥ back to top

Q. Can you override private virtual methods?

No. A private method is not accessible in derived classes, so it cannot be overridden. The compiler will produce error CS0621 if you try to mark a private method as virtual.

public class Base
{
    //  Compile error CS0621: 'Base.DoWork()' cannot be declared virtual
    // because it is private
    // private virtual void DoWork() { }

    // To allow overriding, minimum access must be protected (or higher):
    protected virtual void DoWork() => Console.WriteLine("Base.DoWork");
}

public class Derived : Base
{
    protected override void DoWork() => Console.WriteLine("Derived.DoWork");
}

Why: private means “visible only in this class”. Since derived classes cannot see it, they have no way to provide an overriding implementation. The C# compiler enforces this at compile time.

What you can do instead — Template Method Pattern:

If you want a private step to be “customisable” while keeping it hidden, expose a protected virtual hook and call it from a private or public method:

public class DataProcessor
{
    // Public entry point — not overridable
    public void Process(string data)
    {
        Validate(data);
        Transform(data); // calls the protected virtual hook
        Save(data);
    }

    private static void Validate(string data)
    {
        if (string.IsNullOrEmpty(data))
            throw new ArgumentException("Data cannot be empty");
    }

    private static void Save(string data) =>
        Console.WriteLine($"Saved: {data}");

    // Protected virtual — derived classes customise only this step
    protected virtual void Transform(string data) =>
        Console.WriteLine($"Default transform: {data}");
}

public class UpperCaseProcessor : DataProcessor
{
    protected override void Transform(string data) =>
        Console.WriteLine($"Upper transform: {data.ToUpper()}");
}

new UpperCaseProcessor().Process("hello");
// Saved: hello
// Upper transform: HELLO
↥ back to top

Q. What does the keyword virtual mean in the method definition?

The virtual keyword marks a method as overridable — derived classes may provide their own implementation using the override keyword. Without virtual, the method is non-virtual and cannot be overridden (only hidden with new).

public class Shape
{
    // virtual — derived classes can replace this implementation
    public virtual double Area() => 0;

    // non-virtual — cannot be overridden, only hidden
    public string TypeName() => "Shape";
}

public class Circle(double radius) : Shape
{
    // override replaces the virtual method for Circle instances
    public override double Area() => Math.PI * radius * radius;
}

Shape s = new Circle(5);
Console.WriteLine(s.Area());     // Output: 78.54... (Circle.Area — override wins)
Console.WriteLine(s.TypeName()); // Output: Shape (non-virtual — base always called)

Key properties of virtual:

Sealing a virtual override:

public class Square(double side) : Shape
{
    // sealed prevents further overriding in classes that inherit from Square
    public sealed override double Area() => side * side;
}

// public class SpecialSquare : Square
// {
//     public override double Area() => 999; //  compile error — sealed
// }
↥ back to top

Q. What is a Virtual Method in C#?

A virtual method is a method declared with the virtual keyword that allows derived classes to override it with their own implementation. The correct version is selected at runtime based on the actual object type (dynamic dispatch).

public class Animal
{
    // Virtual method with a default implementation
    public virtual string MakeSound() => $"{GetType().Name} makes a generic sound";

    // Virtual property
    public virtual string Category => "Unknown";
}

public class Dog : Animal
{
    public override string MakeSound() => "Woof!";
    public override string Category    => "Mammal";
}

public class Eagle : Animal
{
    public override string MakeSound() => "Screech!";
    public override string Category    => "Bird";
}

public class Fish : Animal
{
    // Does NOT override — uses base default
}

// Dynamic dispatch — runtime picks the right MakeSound()
Animal[] animals = [new Dog(), new Eagle(), new Fish()];

foreach (var a in animals)
    Console.WriteLine($"{a.GetType().Name} [{a.Category}]: {a.MakeSound()}");

// Output:
// Dog [Mammal]: Woof!
// Eagle [Bird]: Screech!
// Fish [Unknown]: Fish makes a generic sound

Virtual methods in abstract classes (mix of abstract + virtual):

public abstract class Report
{
    // abstract — must be implemented (no default)
    public abstract string GenerateContent();

    // virtual — default header, can be customised
    public virtual string GenerateHeader() => $"=== Report ({DateTime.Today:d}) ===";

    // non-virtual — fixed footer, never changes
    public string GenerateFooter() => "=== End of Report ===";

    public void Print()
    {
        Console.WriteLine(GenerateHeader());
        Console.WriteLine(GenerateContent()); // dynamic dispatch
        Console.WriteLine(GenerateFooter());
    }
}

public class SalesReport : Report
{
    public override string GenerateContent() => "Sales: £50,000 this month";
    // Uses default header (virtual, not overridden)
}

public class AnnualReport : Report
{
    public override string GenerateContent() => "Annual Revenue: £600,000";
    public override string GenerateHeader()  => "=== Annual Report 2025 ===";
}

new SalesReport().Print();
// === Report (19/04/2026) ===
// Sales: £50,000 this month
// === End of Report ===

new AnnualReport().Print();
// === Annual Report 2025 ===
// Annual Revenue: £600,000
// === End of Report ===
↥ back to top

Q. Why a private virtual method cannot be overridden in C#?

A private virtual method is a contradiction — and the C# compiler rejects it with CS0621.

Two reasons:

1. Accessibility: private means the method is visible only within the declaring class. Derived classes cannot see private members — they cannot reference them, let alone override them.

2. Virtual dispatch requires visibility: For override to work, the derived class must be able to see the method signature and declare it with override. Since private blocks that visibility, override is impossible.

public class Base
{
    //  CS0621 — cannot be both private and virtual
    // private virtual void Compute() { }

    // … To be overridable, minimum access is protected
    protected virtual void Compute() => Console.WriteLine("Base.Compute");

    // … Private method can be called via a protected/public virtual hook
    private void InternalWork() => Console.WriteLine("Internal work");

    protected virtual void DoWork()
    {
        InternalWork(); // private helper — called from overridable method
        Compute();
    }
}

public class Derived : Base
{
    protected override void Compute() => Console.WriteLine("Derived.Compute");
    // Cannot access InternalWork — it\'s private to Base
}

Summary of which access modifiers can be combined with virtual:

Access modifier Can be virtual?
private No (CS0621)
protected … Yes
internal … Yes
protected internal … Yes
public … Yes
↥ back to top

Q. How do you override a method in C#?

To override a method:

  1. The base class method must be marked virtual, abstract, or override.
  2. The derived class method must use the override keyword.
  3. The signature (name, parameter types, return type) must match exactly.
public class Animal
{
    public virtual string Speak() => "...";               // virtual — can be overridden
    public virtual string Describe() => $"I am an Animal";
}

public class Dog : Animal
{
    public override string Speak() => "Woof!";             // overrides Animal.Speak
    // Describe() not overridden — Dog uses Animal\'s version
}

public class GoldenRetriever : Dog
{
    // Override again — must still match signature
    public override string Speak() => "Woof Woof!";
    public override string Describe() => "I am a Golden Retriever";
}

Animal a = new GoldenRetriever();
Console.WriteLine(a.Speak());    // Output: Woof Woof!   (runtime type decides)
Console.WriteLine(a.Describe()); // Output: I am a Golden Retriever

Calling the base implementation from an override:

public class TimestampLogger
{
    public virtual void Log(string message) =>
        Console.WriteLine($"[LOG] {message}");
}

public class PrefixLogger : TimestampLogger
{
    public override void Log(string message)
    {
        base.Log(message);             // call base first
        Console.WriteLine($"  at {DateTime.UtcNow:HH:mm:ss UTC}");
    }
}

new PrefixLogger().Log("App started");
// [LOG] App started
//   at 14:32:01 UTC

Preventing further override with sealed override:

public class SpecialDog : Dog
{
    // sealed — no class deriving from SpecialDog can override Speak again
    public sealed override string Speak() => "Bark Bark!";
}

Abstract method override (mandatory):

public abstract class Shape
{
    public abstract double Area(); // no body — must be overridden
}

public class Circle(double r) : Shape
{
    public override double Area() => Math.PI * r * r; // required
}
↥ back to top

Q. What is the difference between override and new keywords in C#?

Both override and new let a derived class define a method with the same name as a base class method, but they have fundamentally different behavior:

Aspect override new (hiding)
Base requirement Base method must be virtual/abstract/override Any base method
Polymorphism … Yes — runtime type decides No — reference type decides
Dispatch Dynamic (runtime) Static (compile-time)
LSP compliance (often breaks it)
Compiler warning if omitted Error Warning CS0108 (shadowing)
public class Animal
{
    public virtual string Sound()   => "...";    // virtual
    public string Category() => "Animal";         // non-virtual
}

public class Dog : Animal
{
    public override string Sound()   => "Woof";  // polymorphic
    public new string Category() => "Dog";        // hiding
}

// ---- Test via base reference ----
Animal a = new Dog();

// override: runtime type (Dog) decides
Console.WriteLine(a.Sound());    // Output: Woof    Dog.Sound

// new (hiding): reference type (Animal) decides
Console.WriteLine(a.Category()); // Output: Animal  Animal.Category (NOT Dog!)

// ---- Test via derived reference ----
Dog d = new Dog();
Console.WriteLine(d.Sound());    // Output: Woof
Console.WriteLine(d.Category()); // Output: Dog

Practical impact — hiding breaks polymorphism:

void Describe(Animal a)
{
    Console.WriteLine($"Sound: {a.Sound()}, Category: {a.Category()}");
}

Describe(new Dog());
// Sound: Woof        override works correctly (Dog\'s Sound)
// Category: Animal   hiding is wrong here (expected "Dog", got "Animal")

Rule: Use override for polymorphic behavior. Use new only for intentional version breaking (e.g., a base class added a method that conflicts with an existing derived method) — and document it clearly.

↥ back to top

Q. How does inheritance promote code reusability?

Inheritance promotes reusability by allowing derived classes to inherit and reuse all non-private members from the base class without rewriting them — while adding or customising only what differs.

1. Shared implementation — write once, reuse many times:

public class Vehicle
{
    public string Brand { get; }
    public int Year { get; }
    public string Registration { get; }

    public Vehicle(string brand, int year, string reg)
    {
        Brand = brand;
        Year  = year;
        Registration = reg;
    }

    // Shared behavior — ALL vehicles log the same way
    public void LogUsage(string action) =>
        Console.WriteLine($"[{Registration}] {Brand} ({Year}): {action}");

    public virtual string FuelType => "Unknown";
}

public class Car(string brand, int year, string reg) : Vehicle(brand, year, reg)
{
    public override string FuelType => "Petrol";
    public int Doors { get; init; } = 4;
}

public class ElectricCar(string brand, int year, string reg) : Vehicle(brand, year, reg)
{
    public override string FuelType => "Electric";
    public int RangeKm { get; init; }
}

// Both reuse LogUsage, Brand, Year, Registration without rewriting
var car = new Car("BMW", 2023, "AB12 CDE") { Doors = 2 };
var ev  = new ElectricCar("Tesla", 2025, "EV99 XYZ") { RangeKm = 500 };

car.LogUsage("started"); // [AB12 CDE] BMW (2023): started
ev.LogUsage("charged");  // [EV99 XYZ] Tesla (2025): charged

Console.WriteLine(car.FuelType); // Petrol
Console.WriteLine(ev.FuelType);  // Electric

2. Hierarchical reuse — build progressively:

public class Employee(string name, decimal salary)
{
    public string Name { get; } = name;
    protected decimal Salary { get; set; } = salary;

    public virtual decimal CalculatePay() => Salary;
    public override string ToString() => $"{Name}: {CalculatePay():C}";
}

public class Manager(string name, decimal salary, decimal bonus)
    : Employee(name, salary)
{
    private readonly decimal _bonus = bonus;
    public override decimal CalculatePay() => base.CalculatePay() + _bonus;
}

public class ContractEmployee(string name, decimal hourlyRate, int hours)
    : Employee(name, hourlyRate * hours)
{
    // CalculatePay() reused from Employee unchanged
}

Employee[] staff =
[
    new Employee("Alice", 4000m),
    new Manager("Bob", 4000m, 1000m),
    new ContractEmployee("Carol", 50m, 100),
];

foreach (var e in staff)
    Console.WriteLine(e); // uses reused ToString + polymorphic CalculatePay
// Alice: £4,000.00
// Bob: £5,000.00
// Carol: £5,000.00
↥ back to top

Q. What are the potential pitfalls of using inheritance?

Inheritance is powerful but easily misused. Common pitfalls:

1. Fragile base class problem — base changes break derived classes:

public class Collection
{
    private int _addCount = 0;

    public virtual void Add(string item) { _addCount++; /* logic */ }

    // Adding a new virtual method that calls Add internally
    public virtual void AddAll(string[] items)
    {
        foreach (var item in items) Add(item); // calls virtual Add
    }
}

public class InstrumentedCollection : Collection
{
    private int _count = 0;
    public override void Add(string item)    { _count++; base.Add(item); }
    public override void AddAll(string[] items) { _count += items.Length; base.AddAll(items); }
    // AddAll calls base.AddAll which calls virtual Add ’ _count incremented TWICE per item!
}

2. Tight coupling — derived depends on base internals:

// If Base changes internal logic, Derived silently breaks
public class Base
{
    protected int _value;
    public virtual void Set(int v) { _value = v * 2; } // secret: doubles internally
}

public class Derived : Base
{
    public override void Set(int v) { base.Set(v); Console.WriteLine(_value); }
    // Derived assumes _value == v, but gets v*2 — unexpected dependency
}

3. Violation of Liskov Substitution Principle (LSP):

public class Rectangle
{
    public virtual int Width  { get; set; }
    public virtual int Height { get; set; }
    public int Area() => Width * Height;
}

public class Square : Rectangle //  Square IS-NOT substitutable for Rectangle
{
    public override int Width  { set { base.Width  = value; base.Height = value; } }
    public override int Height { set { base.Height = value; base.Width  = value; } }
}

Rectangle r = new Square();
r.Width = 4; r.Height = 5;
Console.WriteLine(r.Area()); // Expected 20, got 25 — LSP violated!

4. Deep inheritance hierarchies — hard to understand and maintain:

// 6-level hierarchy — changing Animal ripples through everything
Animal  Vertebrate  Mammal  Carnivore  Feline  Cat

5. Inheritance for code reuse only (not “is-a”):

//  Stack should NOT inherit List just to reuse its storage
public class Stack<T> : List<T> // exposes Add, Remove, Insert — breaks Stack semantics

Mitigations:

↥ back to top

Q. How do you prevent a class from being inherited in C#?

Apply the sealed keyword to the class. A sealed class cannot be used as a base class — any attempt to inherit from it is a compile error (CS0509).

public sealed class DatabaseConnection
{
    private readonly string _connectionString;

    public DatabaseConnection(string connectionString)
    {
        if (string.IsNullOrWhiteSpace(connectionString))
            throw new ArgumentException("Connection string required");
        _connectionString = connectionString;
    }

    public void Open()  => Console.WriteLine($"Opening: {_connectionString}");
    public void Close() => Console.WriteLine("Connection closed");
}

//  CS0509: 'MyConnection' cannot derive from sealed type 'DatabaseConnection'
// public class MyConnection : DatabaseConnection { }

Sealing a specific override (not the whole class):

public class Animal
{
    public virtual string Sound() => "...";
}

public class Dog : Animal
{
    // sealed on the override — Dog can be inherited, but Sound() cannot be overridden further
    public sealed override string Sound() => "Woof";
}

public class GoldenRetriever : Dog
{
    //  CS0239: cannot override inherited member 'Dog.Sound()' because it is sealed
    // public override string Sound() => "Woof Woof";
}

Why seal a class?

Common examples of sealed classes in .NET:

↥ back to top

Q. What is explicit interface implementation in C# and when should it be used?

Explicit interface implementation allows a class to implement an interface member without exposing it as a regular public method. The member is only accessible through a reference of the interface type.

When to use it:

interface IArea
{
    double Calculate();
}

interface IPerimeter
{
    double Calculate();   // same name, different meaning
}

public class Rectangle : IArea, IPerimeter
{
    public double Width { get; init; }
    public double Height { get; init; }

    // ” Explicit implementation — only callable via the interface ”
    double IArea.Calculate()      => Width * Height;
    double IPerimeter.Calculate() => 2 * (Width + Height);

    // ” Optional convenience properties ”—————————————————————————
    public double Area      => ((IArea)this).Calculate();
    public double Perimeter => ((IPerimeter)this).Calculate();
}

var rect = new Rectangle { Width = 5, Height = 3 };

// Through the class directly — public Area/Perimeter properties
Console.WriteLine(rect.Area);       // 15
Console.WriteLine(rect.Perimeter);  // 16

// Through the interface references
IArea     ia = rect;
IPerimeter ip = rect;
Console.WriteLine(ia.Calculate());   // 15
Console.WriteLine(ip.Calculate());   // 16

// rect.Calculate()   compile error — not accessible directly

Explicit vs implicit implementation:

Aspect Implicit Explicit
Access modifier public None (interface access only)
Accessible via class reference Yes No — requires cast
Visible in IntelliSense Yes Only when typed as interface
Use case Normal implementation Disambiguation, hiding
↥ back to top

Q. What are default interface members in C# 8 and how are they used?

Default interface members (C# 8+) allow interfaces to provide a default implementation for a method. Implementing classes can optionally override them. This enables interface versioning without breaking existing implementations.

// ” 1. Basic default implementation ”—————————————————————————————
interface ILogger
{
    void Log(string message);

    // Default implementation — optional override in implementing class
    void LogError(string message) => Log($"[ERROR] {message}");
    void LogInfo(string message)  => Log($"[INFO]  {message}");
}

class ConsoleLogger : ILogger
{
    // Only required method implemented; defaults inherited
    public void Log(string message) => Console.WriteLine(message);
}

class FileLogger : ILogger
{
    public void Log(string message) => File.AppendAllText("app.log", message + "\n");

    // Override the default for a custom format
    public void LogError(string message) => Log($"CRITICAL >> {message}");
}

ILogger console = new ConsoleLogger();
console.LogInfo("Server started");   // [INFO]  Server started
console.LogError("Disk full");       // [ERROR] Disk full

ILogger file = new FileLogger();
file.LogError("DB timeout");         // CRITICAL >> DB timeout (overridden)

// ” 2. Interface evolution — adding a method without breaking callers
interface ICache
{
    object? Get(string key);
    void Set(string key, object value);

    // Added in v2 — existing implementors are not broken
    bool TryGet(string key, out object? value)
    {
        value = Get(key);
        return value is not null;
    }
}

// ” 3. Static abstract members (C# 11) — for generic math
interface IAddable<T> where T : IAddable<T>
{
    static abstract T operator +(T left, T right);
    static abstract T Zero { get; }
}

record struct Vector2D(double X, double Y) : IAddable<Vector2D>
{
    public static Vector2D operator +(Vector2D a, Vector2D b) => new(a.X + b.X, a.Y + b.Y);
    public static Vector2D Zero => new(0, 0);
}

T Sum<T>(IEnumerable<T> items) where T : IAddable<T>
    => items.Aggregate(T.Zero, (acc, x) => acc + x);

Console.WriteLine(Sum(new[] { new Vector2D(1, 2), new Vector2D(3, 4) })); // Vector2D { X = 4, Y = 6 }

Key rules:

↥ back to top

# 5. COLLECTIONS


Q. What are collections in C# and why are they used?

Collections are data structures that store, manage, and manipulate groups of related objects. Unlike arrays (fixed size), most .NET collections resize dynamically and provide rich APIs for searching, sorting, filtering, and thread-safe access.

Main collection namespaces:

Commonly used collections:

Collection Key Feature
List<T> Ordered, resizable, index access
Dictionary<K,V> Key-value pairs, O(1) lookup
HashSet<T> Unique elements, fast membership check
Queue<T> FIFO order
Stack<T> LIFO order
LinkedList<T> Doubly linked, efficient insert/remove
SortedDictionary<K,V> Key-sorted dictionary
ConcurrentDictionary<K,V> Thread-safe key-value
ImmutableList<T> Immutable, safe to share across threads
FrozenDictionary<K,V> Optimized read-only dictionary (.NET 8+)

**Example — List:**

var names = new List<string> { "Alice", "Bob", "Carol" };
names.Add("Dave");
names.Remove("Bob");

foreach (var name in names)
    Console.WriteLine(name);
// Output: Alice Carol Dave

Example — Dictionary<K,V>:

var scores = new Dictionary<string, int>
{
    ["Alice"] = 95,
    ["Bob"]   = 87,
};

scores["Carol"] = 92;

if (scores.TryGetValue("Alice", out int score))
    Console.WriteLine($"Alice: {score}"); // Alice: 95

Example — FrozenDictionary (.NET 8+) for read-heavy scenarios:

using System.Collections.Frozen;

var lookup = new Dictionary<string, int>
{
    ["red"]   = 0xFF0000,
    ["green"] = 0x00FF00,
    ["blue"]  = 0x0000FF,
}.ToFrozenDictionary();

Console.WriteLine(lookup["red"].ToString("X")); // FF0000

Example — Collection expressions (C# 12):

List<int> numbers = [1, 2, 3, 4, 5];
int[] arr = [.. numbers, 6, 7];  // spread operator
↥ back to top

Q. What is the difference between an array and a collection in C#?

Feature Array (T[]) Collection (e.g., List<T>)
Size Fixed at creation Dynamic (grows/shrinks)
Type safety Always typed (int[]) Generic collections are typed
Performance Fastest element access (O(1)) Slightly more overhead
Insertion/deletion Not supported (fixed) Efficient (Add, Remove)
Interface IEnumerable, IList Richer API (LINQ, sort, search)
Null safety Length is always known Count property
// Array — fixed size, fast direct access
int[] scores = [10, 20, 30, 40, 50];
Console.WriteLine(scores[2]); // 30
// scores.Add(60); //  arrays have no Add — fixed size

// List<T> — dynamic size, full API
var names = new List<string> { "Alice", "Bob" };
names.Add("Carol");
names.Remove("Bob");
Console.WriteLine(names.Count); // 2

// Modern collection expressions (C# 12)
List<int> nums = [1, 2, 3];
int[] arr     = [.. nums, 4, 5]; // spread into array

When to use arrays:

When to use collections:

↥ back to top

Q. What are the different types of collections available in C#?

.NET provides collections across several namespaces:

System.Collections.Generic (strongly typed — preferred):

Type Description
List<T> Ordered, resizable, index access
Dictionary<K,V> Key-value, O(1) lookup
HashSet<T> Unique elements, fast membership
Queue<T> FIFO
Stack<T> LIFO
LinkedList<T> Doubly linked list
SortedList<K,V> Key-sorted, backed by array
SortedDictionary<K,V> Key-sorted, backed by BST
SortedSet<T> Unique sorted elements

System.Collections.Concurrent (thread-safe):

Type Description
ConcurrentDictionary<K,V> Thread-safe dictionary
ConcurrentQueue<T> Thread-safe FIFO
ConcurrentStack<T> Thread-safe LIFO
ConcurrentBag<T> Unordered, thread-safe bag
BlockingCollection<T> Bounded producer-consumer

System.Collections.Immutable (.NET 5+):

ImmutableList<T>, ImmutableDictionary<K,V>, ImmutableArray<T>, etc.

System.Collections.Frozen (.NET 8+):

FrozenDictionary<K,V>, FrozenSet<T> — read-optimized, ideal for static lookup tables.

// Generic
var list  = new List<int> { 1, 2, 3 };
var dict  = new Dictionary<string, int> { ["a"] = 1 };
var set   = new HashSet<string> { "x", "y", "z" };

// Frozen (read-only optimized — .NET 8+)
using System.Collections.Frozen;
var frozen = new Dictionary<string, int> { ["one"] = 1, ["two"] = 2 }
    .ToFrozenDictionary();
Console.WriteLine(frozen["one"]); // 1

// Immutable
using System.Collections.Immutable;
var immList = ImmutableList.Create(1, 2, 3);
var added   = immList.Add(4); // returns NEW list
Console.WriteLine(immList.Count); // 3 (original unchanged)
Console.WriteLine(added.Count);   // 4
↥ back to top

Q. What are Concurrent Collection Classes?

Concurrent collection classes in the System.Collections.Concurrent namespace are thread-safe without requiring external locks. They use fine-grained locking or lock-free algorithms for better performance in multi-threaded scenarios.

Class Description Thread-safe operation
ConcurrentDictionary<K,V> Thread-safe key-value store TryAdd, TryUpdate, GetOrAdd
ConcurrentQueue<T> Thread-safe FIFO Enqueue, TryDequeue
ConcurrentStack<T> Thread-safe LIFO Push, TryPop
ConcurrentBag<T> Unordered thread-safe collection Add, TryTake
BlockingCollection<T> Bounded producer-consumer Add, Take (blocks when empty/full)
// ConcurrentDictionary — safe multi-threaded add/update
var cache = new ConcurrentDictionary<string, int>();

// Multiple threads can add/read simultaneously
await Task.WhenAll(Enumerable.Range(0, 10).Select(i =>
    Task.Run(() => cache.TryAdd($"key{i}", i))));

Console.WriteLine(cache.Count); // 10 (always correct, no race conditions)

// GetOrAdd — atomic: get existing or add new
int val = cache.GetOrAdd("key5", k => 99);
Console.WriteLine(val); // 5 (already existed)

// AddOrUpdate — atomic increment
cache.AddOrUpdate("counter", 1, (_, existing) => existing + 1);

// ConcurrentQueue — thread-safe producer/consumer
var queue = new ConcurrentQueue<string>();
queue.Enqueue("task1");
queue.Enqueue("task2");

if (queue.TryDequeue(out string? item))
    Console.WriteLine(item); // task1

// BlockingCollection — bounded buffer (blocks producer when full)
var buffer = new BlockingCollection<int>(boundedCapacity: 5);

var producer = Task.Run(() =>
{
    for (int i = 0; i < 10; i++)
    {
        buffer.Add(i); // blocks when buffer is full
        Console.WriteLine($"Produced: {i}");
    }
    buffer.CompleteAdding();
});

var consumer = Task.Run(() =>
{
    foreach (var n in buffer.GetConsumingEnumerable())
        Console.WriteLine($"Consumed: {n}");
});

await Task.WhenAll(producer, consumer);
↥ back to top

Q. Explain Hashtable in C#?

Hashtable is a non-generic key-value collection in System.Collections that stores objects as object/object pairs. It was the primary associative collection before generics were introduced in .NET 2.0. In modern .NET, Dictionary<K,V> is always preferred.

using System.Collections;

var table = new Hashtable();
table["name"]    = "Pradeep";
table["age"]     = 30;
table["active"]  = true;

Console.WriteLine(table["name"]); // Pradeep

// Iteration — key order is not guaranteed
foreach (DictionaryEntry entry in table)
    Console.WriteLine($"{entry.Key}: {entry.Value}");

// Check existence
Console.WriteLine(table.ContainsKey("age"));   // True
Console.WriteLine(table.ContainsValue(30));    // True

table.Remove("active");
Console.WriteLine(table.Count); // 2

Why Dictionary<K,V> is better than Hashtable:

Feature Hashtable Dictionary<K,V>
Type safety object — boxing/unboxing … Strongly typed
Performance Slower (boxing overhead) Faster (no boxing for value types)
Null keys Not allowed Not allowed (same)
Thread safety Thread-safe for reads only Use ConcurrentDictionary for writes
Recommended Legacy code only … Always prefer
// Modern equivalent — Dictionary<K,V>
var dict = new Dictionary<string, object?>
{
    ["name"]   = "Pradeep",
    ["age"]    = 30,
    ["active"] = true,
};

if (dict.TryGetValue("age", out object? age))
    Console.WriteLine((int)age); // 30
↥ back to top

Q. What is IEnumerable<> in C#?

IEnumerable<T> is the fundamental interface for all enumerable sequences in .NET. It exposes a single method GetEnumerator() that allows iterating through a collection with foreach.

// IEnumerable<T> definition (simplified)
public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

Key characteristics:

// Any type implementing IEnumerable<T> works with foreach + LINQ
IEnumerable<int> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

var evens = numbers
    .Where(n => n % 2 == 0)   // deferred — not executed yet
    .Select(n => n * n);       // deferred — not executed yet

foreach (var n in evens)       // execution happens here
    Console.Write($"{n} ");    // 4 16 36 64 100

// Custom iterator with yield return
IEnumerable<int> FibonacciSequence(int count)
{
    int a = 0, b = 1;
    for (int i = 0; i < count; i++)
    {
        yield return a;
        (a, b) = (b, a + b);
    }
}

foreach (var f in FibonacciSequence(8))
    Console.Write($"{f} "); // 0 1 1 2 3 5 8 13

// IAsyncEnumerable<T> — async streaming (C# 8+)
async IAsyncEnumerable<int> GetDataAsync()
{
    for (int i = 0; i < 5; i++)
    {
        await Task.Delay(10); // simulate async I/O
        yield return i;
    }
}

await foreach (var item in GetDataAsync())
    Console.Write($"{item} "); // 0 1 2 3 4

When to use IEnumerable<T> as a parameter/return type:

↥ back to top

Q. What is the difference between a BlockingCollection and a ConcurrentQueue or ConcurrentStack? In which scenarios would you choose to use the BlockingCollection, and why?

Feature ConcurrentQueue<T> / ConcurrentStack<T> BlockingCollection<T>
Blocking Non-blocking (TryDequeue returns false) … Blocks consumer until item available
Bounded capacity Unlimited … Optional upper bound
Completion signal No CompleteAdding() signals end-of-stream
Ordering Queue=FIFO, Stack=LIFO Wraps any IProducerConsumerCollection<T>
Use case Fire-and-forget, polling Classic producer-consumer pipelines

ConcurrentQueue — non-blocking, just thread-safe:

var queue = new ConcurrentQueue<int>();
queue.Enqueue(1);

// Returns false immediately if empty — caller must handle
if (!queue.TryDequeue(out int item))
    Console.WriteLine("Queue empty — try again later");

BlockingCollection — blocks consumer, supports bounded buffer and completion:

// Bounded buffer: producer blocks when capacity reached
var pipeline = new BlockingCollection<string>(boundedCapacity: 3);

var producer = Task.Run(async () =>
{
    string[] items = ["A", "B", "C", "D", "E"];
    foreach (var item in items)
    {
        pipeline.Add(item); // blocks if buffer is full
        Console.WriteLine($"Produced: {item}");
        await Task.Delay(50);
    }
    pipeline.CompleteAdding(); // signals no more items
});

var consumer = Task.Run(() =>
{
    // GetConsumingEnumerable blocks when empty, exits when CompleteAdding() called
    foreach (var item in pipeline.GetConsumingEnumerable())
        Console.WriteLine($"Consumed: {item}");
});

await Task.WhenAll(producer, consumer);

BlockingCollection wrapping a ConcurrentStack (LIFO behavior):

// Default is ConcurrentQueue (FIFO). Override with ConcurrentStack for LIFO:
var lifoCollection = new BlockingCollection<int>(new ConcurrentStack<int>(), boundedCapacity: 10);

Choose BlockingCollection when:

↥ back to top

Q. Explain the difference between IQueryable, ICollection, IList & IDictionary interfaces?

Interface Namespace Key purpose Adds over parent
IEnumerable<T> System.Collections.Generic Forward-only iteration (base)
ICollection<T> System.Collections.Generic Count + Add/Remove Count, Add, Remove, Contains
IList<T> System.Collections.Generic Index access this[index], Insert, RemoveAt
IDictionary<K,V> System.Collections.Generic Key-value mapping this[key], Keys, Values, TryGetValue
IQueryable<T> System.Linq Remote/deferred queries Expression, Provider — translates to SQL/etc.
// ICollection<T> — knows its count, can add/remove
ICollection<string> col = new List<string> { "a", "b" };
col.Add("c");
Console.WriteLine(col.Count); // 3

// IList<T> — index access + ordered insertion
IList<int> list = new List<int> { 10, 20, 30 };
Console.WriteLine(list[1]); // 20
list.Insert(1, 15);         // [10, 15, 20, 30]
list.RemoveAt(0);            // [15, 20, 30]

// IDictionary<K,V> — key-value access
IDictionary<string, int> dict = new Dictionary<string, int>
{
    ["apple"] = 3,
    ["banana"] = 5,
};
dict["cherry"] = 2;
Console.WriteLine(dict["banana"]); // 5

if (dict.TryGetValue("apple", out int count))
    Console.WriteLine($"apple: {count}"); // apple: 3

// IQueryable<T> — translates to SQL via EF Core
// (requires a DbContext)
// IQueryable<Product> query = dbContext.Products
//     .Where(p => p.Price > 100)   // translated to SQL WHERE clause
//     .OrderBy(p => p.Name);       // translated to SQL ORDER BY
// var results = await query.ToListAsync(); // SQL executes here

IQueryable<T> vs IEnumerable<T> key distinction:

↥ back to top

Q. What is difference between Hashtable and Dictionary?

Feature Hashtable Dictionary<K,V>
Namespace System.Collections System.Collections.Generic
Type safety Non-generic (object) … Generic — strongly typed
Performance Slower (boxing for value types) Faster (no boxing)
Null key Not allowed Not allowed
Null value … Allowed … Allowed
Thread safety Thread-safe for multiple readers Not thread-safe (use ConcurrentDictionary)
Ordering Not guaranteed Not guaranteed (insertion order in .NET 5+)
Introduced .NET 1.0 .NET 2.0 (generics era)
// Hashtable — non-generic, stores object/object
var ht = new Hashtable();
ht["name"] = "Pradeep"; // boxing if value type
ht[1]      = 42;
Console.WriteLine((string)ht["name"]!); // requires cast

// Dictionary<K,V> — generic, type-safe, faster
var dict = new Dictionary<string, int>
{
    ["apples"]  = 5,
    ["bananas"] = 3,
};

dict["cherries"] = 8;

if (dict.TryGetValue("apples", out int qty))
    Console.WriteLine($"Apples: {qty}"); // Apples: 5

// Iterate
foreach (var (key, value) in dict)
    Console.WriteLine($"{key}: {value}");

// Thread-safe alternative
var safe = new System.Collections.Concurrent.ConcurrentDictionary<string, int>();
safe.TryAdd("x", 1);

Rule: Always use Dictionary<K,V> in new code. Use Hashtable only when maintaining legacy code.

↥ back to top

Q. What is difference between SortedList and SortedDictionary in C#?

Both maintain key-value pairs sorted by key, but differ in their internal data structure and performance characteristics.

Feature SortedList<K,V> SortedDictionary<K,V>
Internal structure Two parallel arrays (keys + values) Red-black BST
Memory Lower (arrays are compact) Higher (BST nodes have overhead)
Lookup by index Keys[i], Values[i] Not supported
Insert / Remove O(n) — shifts array O(log n) — BST rebalance
Lookup by key O(log n) binary search O(log n) BST traversal
Best for Read-heavy, sorted enumeration Frequent insert/delete
// SortedList<K,V> — array-backed, index access
var sortedList = new SortedList<string, int>
{
    ["banana"] = 3,
    ["apple"]  = 5,
    ["cherry"] = 2,
};

// Keys are always sorted
foreach (var (k, v) in sortedList)
    Console.WriteLine($"{k}: {v}");
// apple: 5
// banana: 3
// cherry: 2

// Index access (unique to SortedList)
Console.WriteLine(sortedList.Keys[0]);   // apple
Console.WriteLine(sortedList.Values[0]); // 5

// SortedDictionary<K,V> — BST-backed, faster insert/delete
var sortedDict = new SortedDictionary<string, int>
{
    ["banana"] = 3,
    ["apple"]  = 5,
    ["cherry"] = 2,
};

sortedDict.Add("date", 7); // O(log n) — faster than SortedList for frequent inserts

foreach (var (k, v) in sortedDict)
    Console.WriteLine($"{k}: {v}");
// apple: 5, banana: 3, cherry: 2, date: 7 (sorted)

Decision guide:

↥ back to top

Q. What is the difference between Array and ArrayList?

Feature Array (T[]) ArrayList
Type safety … Strongly typed Stores object — no type safety
Size Fixed Dynamic
Performance Fast — no boxing for value types Slower — boxing/unboxing for value types
Namespace Built-in System.Collections
Generics N/A Non-generic — superseded by List<T>
LINQ support … (via cast to IEnumerable)
// Array — fixed size, typed
int[] arr = new int[3] { 1, 2, 3 };
arr[0] = 10;
Console.WriteLine(arr.Length); // 3
// arr[3] = 4; //  IndexOutOfRangeException

// ArrayList — dynamic, but loses type safety
var al = new System.Collections.ArrayList();
al.Add(1);        // boxing int ’ object
al.Add("hello");  // mixes types — no compile error!
al.Add(3.14);

foreach (object item in al)
    Console.WriteLine(item); // 1 / hello / 3.14

//  Runtime error possible:
// int x = (int)al[1]; // InvalidCastException — "hello" is not int

// … Modern replacement — List<T>
var list = new List<int> { 1, 2, 3 };
list.Add(4);
Console.WriteLine(list.Count); // 4

Rule: Never use ArrayList in new code. Use List<T> — it is type-safe, faster (no boxing), and has a richer API.

↥ back to top

Q. Whose performance is better array or arraylist?

Array (T[]) is significantly faster than ArrayList for value types because:

  1. No boxing — arrays store value types directly; ArrayList boxes every value type into object (heap allocation per element).
  2. No casting — array element access is typed; ArrayList requires a cast on retrieval.
  3. Better cache locality — typed arrays are contiguous memory; ArrayList elements are object references scattered on the heap.
using System.Diagnostics;

const int N = 1_000_000;

// Array benchmark
var sw = Stopwatch.StartNew();
int[] intArray = new int[N];
for (int i = 0; i < N; i++) intArray[i] = i;
long sum1 = 0;
foreach (int x in intArray) sum1 += x;
sw.Stop();
Console.WriteLine($"Array:     {sw.ElapsedMilliseconds}ms, Sum={sum1}");

// ArrayList benchmark
sw.Restart();
var al = new System.Collections.ArrayList(N);
for (int i = 0; i < N; i++) al.Add(i);         // boxing!
long sum2 = 0;
foreach (object x in al) sum2 += (int)x;        // unboxing!
sw.Stop();
Console.WriteLine($"ArrayList: {sw.ElapsedMilliseconds}ms, Sum={sum2}");

// List<int> — same speed as array (no boxing)
sw.Restart();
var list = new List<int>(N);
for (int i = 0; i < N; i++) list.Add(i);
long sum3 = 0;
foreach (int x in list) sum3 += x;
sw.Stop();
Console.WriteLine($"List<int>: {sw.ElapsedMilliseconds}ms, Sum={sum3}");
// Typical: Array  List<int> >> ArrayList

Performance ranking (value types): T[] List<T> » ArrayList

For reference types (classes), the boxing penalty disappears, so the gap narrows — but List<T> is still preferred for type safety.

↥ back to top

Q. What class is underneath the Sorted List class?

SortedList<K,V> is backed internally by two parallel arrays: one for keys and one for values. It is not backed by a separate public class — it manages the arrays directly.

The internal implementation uses:

var sl = new SortedList<string, int>
{
    ["banana"] = 2,
    ["apple"]  = 1,
    ["cherry"] = 3,
};

// Internally, after insertion:
// keys:   ["apple", "banana", "cherry"]    sorted array
// values: [1,       2,        3]            parallel array

// You can directly access the underlying key/value collections
IList<string> keys   = sl.Keys;    // IList<TKey> view of the key array
IList<int>    values = sl.Values;  // IList<TValue> view of the value array

Console.WriteLine(sl.Keys[0]);   // apple   (index access — unique to SortedList)
Console.WriteLine(sl.Values[0]); // 1

// Index of a key
int idx = sl.IndexOfKey("banana"); // 1 (binary search on the key array)
Console.WriteLine(sl.Keys[idx]);   // banana

Consequence of the array backing:

↥ back to top

Q. IEnumerable vs List - What to Use? How do they work?

IEnumerable<T> — the minimal interface for any forward-only sequence. Provides GetEnumerator() only. May be lazy (deferred execution).

List<T> — a concrete, in-memory, ordered, resizable collection implementing IList<T>, ICollection<T>, and IEnumerable<T>. All items are materialised in memory immediately.

Aspect IEnumerable<T> List<T>
Memory Lazy — items produced on demand Eager — all items in memory
Execution Deferred (LINQ queries) Immediate
Count / Length Not available (enumerate to count) … O(1) .Count
Index access   list[i]
Mutation   Add, Remove, Sort
Re-enumeration May re-execute the query … Safe — always same data
Best for Method parameters (widest compatibility) In-memory data management
// IEnumerable<T> — lazy, deferred
IEnumerable<int> LazySquares(int n)
{
    for (int i = 1; i <= n; i++)
    {
        Console.WriteLine($"Computing {i}");
        yield return i * i;
    }
}

var seq = LazySquares(5); // nothing computed yet
var first = seq.First();   // computes 1 only, stops
Console.WriteLine(first);  // 1

// List<T> — eager, in-memory
List<int> squares = LazySquares(5).ToList(); // ALL 5 computed immediately
Console.WriteLine(squares[2]); // 9 (O(1) index access)
squares.Add(36);
squares.Sort();

// Use IEnumerable<T> as parameter type for maximum flexibility:
void PrintAll(IEnumerable<int> items) // accepts List, array, query, etc.
{
    foreach (var item in items)
        Console.Write($"{item} ");
}

PrintAll(squares);             // works
PrintAll([1, 2, 3]);           // works (array)
PrintAll(Enumerable.Range(1, 3)); // works (query)

Decision:

↥ back to top

Q. When to use ArrayList over array[] in C#?

Answer: Almost never in modern .NET. ArrayList was introduced in .NET 1.0 before generics existed. Since .NET 2.0, List<T> supersedes it completely.

The only remaining legitimate use of ArrayList is when maintaining legacy code that predates generics (.NET 1.x era) and cannot be refactored.

Scenario Recommendation
New code — heterogeneous types Use List<object> or a discriminated union/interface
New code — homogeneous types Use List<T> or T[]
Interop with very old APIs expecting ArrayList Use ArrayList only at the boundary
Legacy maintenance Keep ArrayList, refactor when possible
//  Old approach — ArrayList loses type safety
var al = new System.Collections.ArrayList();
al.Add(42);       // boxing
al.Add("hello");  // mixed types — no compile error
int x = (int)al[0]; // unboxing — runtime error if wrong type

// … Modern replacement
var list = new List<int> { 42, 99 };
list.Add(100); // type-safe, no boxing

// For truly heterogeneous data, use object or an interface:
var mixed = new List<object> { 42, "hello", 3.14, true };

// Or better yet, a discriminated union via a sealed hierarchy / oneOf:
sealed record IntValue(int Value);
sealed record StringValue(string Value);
var typed = new List<object> { new IntValue(42), new StringValue("hi") };
↥ back to top

Q. What are the differences between IEnumerable and IQueryable?

Feature IEnumerable<T> IQueryable<T>
Execution location In-memory (C# code) Remote (SQL, OData, etc.)
Query translation No — LINQ methods run as C# delegates Yes — expression trees translated to SQL
Data retrieval Pulls all data from source first Sends only necessary query to the data source
Provider None (local iteration) Requires a QueryProvider (e.g., EF Core)
Filtering After fetching (expensive for large data) At the source (efficient — WHERE in SQL)
Inherits from IEnumerable IEnumerable<T> + IQueryable
Use case In-memory collections (List, Array) ORM queries (EF Core, LINQ to SQL)
var products = new List<Product>
{
    new("Laptop",  1200m),
    new("Mouse",   25m),
    new("Monitor", 400m),
    new("Keyboard",75m),
};

// IEnumerable<T> — filters IN MEMORY (all 4 rows loaded first)
IEnumerable<Product> expensiveEnum = products
    .Where(p => p.Price > 100); // C# delegate, runs in-memory

// IQueryable<T> — with EF Core, WHERE translated to SQL (only matching rows fetched)
// IQueryable<Product> expensiveQuery = dbContext.Products
//     .Where(p => p.Price > 100); // becomes: SELECT * FROM Products WHERE Price > 100
// var result = await expensiveQuery.ToListAsync(); // SQL executes here

// Mixing: IQueryable ’ AsEnumerable() forces in-memory from that point
// dbContext.Products
//     .Where(p => p.Price > 100)   // SQL
//     .AsEnumerable()              // switch to in-memory
//     .Where(p => p.Name.Contains("a")); // C# in-memory filter

record Product(string Name, decimal Price);

Key rule: Use IQueryable<T> when the data lives in a remote store (database). Use IEnumerable<T> for in-memory data. Never filter a large database table with IEnumerable — it loads every row into memory before filtering.

↥ back to top

Q. How to sort the generic SortedList in the descending order?

SortedList<K,V> always keeps keys in ascending order by default. To sort in descending order, pass a custom IComparer<K> that reverses the comparison.

// Ascending (default)
var ascending = new SortedList<int, string>
{
    [3] = "three",
    [1] = "one",
    [2] = "two",
};

foreach (var (k, v) in ascending)
    Console.WriteLine($"{k}: {v}"); // 1 / 2 / 3

// Descending — use Comparer<T>.Create with reversed comparison
var descending = new SortedList<int, string>(
    Comparer<int>.Create((a, b) => b.CompareTo(a))); // reverse!

descending[3] = "three";
descending[1] = "one";
descending[2] = "two";

foreach (var (k, v) in descending)
    Console.WriteLine($"{k}: {v}"); // 3 / 2 / 1

// For strings — descending alphabetically
var names = new SortedList<string, int>(
    StringComparer.OrdinalIgnoreCase, // case-insensitive
    Comparer<string>.Create((a, b) => string.Compare(b, a, StringComparison.OrdinalIgnoreCase)));

// Simpler: collect into a List and sort descending with LINQ
var sl = new SortedList<int, string> { [1] = "a", [3] = "c", [2] = "b" };

var sortedDesc = sl.OrderByDescending(kv => kv.Key).ToList();
foreach (var (k, v) in sortedDesc)
    Console.WriteLine($"{k}: {v}"); // 3 / 2 / 1
↥ back to top

Q. What is a HashSet and when would you use it?

HashSet<T> is an unordered collection of unique elements backed by a hash table. It provides O(1) average-case operations for Add, Remove, and Contains.

Use it when:

// Basic usage
var fruits = new HashSet<string> { "apple", "banana", "cherry" };
fruits.Add("apple");    //  duplicate ignored — returns false
fruits.Add("date");     // … added

Console.WriteLine(fruits.Contains("banana")); // True  — O(1)
Console.WriteLine(fruits.Count);              // 4

// Set operations
var a = new HashSet<int> { 1, 2, 3, 4, 5 };
var b = new HashSet<int> { 3, 4, 5, 6, 7 };

// Union — all elements from both
var union = new HashSet<int>(a);
union.UnionWith(b);
Console.WriteLine(string.Join(",", union)); // 1,2,3,4,5,6,7

// Intersection — elements in both
var intersection = new HashSet<int>(a);
intersection.IntersectWith(b);
Console.WriteLine(string.Join(",", intersection)); // 3,4,5

// Difference — elements in a but not b
var diff = new HashSet<int>(a);
diff.ExceptWith(b);
Console.WriteLine(string.Join(",", diff)); // 1,2

// Remove duplicates from a list — common pattern
var withDuplicates = new List<int> { 1, 2, 2, 3, 3, 3, 4 };
var unique = new HashSet<int>(withDuplicates);
Console.WriteLine(string.Join(",", unique)); // 1,2,3,4

// Frozen variant (.NET 8+) — for read-only lookup tables
using System.Collections.Frozen;
FrozenSet<string> reserved = FrozenSet.ToFrozenSet(
    new[] { "class", "void", "public", "private" });
Console.WriteLine(reserved.Contains("void")); // True
↥ back to top

Q. How is LinkedList used in C#?

LinkedList<T> is a doubly linked list where each node (LinkedListNode<T>) holds a value and references to the previous and next nodes. It excels at O(1) insertion and removal at any position when you already have a reference to the node.

var ll = new LinkedList<string>();

// Add to end / beginning
ll.AddLast("B");
ll.AddFirst("A");
ll.AddLast("C");
// List: A <-> B <-> C

Console.WriteLine(string.Join(" -> ", ll)); // A -> B -> C

// Find a node and insert before/after it
var nodeB = ll.Find("B")!;
ll.AddBefore(nodeB, "A.5");
ll.AddAfter(nodeB, "B.5");
// List: A <-> A.5 <-> B <-> B.5 <-> C

Console.WriteLine(string.Join(" -> ", ll)); // A -> A.5 -> B -> B.5 -> C

// Remove by value or node
ll.Remove("A.5");
ll.Remove(nodeB); // O(1) — already have the node reference
Console.WriteLine(string.Join(" -> ", ll)); // A -> B.5 -> C

// Traverse forward and backward
var node = ll.First;
while (node is not null)
{
    Console.Write($"{node.Value} ");
    node = node.Next;
}
// A B.5 C

node = ll.Last;
while (node is not null)
{
    Console.Write($"{node.Value} ");
    node = node.Previous;
}
// C B.5 A

When to use LinkedList<T>:

When NOT to use:

↥ back to top

Q. What is difference between Stack and Heap?

In .NET, stack and heap are the two primary memory regions used during program execution.

Aspect Stack Heap
Contents Value types, method call frames, local variable references Reference type objects, boxed values
Allocation Automatic — push on call, pop on return Managed by garbage collector
Access speed Very fast (contiguous, CPU-cached) Slower (random access, GC overhead)
Size Limited (~1 MB per thread) Large (limited by available RAM)
Lifetime Limited to the scope/method Until GC collects it
Thread Each thread has its own stack Shared across all threads
Fragmentation None (LIFO) Can fragment over time
void Method()
{
    int x = 42;              // value type — stored on stack
    double y = 3.14;         // value type — stored on stack

    var list = new List<int>(); // reference type — object on heap
                                // `list` variable (reference) on stack

    var point = new Point(1, 2); // struct — on stack (value type)
    var obj   = new object();    // class  — on heap (reference type)
}

public record struct Point(int X, int Y); // struct = value type

Visual:

STACK (per thread)            HEAP (shared)
””—————————————————           ””————————————————————————
” x = 42          ”           ” List<int> object        ”
” y = 3.14        ”           ”  [_items array, Count]  ”
” list ”————————————————————–”                         ”
” point = (1,2)   ”           ” object {}               ”
” obj ”——————————————————————–                         ”
”””—————————————————           ”””————————————————————————
↥ back to top

Q. Are string allocated on stack or heap?

Strings are reference types — their content is allocated on the heap. The variable holding the reference lives on the stack (or inline in an object), but the actual string data is on the heap.

However, string has special behavior:

string s1 = "hello"; // literal — interned on heap, reference on stack
string s2 = "hello"; // same interned object — s1 and s2 point to same heap address

Console.WriteLine(object.ReferenceEquals(s1, s2)); // True (interned)

string s3 = new string("hello".ToCharArray()); // forced new object — NOT interned
Console.WriteLine(object.ReferenceEquals(s1, s3)); // False

// Manual interning
string s4 = string.Intern(s3); // returns the interned version
Console.WriteLine(object.ReferenceEquals(s1, s4)); // True

// String immutability — each operation allocates a new heap object
string original = "Hello";
string modified = original + " World"; // new heap string
Console.WriteLine(object.ReferenceEquals(original, modified)); // False

// For many string operations, use StringBuilder (single buffer)
var sb = new System.Text.StringBuilder();
for (int i = 0; i < 1000; i++)
    sb.Append(i);
string result = sb.ToString(); // single heap allocation

Summary: String variable ’ stack (or field in object); String content ’ always heap.

↥ back to top

Q. How many stack and heaps are created for an application?

.NET's managed heap is internally divided into:

// Each new thread = new stack
var t1 = new Thread(() =>
{
    int x = 10; // on t1\'s own stack
    Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: x={x}");
});

var t2 = new Thread(() =>
{
    int x = 20; // on t2\'s own stack — different from t1\'s x
    Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: x={x}");
});

t1.Start(); t2.Start();
t1.Join();  t2.Join();

// Heap is shared — reference types visible across threads
var shared = new List<int>();
var t3 = new Thread(() => shared.Add(1)); // modifies shared heap object
var t4 = new Thread(() => shared.Add(2)); // same heap object
t3.Start(); t4.Start();
t3.Join();  t4.Join();
Console.WriteLine(shared.Count); // 2 (both threads wrote to same heap list)
// Note: List<T> is NOT thread-safe — use ConcurrentBag or lock in production
↥ back to top

Q. How are stack and heap memory deallocated?

Stack deallocation — automatic and immediate:

Stack memory is released as soon as a method returns. The stack pointer moves back (stack “unwinds”). No GC involvement.

void Outer()
{
    Inner();
    // After Inner() returns, all of Inner\'s stack frame is gone
}

void Inner()
{
    int a = 10;       // allocated on stack
    double b = 3.14;  // allocated on stack
} // stack frame released here — a and b are gone

Heap deallocation — by the Garbage Collector (GC):

Objects on the heap are not freed immediately. The GC traces live references and collects unreachable objects in generations (Gen 0, 1, 2).

void CreateObject()
{
    var obj = new byte[1000]; // allocated on heap
    // use obj...
} // obj reference is gone from stack — object is GC-eligible
// GC may collect it at any later point

// Force collection (for demonstration only — avoid in production)
GC.Collect();
GC.WaitForPendingFinalizers();

IDisposable / using — release unmanaged resources deterministically:

using var stream = new FileStream("file.txt", FileMode.OpenOrCreate);
// stream is disposed (file handle released) here — deterministic, not waiting for GC
Memory Released by When
Stack CPU (stack pointer) When method returns
Heap (managed) GC Non-deterministically (Gen 0/1/2 collections)
Heap (unmanaged) Dispose() / finalizer using block or GC finalizer queue
↥ back to top

Q. Who clears the heap memory?

The .NET Garbage Collector (GC) is responsible for clearing heap memory. It:

  1. Traces all live object references starting from GC roots (static fields, local variables, CPU registers, GC handles).
  2. Marks reachable objects as alive.
  3. Collects (sweeps) objects not marked — their memory is reclaimed.
  4. Compacts the heap to remove fragmentation (for Gen 0/1/2; LOH is not compacted by default).
public class Resource
{
    private readonly string _name;
    public Resource(string name) => _name = name;

    // Finalizer — called by GC on a separate finalizer thread
    ~Resource() => Console.WriteLine($"GC collected: {_name}");
}

// Demonstrate GC collection
var r = new Resource("MyResource");
r = null!; // remove the only reference — object becomes unreachable

GC.Collect();               // request a GC (not guaranteed immediate)
GC.WaitForPendingFinalizers(); // wait for finalizer to run
// Output: GC collected: MyResource

// For deterministic cleanup of unmanaged resources:
public class ManagedResource : IDisposable
{
    private bool _disposed;

    public void Dispose()
    {
        if (_disposed) return;
        // Free unmanaged resources here (file handles, network connections, etc.)
        Console.WriteLine("Resource disposed");
        _disposed = true;
        GC.SuppressFinalize(this); // tell GC not to call finalizer
    }

    ~ManagedResource() => Dispose(); // fallback if Dispose not called
}

using var res = new ManagedResource(); // Dispose called at end of using block

GC generations:

↥ back to top

Q. Where is structure allocated Stack or Heap?

It depends on context — not always the stack:

Where struct is used Allocation location
Local variable in a method Stack
Field of a class (reference type) Heap (inside the class object)
Field of another struct Same location as the containing struct
Boxed (cast to object or interface) Heap
Array element (int[], Point[]) Heap (arrays are reference types)
public struct Point { public int X, Y; }
public class Container { public Point Location; } // Point field — on heap inside Container

void Demo()
{
    Point p1 = new Point { X = 1, Y = 2 }; // local var — STACK

    var c = new Container();
    c.Location = new Point { X = 3, Y = 4 }; // field in class — HEAP

    Point[] points = [new(1, 2), new(3, 4)]; // array — HEAP (contiguous)

    // Boxing — struct moved to heap
    object boxed = p1;               // heap allocation!
    Point unboxed = (Point)boxed;    // copy back from heap
}

// readonly record struct (C# 10) — best practice for immutable value types
public readonly record struct Vector2D(double X, double Y)
{
    public double Length => Math.Sqrt(X * X + Y * Y);
}

void VectorDemo()
{
    Vector2D v = new(3, 4); // STACK — local variable
    Console.WriteLine(v.Length); // 5
}

Key point: The common saying “structs are on the stack” is an oversimplification. Structs follow the same allocation rules as their container.

↥ back to top

Q. Can structures get created on Heap?

Yes. Structs can live on the heap in several scenarios:

  1. Boxed — cast to object or an interface.
  2. Field of a class — embedded in the class object's heap memory.
  3. Element of an array — arrays are reference types (heap).
  4. Captured by a closure — if a struct local variable is captured by a lambda/delegate, it may be promoted to the heap.
  5. async state machine — local struct variables in async methods are stored in the heap-allocated state machine object.
public struct Counter { public int Value; }

// 1. Boxing — struct moves to heap
Counter c = new Counter { Value = 5 };
object boxed = c;  // heap allocation, copy of c

// 2. Class field — struct embedded in heap object
public class Wrapper { public Counter Counter; }
var w = new Wrapper(); // w.Counter lives on heap (inside Wrapper)

// 3. Array — heap
Counter[] arr = new Counter[3]; // 3 Counter structs, all on heap

// 4. Closure capture
int x = 10; // local int (struct) captured by lambda ’ promoted to heap
Action inc = () => x++; // x is now on the heap inside a display class
inc();
Console.WriteLine(x); // 11

// 5. async method — locals stored in heap state machine
async Task AsyncDemo()
{
    Counter local = new Counter { Value = 1 }; // stored in async state machine (heap)
    await Task.Delay(1);
    Console.WriteLine(local.Value);
}
↥ back to top

Q. Is there a way we can see this Heap memory?

Yes — several tools and APIs expose the managed heap:

1. Via code — GC APIs:

// Total memory currently used by managed heap
long before = GC.GetTotalMemory(forceFullCollection: false);

var list = new List<byte[]>();
for (int i = 0; i < 100; i++)
    list.Add(new byte[1024]); // allocate 100 KB

long after = GC.GetTotalMemory(forceFullCollection: false);
Console.WriteLine($"Heap grew by: {(after - before) / 1024} KB");

// GC memory info (.NET 5+)
GCMemoryInfo info = GC.GetGCMemoryInfo();
Console.WriteLine($"Heap size: {info.HeapSizeBytes / 1_048_576:F1} MB");
Console.WriteLine($"Committed: {info.TotalCommittedBytes / 1_048_576:F1} MB");
Console.WriteLine($"Gen0 size: {info.GenerationInfo[0].SizeAfterBytes} bytes");
Console.WriteLine($"Gen2 size: {info.GenerationInfo[2].SizeAfterBytes} bytes");

// Generation of a specific object
var obj = new object();
Console.WriteLine($"obj is in Gen{GC.GetGeneration(obj)}"); // 0 (just allocated)
GC.Collect();
Console.WriteLine($"obj is in Gen{GC.GetGeneration(obj)}"); // 1 (survived collection)

2. External tools:

# Capture a GC heap dump from command line
dotnet-gcdump collect -p <pid>
# Open in Visual Studio or dotnet-gcdump report for analysis
↥ back to top

Q. Explain stack and Heap?

Stack is a LIFO (Last-In, First-Out) memory region used for method call frames and local variables. Each thread has its own stack. Memory is allocated and deallocated automatically and instantly as methods are called and return.

Heap is a large, shared memory region managed by the .NET GC. Reference type objects are allocated here. The GC periodically reclaims memory from unreachable objects.

 Stack (per thread)                  Heap (shared, GC managed)
 ””—————————————————————————         ””—————————————————————————
 ” Main() frame            ”         ” ””————————————————————— ”
 ”   args reference ”—————————————– ” ” string[] args       ” ”
 ”   person ref ”—————————————————– ” ”””————————————————————— ”
 ””—————————————————————————         ” ””————————————————————— ”
 ” CreatePerson() frame    ”         ” ” Person object       ” ”
 ”   name = "Pradeep"      ”         ” ”  Name: "Pradeep"    ” ”
 ”   age  = 30             ”         ” ”  Age:  30           ” ”
 ”   p ref ”——————————————————————– ” ”””————————————————————— ”
 ”””—————————————————————————         ”””—————————————————————————
class Person { public string Name { get; } = ""; public int Age; }

void Demo()
{
    int age = 30;                  // value type ’ stack
    string name = "Pradeep";      // reference ’ stack, content ’ heap
    Person p = new Person();       // reference ’ stack, object ’ heap

    Console.WriteLine(GC.GetGeneration(p)); // 0 — just allocated
} // stack frame released; p reference gone; Person object GC-eligible

Summary:

Stack Heap
Fast, automatic, LIFO Slower, GC-managed
Value types + references Reference type objects
Per-thread Shared across threads
Limited size (~1 MB) Large (GB range)
No fragmentation Can fragment (GC compacts)
↥ back to top

Q. Where are stack and heap stored?

Both stack and heap are stored in RAM (physical and virtual memory), managed by the OS and the .NET runtime:

// You can observe memory allocations via the Environment class
Console.WriteLine($"64-bit process: {Environment.Is64BitProcess}");

// Working set and virtual memory
using var proc = System.Diagnostics.Process.GetCurrentProcess();
Console.WriteLine($"Working set:   {proc.WorkingSet64 / 1_048_576} MB");
Console.WriteLine($"Virtual memory:{proc.VirtualMemorySize64 / 1_048_576} MB");
Console.WriteLine($"Private memory:{proc.PrivateMemorySize64 / 1_048_576} MB");

// Heap info via GC
GCMemoryInfo info = GC.GetGCMemoryInfo();
Console.WriteLine($"Total heap committed: {info.TotalCommittedBytes / 1_048_576} MB");

Conceptually:

Physical RAM
  ”” OS kernel code + data
  ”” Thread 1 stack (virtual address range, ~1 MB reserved)
  ”” Thread 2 stack (separate range)
  ”” Managed heap segments (GC-managed, can grow)
  ”     ”” Gen 0 segment
  ”     ”” Gen 1 segment
  ”     ”” Gen 2 segment
  ”     ””” LOH segment
  ””” Native heaps (unmanaged, DLL code, etc.)
↥ back to top

Q. What goes on stack and what goes on heap?

Stack:

Heap:

public struct Point { public int X, Y; }   // value type
public class Circle { public double R; }   // reference type

void Example()
{
    // ” STACK ”————————————————————————————————————————
    int age     = 30;          // int ’ stack
    bool active = true;        // bool ’ stack
    Point pt    = new(3, 4);   // struct ’ stack (inline)
    Circle c    = new() { R = 5 }; // c (reference) ’ stack; object ’ heap

    // ” HEAP ”—————————————————————————————————————————
    // new Circle() object is on heap, 'c' on stack points to it
    var list = new List<int>(); // List object on heap; 'list' ref on stack
    list.Add(42);               // int 42 stored inside the List\'s backing array (heap)

    // Boxing — value type moved to heap
    object boxed = age;         // new heap allocation; 'boxed' ref on stack

    // Array — always heap (array is a reference type)
    int[] arr = [1, 2, 3];      // array object on heap; 'arr' ref on stack
}

Rule of thumb:

Category Where
int, double, bool, char, decimal Stack (as locals)
struct Stack (as locals)
class instance Heap
string content Heap
array Heap
Reference variable Stack
↥ back to top

Q. How is the stack memory address arranged?

The stack grows downward in memory on x86/x64 architectures. When a new method frame is pushed, the stack pointer (RSP) is decremented (moves toward lower addresses). When the method returns, RSP is incremented back (moves toward higher addresses).

High addresses
  ””—————————————————————————  Stack base (thread start)
  ” Main() frame            ”
  ”   [return address]      ”
  ”   [saved registers]     ”
  ”   args = ...            ” RSP points here when in Main()
  ””—————————————————————————
  ” Method1() frame         ”  RSP decremented when Method1() called
  ”   [return address]      ”
  ”   local int a = 10      ”
  ”   local double b = 3.14 ”
  ””—————————————————————————
  ” Method2() frame         ”  RSP decremented again
  ”   [return address]      ”
  ”   local int x = 5       ”  RSP points here (top of stack)
  ”””—————————————————————————
Low addresses (stack grows downward “)
// You can observe stack behavior via StackTrace
void InnerMethod()
{
    var trace = new System.Diagnostics.StackTrace(fNeedFileInfo: true);
    Console.WriteLine(trace);
    // Output shows call stack: InnerMethod ’ OuterMethod ’ Main
}

void OuterMethod() => InnerMethod();
OuterMethod();

Key points:

↥ back to top

Q. How is stack memory deallocated LIFO or FIFO?

LIFO (Last-In, First-Out). Stack memory deallocation is strictly LIFO — the most recently pushed frame is always released first (when its method returns).

void A()
{
    int x = 1; // pushed when A() called
    Console.WriteLine("A: entered");
    B();       // B\'s frame pushed ON TOP of A\'s frame
    Console.WriteLine("A: B returned"); // A\'s frame still alive
} // A\'s frame popped — x released

void B()
{
    int y = 2; // pushed when B() called
    Console.WriteLine("B: entered");
    C();       // C\'s frame pushed on top
    Console.WriteLine("B: C returned");
} // B\'s frame popped — y released

void C()
{
    int z = 3; // pushed last
    Console.WriteLine("C: entered");
} // C\'s frame popped FIRST — z released first (LIFO)

A();
// Output:
// A: entered
// B: entered
// C: entered    C allocated last
// B: C returned
// A: B returned
// Stack release order: C first, then B, then A (LIFO)

Why LIFO works: Methods call each other in a nested (tree) structure. A callee always returns before its caller — so the callee's frame is always on top and is always freed first. This makes a simple stack pointer increment sufficient for deallocation — no GC, no scanning, no bookkeeping.

↥ back to top

Q. How do you choose between List<T> and ArrayList in C#?

Always choose List<T> in new code. ArrayList is a legacy type that predates generics and should not be used in modern .NET.

Decision factor ArrayList List<T>
Type safety Stores object — runtime cast errors … Compile-time type checking
Performance (value types) Boxing/unboxing on every operation … No boxing — direct storage
IntelliSense / tooling All methods return object … Full typed IntelliSense
LINQ Requires cast … Native LINQ support
Recommended Legacy code only … Always
//  ArrayList — avoid in new code
var al = new System.Collections.ArrayList();
al.Add(42);       // boxing int ’ object
al.Add("mixed");  // no compile error — mixed types allowed
int n = (int)al[0]; // unboxing, runtime error if wrong type

// … List<T> — always prefer
var list = new List<int> { 1, 2, 3 };
list.Add(4);         // no boxing
list.Add(5);
Console.WriteLine(list.Count); // 5

// List<T> with LINQ
var evens = list.Where(x => x % 2 == 0).ToList();
Console.WriteLine(string.Join(",", evens)); // 2,4

// List<T> capacity pre-allocation for performance
var large = new List<int>(capacity: 1_000_000);
for (int i = 0; i < 1_000_000; i++)
    large.Add(i); // no re-allocation needed — capacity was pre-set
↥ back to top

Q. How do you iterate over a collection in C#?

var numbers = new List<int> { 1, 2, 3, 4, 5 };

// 1. foreach — simplest, works on any IEnumerable<T>
foreach (int n in numbers)
    Console.Write($"{n} "); // 1 2 3 4 5

// 2. for loop — when you need the index
for (int i = 0; i < numbers.Count; i++)
    Console.Write($"[{i}]={numbers[i]} ");

// 3. LINQ — declarative, composable
numbers.ForEach(Console.WriteLine);    // List<T>.ForEach
var doubled = numbers.Select(n => n * 2).ToList();

// 4. while with enumerator — manual control
using var enumerator = numbers.GetEnumerator();
while (enumerator.MoveNext())
    Console.Write($"{enumerator.Current} ");

// 5. Span<T> / ReadOnlySpan<T> — zero-allocation iteration (C# 7.2+)
int[] arr = [10, 20, 30, 40];
ReadOnlySpan<int> span = arr;
foreach (ref readonly int item in span)
    Console.Write($"{item} ");

// 6. Dictionary iteration
var dict = new Dictionary<string, int> { ["a"] = 1, ["b"] = 2 };
foreach (var (key, value) in dict) // deconstruction (C# 7+)
    Console.WriteLine($"{key}={value}");

// 7. Parallel iteration (CPU-bound work)
Parallel.ForEach(numbers, n => Console.Write($"{n * n} ")); // order not guaranteed

// 8. Async iteration — IAsyncEnumerable<T> (C# 8+)
async IAsyncEnumerable<int> GetAsync()
{
    foreach (var n in numbers)
    {
        await Task.Delay(1);
        yield return n;
    }
}

await foreach (int n in GetAsync())
    Console.Write($"{n} ");
↥ back to top

Q. What is the difference between IEnumerable and ICollection in C#?

ICollection<T> extends IEnumerable<T> by adding size awareness and modification capabilities.

Member IEnumerable<T> ICollection<T>
GetEnumerator() … (inherited)
Count  
IsReadOnly  
Add(T)  
Remove(T)  
Contains(T)  
Clear()  
CopyTo(T[], int)  
// IEnumerable<T> — iterate only
IEnumerable<int> seq = [1, 2, 3, 4, 5];
foreach (var n in seq) Console.Write($"{n} ");
// seq.Count(); // works via LINQ extension, but O(n)
// seq.Add(6);  //  no Add on IEnumerable

// ICollection<T> — add, remove, count
ICollection<string> col = new List<string> { "Alice", "Bob" };
col.Add("Carol");
col.Remove("Bob");
Console.WriteLine(col.Count);          // 2
Console.WriteLine(col.Contains("Alice")); // True

// Implementing ICollection<T>
public class FixedSizeCollection<T> : ICollection<T>
{
    private readonly List<T> _inner = [];
    private readonly int _maxSize;

    public FixedSizeCollection(int maxSize) => _maxSize = maxSize;

    public int Count => _inner.Count;
    public bool IsReadOnly => false;

    public void Add(T item)
    {
        if (_inner.Count >= _maxSize)
            throw new InvalidOperationException("Collection is full");
        _inner.Add(item);
    }

    public bool Remove(T item)      => _inner.Remove(item);
    public bool Contains(T item)    => _inner.Contains(item);
    public void Clear()             => _inner.Clear();
    public void CopyTo(T[] arr, int i) => _inner.CopyTo(arr, i);
    public IEnumerator<T> GetEnumerator() => _inner.GetEnumerator();
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}
↥ back to top

Q. How do you sort a collection in C#?

// 1. List<T>.Sort() — in-place, uses default comparer
var numbers = new List<int> { 5, 3, 1, 4, 2 };
numbers.Sort();
Console.WriteLine(string.Join(",", numbers)); // 1,2,3,4,5

// 2. List<T>.Sort() with custom comparer — descending
numbers.Sort((a, b) => b.CompareTo(a));
Console.WriteLine(string.Join(",", numbers)); // 5,4,3,2,1

// 3. Array.Sort()
int[] arr = [5, 3, 1, 4, 2];
Array.Sort(arr);
Console.WriteLine(string.Join(",", arr)); // 1,2,3,4,5

// 4. LINQ OrderBy / OrderByDescending — returns new sequence (non-destructive)
var names = new List<string> { "Charlie", "Alice", "Bob" };
var sorted = names.OrderBy(n => n).ToList();
var desc   = names.OrderByDescending(n => n).ToList();
Console.WriteLine(string.Join(",", sorted)); // Alice,Bob,Charlie
Console.WriteLine(string.Join(",", desc));   // Charlie,Bob,Alice

// 5. Sort complex objects
record Product(string Name, decimal Price);

var products = new List<Product>
{
    new("Laptop", 999m),
    new("Mouse", 25m),
    new("Monitor", 400m),
};

// By single property
var byPrice = products.OrderBy(p => p.Price).ToList();

// By multiple properties
var sorted2 = products
    .OrderBy(p => p.Name.Length) // primary
    .ThenBy(p => p.Price)        // secondary
    .ToList();

foreach (var p in byPrice)
    Console.WriteLine($"{p.Name}: {p.Price:C}");

// 6. IComparer<T> — reusable custom sort logic
var byNameDesc = new List<string> { "Charlie", "Alice", "Bob" };
byNameDesc.Sort(StringComparer.OrdinalIgnoreCase); // case-insensitive ascending

// 7. CollectionsMarshal / Span sort (.NET 5+) — zero-allocation in-place
var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(numbers);
span.Sort(); // sorts the List\'s backing array directly
↥ back to top

Q. How do you remove duplicates from a collection in C#?

var withDups = new List<int> { 1, 2, 2, 3, 3, 3, 4, 4, 5 };

// 1. HashSet<T> — fastest, loses order
var unique1 = new HashSet<int>(withDups);
Console.WriteLine(string.Join(",", unique1)); // 1,2,3,4,5 (order may vary)

// 2. LINQ Distinct() — preserves first occurrence order
var unique2 = withDups.Distinct().ToList();
Console.WriteLine(string.Join(",", unique2)); // 1,2,3,4,5 (ordered)

// 3. LINQ DistinctBy (C# 6 / .NET 6+) — by property
record Product(string Name, decimal Price);

var products = new List<Product>
{
    new("Laptop",  999m),
    new("Mouse",   25m),
    new("Laptop",  899m), // duplicate name
    new("Monitor", 400m),
};

var distinctByName = products.DistinctBy(p => p.Name).ToList();
foreach (var p in distinctByName)
    Console.WriteLine($"{p.Name}: {p.Price:C}");
// Laptop: £999.00, Mouse: £25.00, Monitor: £400.00

// 4. GroupBy — group then take first (more control)
var deduplicated = products
    .GroupBy(p => p.Name)
    .Select(g => g.OrderBy(p => p.Price).First()) // take cheapest per name
    .ToList();

// 5. ToHashSet() extension — convenient
var strings = new[] { "apple", "banana", "apple", "cherry", "banana" };
var uniqueStrings = strings.ToHashSet();
Console.WriteLine(string.Join(",", uniqueStrings)); // apple,banana,cherry

// 6. Custom equality — using IEqualityComparer
var uniqueIgnoreCase = strings
    .Distinct(StringComparer.OrdinalIgnoreCase)
    .ToList();
↥ back to top

Q. What is the difference between Queue and Stack in C#?

Feature Queue<T> Stack<T>
Order FIFO — First In, First Out LIFO — Last In, First Out
Add Enqueue(item) Push(item)
Remove Dequeue() Pop()
Peek (no remove) Peek() Peek()
Non-removing try TryDequeue(out T) TryPop(out T)
Use case Task queues, BFS, print spoolers Undo/redo, call stack simulation, DFS
// Queue<T> — FIFO
var queue = new Queue<string>();
queue.Enqueue("First");
queue.Enqueue("Second");
queue.Enqueue("Third");

Console.WriteLine(queue.Dequeue()); // First   oldest item out
Console.WriteLine(queue.Peek());    // Second  next without removing
Console.WriteLine(queue.Count);     // 2

// Practical: task processing queue
var taskQueue = new Queue<Func<Task>>();
taskQueue.Enqueue(() => Task.Run(() => Console.WriteLine("Task A")));
taskQueue.Enqueue(() => Task.Run(() => Console.WriteLine("Task B")));

while (taskQueue.TryDequeue(out var task))
    await task();

// Stack<T> — LIFO
var stack = new Stack<string>();
stack.Push("First");
stack.Push("Second");
stack.Push("Third");

Console.WriteLine(stack.Pop());  // Third   most recent item out
Console.WriteLine(stack.Peek()); // Second  next without removing
Console.WriteLine(stack.Count);  // 2

// Practical: undo history
var undoStack = new Stack<string>();
undoStack.Push("Type 'Hello'");
undoStack.Push("Type ' World'");
undoStack.Push("Delete 'World'");

Console.WriteLine($"Undo: {undoStack.Pop()}"); // Undo: Delete 'World'
Console.WriteLine($"Undo: {undoStack.Pop()}"); // Undo: Type ' World'
↥ back to top

Q. How do you convert an array to a collection in C#?

int[] arr = [1, 2, 3, 4, 5];

// 1. To List<T>
var list = arr.ToList();             // LINQ extension — O(n) copy
var list2 = new List<int>(arr);      // constructor overload
Console.WriteLine(list.GetType().Name); // List`1

// 2. To HashSet<T> — deduplicate while converting
var set = arr.ToHashSet();
int[] withDups = [1, 2, 2, 3, 3];
var uniqueSet = withDups.ToHashSet(); // { 1, 2, 3 }

// 3. To Queue<T>
var queue = new Queue<int>(arr);     // FIFO order preserved

// 4. To Stack<T>
var stack = new Stack<int>(arr);     // note: iteration order is reversed

// 5. To Dictionary<K,V>
string[] words = ["apple", "banana", "cherry"];
var wordDict = words.ToDictionary(w => w, w => w.Length);
// { "apple": 5, "banana": 6, "cherry": 6 }

// 6. To ImmutableList<T>
using System.Collections.Immutable;
var immutable = arr.ToImmutableList();
var added = immutable.Add(6); // returns new list; original unchanged

// 7. To Span<T> / Memory<T> — zero-copy (for performance-sensitive code)
Span<int> span     = arr.AsSpan();
Memory<int> memory = arr.AsMemory();
span[0] = 99; // modifies the original array

// 8. Collection expressions (C# 12) — spread operator
List<int> merged = [.. arr, 6, 7, 8]; // spread arr + append literals

// 9. As IEnumerable<T> — no copy (already implements it)
IEnumerable<int> enumerable = arr;
foreach (var n in enumerable) Console.Write($"{n} ");
↥ back to top

Q. What are the thread-safe collection classes available in C#?

Thread-safe collections in System.Collections.Concurrent use fine-grained locking or lock-free algorithms (CAS — compare-and-swap) instead of requiring external lock statements.

Class Thread-safe equivalent of Key operations
ConcurrentDictionary<K,V> Dictionary<K,V> TryAdd, TryUpdate, GetOrAdd, AddOrUpdate
ConcurrentQueue<T> Queue<T> Enqueue, TryDequeue, TryPeek
ConcurrentStack<T> Stack<T> Push, TryPop, TryPeek
ConcurrentBag<T> (unordered) Add, TryTake, TryPeek
BlockingCollection<T> (producer-consumer) Add, Take, GetConsumingEnumerable
// ConcurrentDictionary — atomic operations
var counter = new ConcurrentDictionary<string, int>();

await Task.WhenAll(Enumerable.Range(0, 1000).Select(_ =>
    Task.Run(() => counter.AddOrUpdate("hits", 1, (_, old) => old + 1))));

Console.WriteLine(counter["hits"]); // 1000 (always correct)

// ConcurrentQueue — thread-safe task distribution
var workQueue = new ConcurrentQueue<int>();
for (int i = 0; i < 10; i++) workQueue.Enqueue(i);

var workers = Enumerable.Range(0, 3).Select(_ => Task.Run(() =>
{
    while (workQueue.TryDequeue(out int item))
        Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} processed {item}");
}));

await Task.WhenAll(workers);

// ConcurrentBag — unordered, thread-local storage (good for object pools)
var bag = new ConcurrentBag<int>();
await Task.WhenAll(Enumerable.Range(0, 5).Select(i =>
    Task.Run(() => bag.Add(i))));
Console.WriteLine(bag.Count); // 5

// ImmutableList — safe to share across threads (read-only after creation)
using System.Collections.Immutable;
var shared = ImmutableList.Create(1, 2, 3);
// Any thread can read — no synchronisation needed (immutable)
var modified = shared.Add(4); // returns NEW list, original unchanged
↥ back to top

Q. How do you use LINQ with collections in C#?

LINQ (Language Integrated Query) provides a rich set of extension methods on IEnumerable<T> for querying and transforming collections declaratively.

record Product(string Name, string Category, decimal Price, int Stock);

var products = new List<Product>
{
    new("Laptop",   "Electronics", 999m,  15),
    new("Mouse",    "Electronics", 25m,   200),
    new("Desk",     "Furniture",   350m,  30),
    new("Monitor",  "Electronics", 450m,  50),
    new("Chair",    "Furniture",   250m,  40),
    new("Keyboard", "Electronics", 75m,   150),
};

// Filter (Where)
var electronics = products.Where(p => p.Category == "Electronics").ToList();

// Project (Select)
var names = products.Select(p => p.Name).ToList();

// Sort (OrderBy / ThenBy)
var sorted = products
    .OrderBy(p => p.Category)
    .ThenByDescending(p => p.Price)
    .ToList();

// Aggregate
decimal total = products.Sum(p => p.Price);
decimal avg   = products.Average(p => p.Price);
int     count = products.Count(p => p.Price > 100);

Console.WriteLine($"Total: {total:C}, Avg: {avg:C}, Count>100: {count}");

// Group
var byCategory = products
    .GroupBy(p => p.Category)
    .Select(g => new { Category = g.Key, Count = g.Count(), TotalValue = g.Sum(p => p.Price) })
    .ToList();

foreach (var g in byCategory)
    Console.WriteLine($"{g.Category}: {g.Count} items, £{g.TotalValue}");

// Flat map (SelectMany)
var tags = new[] { new[] { "a", "b" }, new[] { "c", "d" } };
var allTags = tags.SelectMany(t => t).ToList(); // [a, b, c, d]

// First / Single / Any / All
var cheapest = products.MinBy(p => p.Price);
bool anyOutOfStock = products.Any(p => p.Stock == 0);
bool allInStock    = products.All(p => p.Stock > 0);

// Distinct / DistinctBy (.NET 6+)
var categories = products.DistinctBy(p => p.Category).Select(p => p.Category).ToList();

// Take / Skip / Chunk
var page1 = products.OrderBy(p => p.Name).Take(3).ToList();
var page2 = products.OrderBy(p => p.Name).Skip(3).Take(3).ToList();
var pages = products.Chunk(2).ToList(); // groups of 2

// Query syntax (equivalent to method syntax above)
var expensiveElectronics =
    from p in products
    where p.Category == "Electronics" && p.Price > 100
    orderby p.Price descending
    select p;

foreach (var p in expensiveElectronics)
    Console.WriteLine($"{p.Name}: {p.Price:C}");
↥ back to top

Q. What is the difference between Array and List in C#?

Feature T[] (Array) List<T>
Size Fixed at creation Dynamic (auto-resizes)
Type Value/reference — contiguous memory Backed by T[], resized on demand
Index access … O(1) … O(1)
Add / Remove Not supported Add, Remove, Insert
Length / Count Length Count
Memory Slightly more efficient (no metadata) Small overhead for capacity tracking
Multi-dimensional … (int[,], int[][]) (use List<List<T>> instead)
LINQ
Span/Memory AsSpan() CollectionsMarshal.AsSpan()
Interop (P/Invoke, etc.) Preferred Usually requires .ToArray()
// Array — fixed size
int[] arr = new int[5];
arr[0] = 10;
// arr[5] = 60; //  IndexOutOfRangeException

// List<T> — dynamic
var list = new List<int> { 1, 2, 3 };
list.Add(4);          // grows automatically
list.Insert(0, 0);    // [0, 1, 2, 3, 4]
list.Remove(2);       // [0, 1, 3, 4]
list.RemoveAt(0);     // [1, 3, 4]

Console.WriteLine(list.Count); // 3

// Convert between them
int[] fromList = list.ToArray();
List<int> fromArr = arr.ToList();

// Performance — pre-allocate List capacity to avoid re-allocations
var preAllocated = new List<int>(capacity: 1_000_000);
for (int i = 0; i < 1_000_000; i++)
    preAllocated.Add(i); // no re-allocations needed

Decision: Use T[] when size is fixed and performance/interop is critical. Use List<T> when you need dynamic sizing and rich collection APIs.

↥ back to top

Q. What is the difference between Array and Collections?

Aspect Array (T[]) Collections (e.g., List<T>, Dictionary<K,V>)
Size Fixed Dynamic
Type Single type, contiguous memory Generic (strongly typed), backed by arrays/BSTs
Flexibility Minimal — only index access Rich API: search, sort, filter, thread-safe variants
Interfaces IList<T>, IEnumerable<T> Same + ICollection<T> and more
Overhead Minimal Small metadata overhead
Multi-dim … (int[,]) (nest collections)
Nullability Can hold nulls Depends on type
// Array — tight, fixed, minimal API
int[] arr = [10, 20, 30];
Console.WriteLine(arr.Length); // 3
// arr[3] = 40; //  cannot resize

// List<T> — flexible, rich
var list = new List<int>([10, 20, 30]);
list.Add(40);
list.Remove(20);
list.Sort();
Console.WriteLine(list.Count); // 3

// Dictionary<K,V> — key-value, O(1) lookup
var dict = new Dictionary<string, int>
{
    ["one"] = 1, ["two"] = 2, ["three"] = 3,
};
Console.WriteLine(dict["two"]); // 2

// HashSet<T> — unique values, O(1) membership
var set = new HashSet<int> { 1, 2, 3, 2, 1 };
Console.WriteLine(set.Count); // 3 (duplicates removed)

In summary: Arrays are the primitive building block. Collections are higher-level abstractions that wrap arrays (or other structures) and add functionality. Most collections internally use arrays.

↥ back to top

Q. What are generic collections?

Generic collections are type-parameterised collection classes in System.Collections.Generic that enforce type safety at compile time and avoid boxing/unboxing for value types. They were introduced in .NET 2.0 and replaced the non-generic collections (ArrayList, Hashtable, etc.).

Benefits:

// Generic List<T>
var numbers = new List<int> { 1, 2, 3 };
numbers.Add(4);
// numbers.Add("text"); //  compile error — type-safe

// Generic Dictionary<K,V>
var scores = new Dictionary<string, int>
{
    ["Alice"] = 95,
    ["Bob"]   = 87,
};
scores["Carol"] = 92;

if (scores.TryGetValue("Alice", out int score))
    Console.WriteLine($"Alice: {score}"); // Alice: 95

// Generic HashSet<T>
var unique = new HashSet<string> { "apple", "banana", "apple" };
Console.WriteLine(unique.Count); // 2

// Generic Queue<T> and Stack<T>
var queue = new Queue<int>();
queue.Enqueue(1); queue.Enqueue(2);
Console.WriteLine(queue.Dequeue()); // 1

var stack = new Stack<string>();
stack.Push("first"); stack.Push("second");
Console.WriteLine(stack.Pop()); // second

// Generic custom class
public class Repository<T> where T : class
{
    private readonly List<T> _items = [];
    public void Add(T item) => _items.Add(item);
    public IReadOnlyList<T> GetAll() => _items;
}

var repo = new Repository<string>();
repo.Add("hello");
repo.Add("world");
Console.WriteLine(repo.GetAll().Count); // 2
↥ back to top

Q. How can you optimize memory usage and performance when working with large data collections in C#?

Key optimisations:

1. Pre-allocate capacity:

//  No capacity — triggers multiple re-allocations as list grows
var list = new List<int>();
for (int i = 0; i < 1_000_000; i++) list.Add(i);

// … Pre-allocate — single backing array allocation
var list2 = new List<int>(capacity: 1_000_000);
for (int i = 0; i < 1_000_000; i++) list2.Add(i);

2. Use Span<T> and Memory<T> for zero-allocation slicing:

int[] arr = Enumerable.Range(0, 1_000_000).ToArray();

//  Creates a new array copy
int[] slice = arr[100..200];

// … Zero-copy view
ReadOnlySpan<int> span = arr.AsSpan(100, 100);
int sum = 0;
foreach (ref readonly int n in span) sum += n;

3. Use ArrayPool<T> for temporary buffers:

using System.Buffers;

//  Allocates a new array each time (GC pressure)
void ProcessData(byte[] data) { /* ... */ }

// … Rent from pool — no GC allocation
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
try
{
    // use buffer
    ProcessData(buffer);
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer); // return to pool
}

4. Use IEnumerable<T> with yield for streaming (avoid loading all data):

//  Loads all 10M records into memory
List<int> LoadAll() => Enumerable.Range(0, 10_000_000).ToList();

// … Streams one at a time — O(1) memory
IEnumerable<int> StreamAll()
{
    for (int i = 0; i < 10_000_000; i++)
        yield return i;
}

// Process without holding all in memory
foreach (var item in StreamAll().Where(n => n % 1000 == 0))
    Console.WriteLine(item);

5. Use FrozenDictionary / FrozenSet for read-only lookup (.NET 8+):

using System.Collections.Frozen;

// FrozenDictionary is optimised for frequent reads — faster than Dictionary for lookups
var lookup = new Dictionary<string, int> { ["a"] = 1, ["b"] = 2 }
    .ToFrozenDictionary();

// Very fast Contains/lookup — ideal for static configuration, keyword lists
Console.WriteLine(lookup.ContainsKey("a")); // True

6. Use LINQ Chunk() to process in batches:

var allItems = Enumerable.Range(1, 100_000);

foreach (var batch in allItems.Chunk(1000))
{
    // Process 1000 items at a time — limits peak memory
    await ProcessBatchAsync(batch);
}

7. Avoid LINQ materialisation when not needed:

//  Materialises to List unnecessarily
var list = items.Where(x => x > 0).ToList().Count;

// … Count without materialising
var count = items.Count(x => x > 0); // single pass, no intermediate list
↥ back to top

Q. How do you use List<T> in C# with common operations?

List<T> is the most commonly used generic collection in .NET. It is a resizable array that provides O(1) indexed access, O(1) amortised appends, and O(n) inserts/removes.

// ” 1. Create and initialise ”————————————————————————————————————
var fruits = new List<string> { "Apple", "Banana", "Cherry" };

// ” 2. Add / Insert ”—————————————————————————————————————————————
fruits.Add("Date");                   // append
fruits.Insert(1, "Avocado");          // insert at index 1
fruits.AddRange(["Elderberry", "Fig"]); // add multiple

// ” 3. Access ”———————————————————————————————————————————————————
Console.WriteLine(fruits[0]);        // Apple
Console.WriteLine(fruits.Count);     // 6

// ” 4. Search ”———————————————————————————————————————————————————
Console.WriteLine(fruits.Contains("Banana"));       // True
Console.WriteLine(fruits.IndexOf("Cherry"));        // 3 (after insert)
Console.WriteLine(fruits.Find(f => f.StartsWith("A"))); // Apple

// ” 5. Remove ”———————————————————————————————————————————————————
fruits.Remove("Banana");              // by value
fruits.RemoveAt(0);                   // by index
fruits.RemoveAll(f => f.Length > 5);  // by predicate

// ” 6. Sort and reverse ”—————————————————————————————————————————
fruits.Sort();
fruits.Reverse();

// ” 7. Convert ”——————————————————————————————————————————————————
string[] array = fruits.ToArray();
List<string> copy  = fruits.ToList();           // shallow copy

// ” 8. Iterate ”——————————————————————————————————————————————————
foreach (var f in fruits)
    Console.WriteLine(f);

// ” 9. LINQ integration ”—————————————————————————————————————————
var longNames = fruits.Where(f => f.Length > 4).OrderBy(f => f).ToList();

// ” 10. Capacity vs Count ”———————————————————————————————————————
var numbers = new List<int>(capacity: 100);  // reserve space upfront
Console.WriteLine(numbers.Capacity);  // 100
Console.WriteLine(numbers.Count);     // 0  — no elements yet

Time complexity summary:

Operation Complexity
Add (end) O(1) amortised
Insert (middle) O(n)
Remove by value O(n)
RemoveAt (end) O(1)
Index access [i] O(1)
Contains O(n)
BinarySearch (sorted) O(log n)
↥ back to top

Q. How do you use Dictionary<TKey, TValue> in C# with CRUD operations?

Dictionary<TKey, TValue> stores key–value pairs in a hash table, providing O(1) average-case lookups, inserts, and deletes.

// ” 1. Create ”———————————————————————————————————————————————————
var scores = new Dictionary<string, int>
{
    ["Alice"] = 95,
    ["Bob"]   = 82,
    ["Carol"] = 78,
};

// ” 2. Add / Update ”—————————————————————————————————————————————
scores.Add("Dave", 91);              // throws if key exists
scores["Eve"] = 88;                  // add or overwrite
scores.TryAdd("Alice", 0);           // no-op if key exists — returns false

scores["Bob"] = 85;                  // update existing value

// ” 3. Read ”—————————————————————————————————————————————————————
Console.WriteLine(scores["Alice"]);  // 95 — throws KeyNotFoundException if missing

if (scores.TryGetValue("Frank", out int frankScore))
    Console.WriteLine(frankScore);
else
    Console.WriteLine("Frank not found");

// Safe default without exception
int val = scores.GetValueOrDefault("Ghost", 0);

// ” 4. Delete ”———————————————————————————————————————————————————
scores.Remove("Carol");              // returns true if removed
scores.Remove("Carol");              // returns false — already gone, no exception

// ” 5. Check existence ”——————————————————————————————————————————
Console.WriteLine(scores.ContainsKey("Alice"));   // True
Console.WriteLine(scores.ContainsValue(91));      // True

// ” 6. Iterate ”——————————————————————————————————————————————————
foreach (var (name, score) in scores)             // KeyValuePair deconstruction
    Console.WriteLine($"{name}: {score}");

foreach (string key in scores.Keys)   Console.WriteLine(key);
foreach (int    v   in scores.Values) Console.WriteLine(v);

// ” 7. Merge / upsert pattern ”———————————————————————————————————
var extras = new Dictionary<string, int> { ["Alice"] = 5, ["Zoe"] = 70 };
foreach (var (k, v) in extras)
    scores[k] = scores.GetValueOrDefault(k) + v;  // adds or accumulates

// ” 8. Group by with Dictionary ”—————————————————————————————————
string[] words = ["apple", "ant", "banana", "berry", "cherry"];
var byFirstLetter = words.GroupBy(w => w[0])
                         .ToDictionary(g => g.Key, g => g.ToList());
Console.WriteLine(string.Join(", ", byFirstLetter['a']));  // apple, ant

// ” 9. Concurrent access ”————————————————————————————————————————
// For thread-safe scenarios use ConcurrentDictionary<K,V>
var concurrentScores = new System.Collections.Concurrent.ConcurrentDictionary<string, int>();
concurrentScores.AddOrUpdate("Alice", 95, (_, old) => old + 5);

Dictionary vs Hashtable:

Feature Dictionary<K,V> Hashtable
Type safety Generic — strongly typed object — requires boxing
Null key Not allowed … Allowed
Thread safety Not thread-safe Partially thread-safe (reads)
Performance Faster (no boxing) Slower
Preferred … Modern code Legacy code only
↥ back to top

Q. How do you use Stack<T> and Queue<T> in C#?

Stack<T> is a LIFO (Last-In, First-Out) collection. Queue<T> is a FIFO (First-In, First-Out) collection. Both provide O(1) push/pop and enqueue/dequeue operations.

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Stack<T>  — LIFO
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
var stack = new Stack<string>();

// Push — add to top
stack.Push("First");
stack.Push("Second");
stack.Push("Third");
Console.WriteLine(stack.Count);   // 3

// Peek — view top without removing
Console.WriteLine(stack.Peek());  // Third

// Pop — remove from top
Console.WriteLine(stack.Pop());   // Third
Console.WriteLine(stack.Pop());   // Second
Console.WriteLine(stack.Count);   // 1

// TryPop / TryPeek (safe, no exception on empty)
if (stack.TryPop(out string? item))
    Console.WriteLine($"Popped: {item}");   // Popped: First

// Real-world: undo/redo, expression evaluation, DFS traversal
var history = new Stack<string>();
history.Push("Page A");
history.Push("Page B");
history.Push("Page C");
string last = history.Pop();   // Go back to Page B
Console.WriteLine(last);       // Page C

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Queue<T>  — FIFO
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
var queue = new Queue<string>();

// Enqueue — add to back
queue.Enqueue("Task 1");
queue.Enqueue("Task 2");
queue.Enqueue("Task 3");
Console.WriteLine(queue.Count);     // 3

// Peek — view front without removing
Console.WriteLine(queue.Peek());    // Task 1

// Dequeue — remove from front
Console.WriteLine(queue.Dequeue()); // Task 1
Console.WriteLine(queue.Dequeue()); // Task 2

// TryDequeue / TryPeek (safe variants)
if (queue.TryDequeue(out string? next))
    Console.WriteLine($"Processing: {next}");   // Processing: Task 3

// Real-world: request queuing, BFS, print spooler
var printQueue = new Queue<string>();
printQueue.Enqueue("Document1.pdf");
printQueue.Enqueue("Document2.docx");

while (printQueue.TryDequeue(out string? doc))
    Console.WriteLine($"Printing: {doc}");

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// PriorityQueue<TElement, TPriority>  — C# 10+
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
var pq = new PriorityQueue<string, int>();
pq.Enqueue("Low priority task",    10);
pq.Enqueue("High priority task",   1);
pq.Enqueue("Medium priority task", 5);

while (pq.TryDequeue(out string? task, out int priority))
    Console.WriteLine($"[{priority}] {task}");
// [1] High priority task
// [5] Medium priority task
// [10] Low priority task

Stack vs Queue comparison:

Feature Stack<T> (LIFO) Queue<T> (FIFO)
Add Push(item) Enqueue(item)
Remove Pop() Dequeue()
Peek Peek() Peek()
Safe remove TryPop(out T) TryDequeue(out T)
Use case Undo, DFS, parsing Task queues, BFS, printing
↥ back to top

# 6. MULTITHREADING


Q. What is multithreading in C# and why is it important?

Multithreading is the ability to execute multiple threads concurrently within a single process, enabling parallelism and better CPU utilization. In modern .NET, the preferred abstraction is Task and async/await (via the Task Parallel Library, TPL) rather than raw Thread management.

Why it matters:

1. Task.Run — run CPU-bound work on the thread pool:

var result = await Task.Run(() =>
{
    // CPU-intensive work (runs on thread pool thread)
    return Enumerable.Range(1, 1_000_000).Sum();
});
Console.WriteLine(result); // Output: 500000500000

2. async/await — non-blocking async I/O (preferred for I/O-bound):

public async Task<string[]> FetchAllAsync(string[] urls)
{
    using var client = new HttpClient();
    var tasks = urls.Select(url => client.GetStringAsync(url));
    return await Task.WhenAll(tasks); // all in parallel
}

3. Parallel.ForEachAsync (.NET 6+) — async parallel processing:

var urls = new[] { "https://api1.example.com", "https://api2.example.com" };

await Parallel.ForEachAsync(urls,
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    async (url, ct) =>
    {
        using var client = new HttpClient();
        var data = await client.GetStringAsync(url, ct);
        Console.WriteLine($"Fetched {data.Length} chars from {url}");
    });

4. Thread-safe shared state with Interlocked:

int counter = 0;
await Task.WhenAll(Enumerable.Range(0, 100).Select(_ =>
    Task.Run(() => Interlocked.Increment(ref counter))));
Console.WriteLine(counter); // Output: 100 (always correct)
↥ back to top

Q. What is Multithreading with .NET, and what is a thread in C#?

A thread is the smallest unit of execution within a process. A process can have multiple threads running concurrently, sharing the same memory space.

Multithreading is the ability to run multiple threads simultaneously to perform work in parallel, improving responsiveness and throughput.

In .NET, threads are managed by the CLR and scheduled by the OS. Modern .NET (5+) recommends using Task and async/await over raw Thread for most scenarios.

// A thread in .NET = lightweight unit of execution
Console.WriteLine($"Main thread ID: {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"Is background: {Thread.CurrentThread.IsBackground}");
Console.WriteLine($"Is thread pool: {Thread.CurrentThread.IsThreadPoolThread}");
Console.WriteLine($"State: {Thread.CurrentThread.ThreadState}");

Ways to implement multithreading in .NET 10:

// 1. Thread (low-level — use only for dedicated long-running work)
var t = new Thread(() => Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}"));
t.IsBackground = true;
t.Start();
t.Join();

// 2. ThreadPool (managed pool — underlying mechanism for Tasks)
ThreadPool.QueueUserWorkItem(_ => Console.WriteLine("ThreadPool work item"));

// 3. Task (preferred — async, return values, exception propagation)
await Task.Run(() => Console.WriteLine("Task on thread pool"));

// 4. Parallel class (data parallelism)
Parallel.For(0, 4, i => Console.WriteLine($"Parallel item {i}"));

// 5. async/await (I/O-bound work without blocking threads)
async Task<string> FetchDataAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);
}

// 6. PLINQ (parallel LINQ)
var results = Enumerable.Range(1, 100).AsParallel().Where(n => n % 2 == 0).ToList();
↥ back to top

Q. What is the difference between a thread and a process?

  Process Thread
Definition An isolated running instance of a program A unit of execution within a process
Memory Has its own address space Shares the process address space
Communication IPC (pipes, sockets, shared memory) Shared memory — fast but needs synchronization
Isolation Crash in one process doesn't affect others Crash in one thread can crash the whole process
Creation cost High (separate memory, handles, etc.) Lower (shares process resources)
Switching cost Expensive (context switch across processes) Less expensive (same address space)
// Process info
var current = System.Diagnostics.Process.GetCurrentProcess();
Console.WriteLine($"PID: {current.Id}");
Console.WriteLine($"Name: {current.ProcessName}");
Console.WriteLine($"Threads: {current.Threads.Count}");
Console.WriteLine($"Memory: {current.WorkingSet64 / 1024 / 1024} MB");

// Spawn a child process
using var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
    FileName  = "dotnet",
    Arguments = "--version",
    RedirectStandardOutput = true,
    UseShellExecute = false,
});
await proc!.WaitForExitAsync();
Console.WriteLine(await proc.StandardOutput.ReadToEndAsync());

// Thread info
var thread = new Thread(() =>
{
    Console.WriteLine($"Thread ID: {Thread.CurrentThread.ManagedThreadId}");
    Console.WriteLine($"Is pool: {Thread.CurrentThread.IsThreadPoolThread}");
});
thread.Start();
thread.Join();
↥ back to top

Q. How do you create a new thread in C#?

// 1. Thread with ThreadStart delegate (no parameters)
var t1 = new Thread(DoWork);
t1.Name         = "WorkerThread";
t1.IsBackground = true; // daemon — terminates when main thread exits
t1.Priority     = ThreadPriority.Normal;
t1.Start();
t1.Join(); // block caller until t1 finishes

void DoWork() => Console.WriteLine($"Running on thread {Thread.CurrentThread.ManagedThreadId}");

// 2. Thread with lambda
var t2 = new Thread(() =>
{
    Console.WriteLine("Lambda thread");
    Thread.Sleep(100); // simulate work
});
t2.Start();

// 3. ParameterizedThreadStart — pass a single object parameter
var t3 = new Thread(param =>
{
    string msg = (string)param!;
    Console.WriteLine($"Message: {msg}");
});
t3.Start("Hello from parameter");

// 4. Type-safe parameter passing via closure (preferred over ParameterizedThreadStart)
int workerId = 42;
string taskName = "ImportJob";
var t4 = new Thread(() =>
{
    // captures workerId and taskName — fully type-safe
    Console.WriteLine($"Worker {workerId}: {taskName}");
});
t4.Start();

// 5. Foreground vs background threads
// Foreground (default): app stays alive until ALL foreground threads finish
// Background: app can exit even if background threads are still running
var fg = new Thread(() => Thread.Sleep(5000)) { IsBackground = false }; // keeps app alive
var bg = new Thread(() => Thread.Sleep(5000)) { IsBackground = true  }; // doesn\'t block exit

// 6. Preferred modern alternative: Task.Run
await Task.Run(() => Console.WriteLine("Preferred: Task on thread pool"));
↥ back to top

Q. Why does a delegate need to be passed to the Thread constructor, and how do you pass parameters type-safely?

The Thread constructor requires a delegate (ThreadStart or ParameterizedThreadStart) because a thread needs to know which method to execute. The delegate is the entry point.

// ThreadStart — no parameters, no return value
ThreadStart start = DoWork;
var t1 = new Thread(start);
t1.Start();

void DoWork() => Console.WriteLine("No params");

// ParameterizedThreadStart — one object parameter (not type-safe)
ParameterizedThreadStart paramStart = obj =>
{
    int value = (int)obj!; // manual cast — runtime error if wrong type
    Console.WriteLine($"Value: {value}");
};
var t2 = new Thread(paramStart);
t2.Start(100); // pass object

// … Type-safe approach — closure over strongly-typed variables
int id    = 7;
string name = "Alice";
var t3 = new Thread(() =>
{
    // id and name captured by reference — fully type-safe, no casting
    Console.WriteLine($"Worker {id}: {name}");
});
t3.Start();

// … Pass a typed object via closure
record WorkItem(int Id, string Name, DateTime Due);
var item = new WorkItem(1, "Report", DateTime.Today);
var t4 = new Thread(() =>
{
    Console.WriteLine($"Processing {item.Name} (due {item.Due:d})");
});
t4.Start();
t4.Join();

// Retrieving data from a thread — use a shared variable + lock, or Task<T>
int result = 0;
var t5 = new Thread(() => result = Compute()); // write result inside thread
t5.Start();
t5.Join();
Console.WriteLine($"Result: {result}"); // safe to read after Join()

int Compute() => 42;

// Preferred: Task<T> — return values built-in, no shared variable needed
int taskResult = await Task.Run(() => Compute());
Console.WriteLine($"Task result: {taskResult}");
↥ back to top

Q. What is the difference between Thread.Join and Thread.Sleep? What are Thread.IsAlive and Thread.Join?

  Thread.Join Thread.Sleep
Blocks The calling thread The current thread
Until The target thread finishes The timeout elapses
Purpose Wait for another thread Pause execution temporarily
Returns bool (overload with timeout) void
var worker = new Thread(() =>
{
    Console.WriteLine("Worker started");
    Thread.Sleep(500); // pause this thread for 500 ms
    Console.WriteLine("Worker done");
});

worker.Start();
Console.WriteLine($"Worker alive: {worker.IsAlive}"); // true

// Join() — main thread blocks here until worker finishes
bool finished = worker.Join(timeout: TimeSpan.FromSeconds(2));
Console.WriteLine($"Finished in time: {finished}");   // true
Console.WriteLine($"Worker alive: {worker.IsAlive}"); // false

// Thread.Sleep(0) — yield to other threads of equal or higher priority
Thread.Sleep(0);

// Thread.Sleep(Timeout.Infinite) — sleep until interrupted
// Thread.Interrupt() — throws ThreadInterruptedException in sleeping/waiting thread

// IsAlive — true after Start() and before the thread method returns
var t = new Thread(() => Thread.Sleep(200));
Console.WriteLine(t.IsAlive); // false — not started yet
t.Start();
Console.WriteLine(t.IsAlive); // true  — running
t.Join();
Console.WriteLine(t.IsAlive); // false — completed
↥ back to top

Q. What are the different states of a Thread in C#?

Thread.ThreadState is a flags enum — a thread can be in multiple states simultaneously.

State Meaning
Unstarted Created but Start() not yet called
Running Actively executing
WaitSleepJoin Blocked in Sleep, Wait, Join, or a lock
Background IsBackground = true
Stopped Completed or terminated
AbortRequested Abort() was called (removed in .NET Core)
Suspended Suspend() was called (removed in .NET Core)
var t = new Thread(() =>
{
    Console.WriteLine("Working...");
    Thread.Sleep(300);
});

Console.WriteLine(t.ThreadState); // Unstarted
t.Start();
Console.WriteLine(t.ThreadState); // Running | (possibly Background)
Thread.Sleep(50);
Console.WriteLine(t.ThreadState); // WaitSleepJoin
t.Join();
Console.WriteLine(t.ThreadState); // Stopped

// Prefer checking IsAlive over ThreadState for simple checks
// ThreadState is mostly useful for diagnostics
↥ back to top

Q. What is the ThreadPool class and how is it used?

The ThreadPool is a pool of pre-created worker threads managed by the CLR. It avoids the overhead of creating and destroying threads for each short-lived task.

// 1. QueueUserWorkItem — fire and forget (avoid in modern code)
ThreadPool.QueueUserWorkItem(_ => Console.WriteLine("Pool work item"));

// 2. Get/set pool limits
ThreadPool.GetMinThreads(out int minWorker, out int minIo);
ThreadPool.GetMaxThreads(out int maxWorker, out int maxIo);
Console.WriteLine($"Min workers: {minWorker}, Max workers: {maxWorker}");

// Set minimum threads (pre-warm the pool to avoid ramp-up latency)
ThreadPool.SetMinThreads(workerThreads: 8, completionPortThreads: 8);

// 3. Task.Run — the modern way to queue work on the thread pool
var task = Task.Run(() =>
{
    Console.WriteLine($"Pool thread: {Thread.CurrentThread.IsThreadPoolThread}"); // true
    return 42;
});
int result = await task;

// 4. Parallel.ForEach — distributes iterations across pool threads
Parallel.ForEach(Enumerable.Range(1, 10), i =>
    Console.WriteLine($"Item {i} on thread {Thread.CurrentThread.ManagedThreadId}"));

// 5. Long-running work should NOT use the thread pool
// Use TaskCreationOptions.LongRunning to get a dedicated thread instead
var longTask = Task.Factory.StartNew(() =>
{
    while (true) { /* background service */ Thread.Sleep(1000); }
}, TaskCreationOptions.LongRunning);
↥ back to top

Q. What are Task and async/await in C#?

A Task represents an asynchronous operation that may return a value (Task<T>). async/await is syntactic sugar that lets you write asynchronous code in a sequential, readable style.

// Task — represents an ongoing or completed operation
Task t = Task.Run(() => Console.WriteLine("Fire and forget"));
Task<int> t2 = Task.Run(() => 42);
int value = await t2; // await suspends the caller, not the thread

// async/await — I/O-bound (no thread blocked)
async Task<string> GetDataAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url); // no thread blocked during HTTP call
}

// CPU-bound — offload to thread pool via Task.Run
async Task<int> ComputeAsync(int n)
{
    return await Task.Run(() =>
    {
        int sum = 0;
        for (int i = 0; i < n; i++) sum += i;
        return sum;
    });
}

// Run multiple tasks concurrently
var tasks = new[] { GetDataAsync("https://httpbin.org/get"), GetDataAsync("https://example.com") };
string[] results = await Task.WhenAll(tasks);

// Task.WhenAny — proceed when the first completes
Task<string> first = await Task.WhenAny(tasks);
Console.WriteLine("First done");

// Return types
// Task        — async void equivalent (no result)
// Task<T>     — async with result
// ValueTask<T>— struct, avoids heap alloc for hot paths that often complete synchronously
// IAsyncEnumerable<T> — async stream

async IAsyncEnumerable<int> GenerateAsync()
{
    for (int i = 0; i < 5; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

await foreach (int n in GenerateAsync())
    Console.Write($"{n} "); // 0 1 2 3 4
↥ back to top

Q. What is the difference between Task.Run and Task.Factory.StartNew?

  Task.Run Task.Factory.StartNew
Introduced .NET 4.5 .NET 4.0
Unwraps nested tasks … Automatically Must call .Unwrap() manually
Default scheduler ThreadPool Current TaskScheduler
LongRunning option Not supported TaskCreationOptions.LongRunning
Recommended for CPU-bound short tasks Long-running or custom scheduler tasks
Simplicity Simpler, safer More flexible but verbose
// Task.Run — preferred for CPU-bound work on the thread pool
int result = await Task.Run(() =>
{
    int sum = Enumerable.Range(1, 1_000_000).Sum();
    return sum;
});
Console.WriteLine(result);

// Task.Factory.StartNew — needed for LongRunning
var longTask = Task.Factory.StartNew(() =>
{
    while (true)
    {
        Console.WriteLine("Background service tick");
        Thread.Sleep(1000);
    }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.DenyChildAttach);

// Task.Run + async lambda — automatically unwraps Task<Task>
int asyncResult = await Task.Run(async () =>
{
    await Task.Delay(100);
    return 42;
});

// Task.Factory.StartNew + async lambda — must unwrap manually
int manualResult = await await Task.Factory.StartNew(async () =>
{
    await Task.Delay(100);
    return 42;
}); // double-await because StartNew returns Task<Task<int>>

// Custom TaskScheduler (advanced — e.g., UI thread, limited concurrency)
var scheduler = new LimitedConcurrencyLevelTaskScheduler(maxDegreeOfParallelism: 2);
var factory   = new TaskFactory(scheduler);
await factory.StartNew(() => Console.WriteLine("Limited concurrency task"));
↥ back to top

Q. How do you handle exceptions in multithreaded applications?

// 1. await — exceptions propagate naturally
async Task ProcessAsync()
{
    try
    {
        await Task.Run(() => throw new InvalidOperationException("Task error"));
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine($"Caught: {ex.Message}");
    }
}
await ProcessAsync();

// 2. Task.WhenAll — AggregateException wraps all exceptions
var tasks = new[]
{
    Task.Run(() => throw new Exception("Error 1")),
    Task.Run(() => throw new Exception("Error 2")),
    Task.Run(() => Console.WriteLine("OK")),
};
try
{
    await Task.WhenAll(tasks);
}
catch // await unwraps first exception
{
    // Inspect all exceptions via the tasks themselves
    foreach (var t in tasks.Where(t => t.IsFaulted))
        Console.WriteLine(t.Exception!.InnerException!.Message);
}

// 3. Unhandled exceptions on raw Thread — must catch inside the thread
var thread = new Thread(() =>
{
    try
    {
        throw new Exception("Thread crash");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Thread caught: {ex.Message}");
    }
});
thread.Start();

// 4. Global unhandled exception handler (last resort)
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
    Console.WriteLine($"Unhandled: {(e.ExceptionObject as Exception)?.Message}");

TaskScheduler.UnobservedTaskException += (_, e) =>
{
    Console.WriteLine($"Unobserved task exception: {e.Exception.Message}");
    e.SetObserved(); // prevent crash
};

// 5. CancellationToken — not an exception per se, but related
var cts = new CancellationTokenSource();
try
{
    await Task.Run(() =>
    {
        cts.Token.ThrowIfCancellationRequested();
    }, cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Task was cancelled");
}
↥ back to top

Q. What is a deadlock and how can it be avoided? What are the four necessary conditions for deadlock?

A deadlock occurs when two or more threads are permanently blocked, each waiting for a resource held by the other.

Four necessary conditions (Coffman conditions):

Condition Meaning
Mutual Exclusion A resource is held exclusively by one thread
Hold and Wait A thread holds a resource while waiting for another
No Preemption Resources cannot be forcibly taken away
Circular Wait Thread A waits for Thread B, which waits for Thread A
// Classic deadlock — two threads lock in opposite orders
object lockA = new(), lockB = new();

var t1 = new Thread(() =>
{
    lock (lockA) { Thread.Sleep(50); lock (lockB) { Console.WriteLine("T1 done"); } }
});
var t2 = new Thread(() =>
{
    lock (lockB) { Thread.Sleep(50); lock (lockA) { Console.WriteLine("T2 done"); } }
});
// t1.Start(); t2.Start(); // would deadlock!

// Prevention strategies:

// 1. Consistent lock ordering — always acquire locks in the same order
var t3 = new Thread(() => { lock (lockA) { lock (lockB) { Console.WriteLine("T3 done"); } } });
var t4 = new Thread(() => { lock (lockA) { lock (lockB) { Console.WriteLine("T4 done"); } } });
t3.Start(); t4.Start();

// 2. Monitor.TryEnter with timeout — fail fast instead of blocking forever
bool got = false;
Monitor.TryEnter(lockA, TimeSpan.FromSeconds(1), ref got);
if (got)
{
    try { /* work */ }
    finally { Monitor.Exit(lockA); }
}
else
{
    Console.WriteLine("Could not acquire lock — skip or retry");
}

// 3. Avoid nested locks — redesign to use a single lock or lock-free structures

// 4. Use async/await — no thread is blocked waiting; deadlock risk eliminated
await Task.Run(() => { /* lock-free async work */ });
↥ back to top

Q. What is LiveLock?

A livelock occurs when two or more threads keep reacting to each other's actions — they are actively executing but making no progress. Unlike a deadlock, threads are not blocked; they just keep changing state in response to each other indefinitely.

// Simulated livelock — two threads keep "politely yielding" to each other
int sharedFlag = 0;
bool thread1Done = false, thread2Done = false;

var t1 = new Thread(() =>
{
    while (!thread1Done)
    {
        if (Interlocked.CompareExchange(ref sharedFlag, 1, 0) == 0)
        {
            Console.WriteLine("T1: doing work");
            Thread.Sleep(50);
            Interlocked.Exchange(ref sharedFlag, 0);
            thread1Done = true;
        }
        else
        {
            Console.WriteLine("T1: yielding"); // keeps yielding to T2
            Thread.Sleep(10);
        }
    }
});

var t2 = new Thread(() =>
{
    while (!thread2Done)
    {
        if (Interlocked.CompareExchange(ref sharedFlag, 2, 0) == 0)
        {
            Console.WriteLine("T2: doing work");
            Thread.Sleep(50);
            Interlocked.Exchange(ref sharedFlag, 0);
            thread2Done = true;
        }
        else
        {
            Console.WriteLine("T2: yielding"); // keeps yielding to T1
            Thread.Sleep(10);
        }
    }
});

// Prevention:
// - Add randomized back-off delays (Thread.Sleep(Random.Next(10, 100)))
// - Use a priority scheme — one thread gets precedence
// - Use proper lock-free algorithms (e.g., Interlocked operations)
↥ back to top

Q. What is the purpose of the lock statement in C#? What is the difference between lock and Interlocked?

The lock statement ensures mutual exclusion — only one thread can execute a guarded block at a time. It is syntactic sugar over Monitor.Enter / Monitor.Exit.

public class SafeCounter
{
    private readonly object _syncRoot = new();
    private int _count;

    public void Increment()
    {
        lock (_syncRoot) // only one thread at a time
        {
            _count++;
        }
    }

    public int Count
    {
        get { lock (_syncRoot) { return _count; } }
    }
}

var counter = new SafeCounter();
await Task.WhenAll(Enumerable.Range(0, 1000).Select(_ =>
    Task.Run(counter.Increment)));
Console.WriteLine(counter.Count); // always 1000

// What lock compiles to:
// Monitor.Enter(obj, ref lockTaken);
// try { ... } finally { if (lockTaken) Monitor.Exit(obj); }

//  Rules:
// - Lock on a private readonly object, never on 'this', string literals, or Type objects
// - Keep locked sections short
// - Never call unknown code inside a lock (can cause deadlock)

lock vs Interlocked:

  lock Interlocked
Use case Guard multi-statement critical sections Atomic operations on single variables
Overhead Higher (OS kernel object) Very low (CPU atomic instruction)
Operations Any code Increment, Decrement, Add, Exchange, CompareExchange, Read
// Interlocked — atomic operations, no lock needed for single-variable updates
int value = 0;

await Task.WhenAll(Enumerable.Range(0, 1000).Select(_ =>
    Task.Run(() => Interlocked.Increment(ref value))));
Console.WriteLine(value); // always 1000

// CompareExchange — optimistic locking / spin loop
int current, updated;
do
{
    current = value;
    updated = current + 10;
} while (Interlocked.CompareExchange(ref value, updated, current) != current);
Console.WriteLine(value);
↥ back to top

Q. What is the difference between Monitor and lock in C#? How do you use the Monitor class?

lock is shorthand for Monitor.Enter/Monitor.Exit. Monitor gives you additional control: TryEnter with timeout, Wait, Pulse, and PulseAll for thread signalling.

object sync = new();

// lock (compiles to Monitor internally)
lock (sync) { /* critical section */ }

// Monitor.Enter / Exit — explicit equivalent of lock
bool lockTaken = false;
try
{
    Monitor.Enter(sync, ref lockTaken);
    // critical section
}
finally
{
    if (lockTaken) Monitor.Exit(sync);
}

// Monitor.TryEnter — non-blocking, with timeout
bool acquired = Monitor.TryEnter(sync, TimeSpan.FromMilliseconds(500));
if (acquired)
{
    try { /* work */ }
    finally { Monitor.Exit(sync); }
}

// Monitor.Wait / Pulse — producer-consumer signalling
object buffer = new();
Queue<int> queue = new();

var producer = new Thread(() =>
{
    for (int i = 0; i < 5; i++)
    {
        lock (buffer)
        {
            queue.Enqueue(i);
            Console.WriteLine($"Produced: {i}");
            Monitor.Pulse(buffer); // wake one waiting thread
        }
        Thread.Sleep(100);
    }
});

var consumer = new Thread(() =>
{
    for (int i = 0; i < 5; i++)
    {
        lock (buffer)
        {
            while (queue.Count == 0)
                Monitor.Wait(buffer); // releases lock + waits for Pulse

            int item = queue.Dequeue();
            Console.WriteLine($"Consumed: {item}");
        }
    }
});

consumer.Start(); producer.Start();
consumer.Join(); producer.Join();
↥ back to top

Q. What is the Race condition?

A race condition occurs when the outcome of a program depends on the timing or ordering of thread execution. When two or more threads access shared data concurrently and at least one modifies it, without proper synchronization, the result is unpredictable.

// Race condition — unsynchronized increment
int counter = 0;

var tasks = Enumerable.Range(0, 1000)
    .Select(_ => Task.Run(() => counter++)) // NOT atomic: read + add + write
    .ToArray();
await Task.WhenAll(tasks);
Console.WriteLine(counter); // may be < 1000 — race condition!

// Strategies to prevent race conditions:

// 1. lock — guard the critical section
int safeCounter = 0;
object sync = new();
await Task.WhenAll(Enumerable.Range(0, 1000)
    .Select(_ => Task.Run(() => { lock (sync) safeCounter++; })));
Console.WriteLine(safeCounter); // always 1000

// 2. Interlocked — atomic update for simple variables
int atomicCounter = 0;
await Task.WhenAll(Enumerable.Range(0, 1000)
    .Select(_ => Task.Run(() => Interlocked.Increment(ref atomicCounter))));
Console.WriteLine(atomicCounter); // always 1000

// 3. Concurrent collections — thread-safe without manual locking
var bag = new System.Collections.Concurrent.ConcurrentBag<int>();
await Task.WhenAll(Enumerable.Range(0, 1000)
    .Select(i => Task.Run(() => bag.Add(i))));
Console.WriteLine(bag.Count); // always 1000

// 4. Immutable data / local variables — no sharing = no race
var results = await Task.WhenAll(
    Enumerable.Range(1, 4).Select(i => Task.Run(() => i * i)));
Console.WriteLine(string.Join(", ", results)); // 1, 4, 9, 16
↥ back to top

Q. What happens if shared resources are not protected from concurrent access? How do you protect shared resources?

Without protection: data corruption, torn reads/writes, stale caches, non-deterministic results.

// Unprotected — torn write (int64 may not be atomically written on 32-bit)
long shared = 0;
// multiple threads writing concurrently = undefined behavior

// Protection options (choose based on scenario):

// 1. lock — simplest, general purpose
private readonly object _lock = new();
private int _state;
public void Update(int value) { lock (_lock) { _state = value; } }
public int  Read()            { lock (_lock) { return _state;  } }

// 2. Interlocked — atomic single-variable ops (fastest)
private int _count;
public void Increment() => Interlocked.Increment(ref _count);
public int  Count       => Interlocked.CompareExchange(ref _count, 0, 0); // atomic read

// 3. ReaderWriterLockSlim — multiple readers OR one writer
private readonly ReaderWriterLockSlim _rwLock = new();
private Dictionary<int, string> _cache = new();

public string? Get(int key)
{
    _rwLock.EnterReadLock();
    try { return _cache.TryGetValue(key, out var v) ? v : null; }
    finally { _rwLock.ExitReadLock(); }
}
public void Set(int key, string val)
{
    _rwLock.EnterWriteLock();
    try { _cache[key] = val; }
    finally { _rwLock.ExitWriteLock(); }
}

// 4. Concurrent collections — thread-safe without explicit locks
var dict  = new System.Collections.Concurrent.ConcurrentDictionary<int, string>();
var queue = new System.Collections.Concurrent.ConcurrentQueue<int>();
var stack = new System.Collections.Concurrent.ConcurrentStack<int>();
var bag   = new System.Collections.Concurrent.ConcurrentBag<int>();

// 5. Volatile — prevent CPU/compiler reordering for simple flags
private volatile bool _running = true;
public void Stop() => _running = false; // visible immediately to all threads

// 6. Channels (System.Threading.Channels) — async-friendly message passing
var channel = System.Threading.Channels.Channel.CreateBounded<int>(100);
await channel.Writer.WriteAsync(42);
int item = await channel.Reader.ReadAsync();
↥ back to top

Q. What is synchronization and why is it important? Can you name the synchronization primitives in .NET?

Synchronization is the coordination of threads to ensure correct access to shared resources, prevent race conditions, and establish ordering guarantees.

Why it matters: Without synchronization, concurrent threads can produce corrupted data, deadlocks, or non-deterministic behaviour.

Synchronization primitives in .NET:

Primitive Use case
lock / Monitor Mutual exclusion for any code block
Mutex Cross-process mutual exclusion
Semaphore / SemaphoreSlim Limit concurrent access to N threads
ManualResetEvent / ManualResetEventSlim Signal multiple waiting threads at once
AutoResetEvent Signal one waiting thread, then auto-reset
CountdownEvent Wait until N operations have completed
Barrier Synchronize N threads at a phase boundary
ReaderWriterLockSlim Multiple readers / exclusive writer
SpinLock Busy-wait for very short critical sections
SpinWait Spinning with back-off before yielding
Interlocked Atomic operations on primitive variables
volatile Visibility guarantee for simple flags
SemaphoreSlim (async) WaitAsync() — async-friendly throttling
Channel<T> Async-safe producer/consumer messaging
// Choosing the right primitive:
// Short critical section on same machine ’ lock
// Need timeout / TryEnter             ’ Monitor.TryEnter
// Limit concurrency (e.g., DB pool)   ’ SemaphoreSlim
// Signal all waiting threads           ’ ManualResetEventSlim
// Signal one thread, auto-reset        ’ AutoResetEvent
// Count-down to zero                   ’ CountdownEvent
// Phase-by-phase parallel work         ’ Barrier
// Concurrent reads, rare writes        ’ ReaderWriterLockSlim
// Nanosecond-critical inner loops      ’ SpinLock
// Cross-process lock                   ’ Mutex

using var slim = new SemaphoreSlim(initialCount: 3, maxCount: 3);
var tasks = Enumerable.Range(0, 10).Select(async i =>
{
    await slim.WaitAsync();
    try
    {
        Console.WriteLine($"Task {i} running (max 3 concurrent)");
        await Task.Delay(200);
    }
    finally { slim.Release(); }
});
await Task.WhenAll(tasks);
↥ back to top

Q. What is AutoResetEvent and how is it different from ManualResetEvent?

Both derive from EventWaitHandle and allow threads to signal each other.

  AutoResetEvent ManualResetEvent / ManualResetEventSlim
Reset Automatically after releasing one waiting thread Must call Reset() manually
Releases Exactly one thread per Set() call All waiting threads when Set() is called
State Like a turnstile — one thread passes, gate closes Like a gate — open for all until closed
Use case Worker thread signalling (one-at-a-time) Broadcast event (all threads proceed)
// AutoResetEvent — one producer signals one consumer at a time
using var are = new AutoResetEvent(initialState: false);

var producer = new Thread(() =>
{
    for (int i = 0; i < 3; i++)
    {
        Thread.Sleep(300);
        Console.WriteLine($"Produced {i}");
        are.Set(); // releases exactly one waiting thread
    }
});

var consumer = new Thread(() =>
{
    for (int i = 0; i < 3; i++)
    {
        are.WaitOne(); // blocks until Set() — auto-resets after waking
        Console.WriteLine($"Consumed {i}");
    }
});

producer.Start(); consumer.Start();
producer.Join();  consumer.Join();

// ManualResetEventSlim — broadcast to ALL waiting threads
using var mre = new ManualResetEventSlim(initialState: false);

var workers = Enumerable.Range(0, 4).Select(i => new Thread(() =>
{
    mre.Wait(); // all four threads block here
    Console.WriteLine($"Worker {i} released");
})).ToList();

workers.ForEach(w => w.Start());
Thread.Sleep(200);
mre.Set();  // releases ALL four workers simultaneously
mre.Reset(); // close gate again for next round
workers.ForEach(w => w.Join());
↥ back to top

Q. What is the Semaphore? What is Mutex and how does it differ from other synchronization mechanisms?

Semaphore limits how many threads can access a resource simultaneously. SemaphoreSlim is the lightweight, async-friendly version recommended for most in-process scenarios.

Mutex is like a lock but works across processes and is owned by the thread that acquired it.

// SemaphoreSlim — limit concurrency to N threads (async-friendly)
using var sem = new SemaphoreSlim(initialCount: 2, maxCount: 2);

var tasks = Enumerable.Range(0, 6).Select(async i =>
{
    await sem.WaitAsync();
    try
    {
        Console.WriteLine($"  [{i}] entered (max 2 concurrent)");
        await Task.Delay(300);
        Console.WriteLine($"  [{i}] leaving");
    }
    finally { sem.Release(); }
});
await Task.WhenAll(tasks);

// Semaphore (kernel-level, cross-thread/process)
using var kernelSem = new Semaphore(initialCount: 1, maximumCount: 1, name: "MyAppSemaphore");
kernelSem.WaitOne();
try { /* exclusive access */ }
finally { kernelSem.Release(); }

// Mutex — cross-process mutual exclusion
using var mutex = new Mutex(initiallyOwned: false, name: "Global\\MyAppMutex");

// Single-instance app pattern
bool createdNew;
using var singleInstance = new Mutex(initiallyOwned: true, name: "Global\\MyApp", createdNew: out createdNew);
if (!createdNew)
{
    Console.WriteLine("Another instance is already running.");
    return;
}
// Only one instance reaches here

Comparison:

  lock Mutex SemaphoreSlim
Scope In-process Cross-process In-process
Max holders 1 1 N (configurable)
Async     WaitAsync
Overhead Low High (kernel) Low
Thread-affinity Yes Yes No
↥ back to top

Q. What is the volatile keyword?

volatile tells the compiler and CPU that a field may be changed by multiple threads, preventing caching of the variable in a CPU register and disabling certain compiler/CPU reordering optimisations.

// Without volatile — compiler may cache _running in a register
// and the loop never sees the update from another thread
public class Processor
{
    private volatile bool _running = true; // volatile ensures visibility

    public void Run()
    {
        while (_running) // reads from memory each iteration, not a register
        {
            // process work...
        }
        Console.WriteLine("Stopped cleanly");
    }

    public void Stop() => _running = false; // immediately visible to Run()
}

// volatile is appropriate for:
// - Simple flags (bool, int, reference)
// - Sentinel values checked in a spin loop

// volatile is NOT appropriate for:
// - Compound operations (check + set, read + increment) — use Interlocked or lock
// - Complex objects — use lock or Concurrent collections

// Difference: volatile vs Interlocked vs lock
//   volatile: prevents caching/reordering; does NOT make compound ops atomic
//   Interlocked: atomic operations on single primitives (Increment, CompareExchange)
//   lock: exclusive section — any code, any type, highest overhead

// Thread.MemoryBarrier — explicit full memory fence (advanced, rarely needed)
private int _data;
private volatile bool _ready;

public void Producer()
{
    _data = 42;
    Thread.MemoryBarrier(); // ensure _data write is visible before _ready write
    _ready = true;
}

public int Consumer()
{
    while (!_ready) Thread.SpinWait(1);
    Thread.MemoryBarrier();
    return _data; // guaranteed to see 42
}
↥ back to top

Q. What are the Interlocked functions?

Interlocked provides atomic operations on shared variables — safe without lock and with minimal overhead (single CPU instruction).

int counter = 0;
long total   = 0;

// Increment / Decrement — thread-safe ++ and --
Interlocked.Increment(ref counter);       // counter++
Interlocked.Decrement(ref counter);       // counter--
Console.WriteLine(counter);               // 0

// Add — thread-safe +=
Interlocked.Add(ref counter, 10);
Console.WriteLine(counter);               // 10

// Exchange — atomically sets value, returns old value
int previous = Interlocked.Exchange(ref counter, 100);
Console.WriteLine($"Was {previous}, now {counter}"); // Was 10, now 100

// CompareExchange — atomically: if (counter == expected) counter = newValue
// Returns the original value
int original = Interlocked.CompareExchange(ref counter, newValue: 200, comparand: 100);
Console.WriteLine($"Original: {original}, Counter: {counter}"); // Original: 100, Counter: 200

// Read — atomic read of a long on 32-bit systems
long atomicRead = Interlocked.Read(ref total);

// Or (C# 9+)
Interlocked.Or(ref counter,  0b1111); // bitwise OR
Interlocked.And(ref counter, 0b1010); // bitwise AND

// Practical: lock-free spin-based update
int value = 0;
int current, newVal;
do
{
    current = value;
    newVal  = current * 2 + 1;
} while (Interlocked.CompareExchange(ref value, newVal, current) != current);
Console.WriteLine(value); // 1

// Practical: reference swap
string? sharedRef = "initial";
string? old = Interlocked.Exchange(ref sharedRef, "updated");
Console.WriteLine($"Was '{old}', now '{sharedRef}'");
↥ back to top

Q. How can you share data between multiple threads?

// 1. Shared field with lock — simplest and most common
public class SharedState
{
    private readonly object _lock = new();
    private List<string> _items = [];

    public void Add(string item)    { lock (_lock) { _items.Add(item); } }
    public List<string> Snapshot()  { lock (_lock) { return [.._items]; } }
}

// 2. Concurrent collections — no manual lock needed
var dict  = new System.Collections.Concurrent.ConcurrentDictionary<string, int>();
var queue = new System.Collections.Concurrent.ConcurrentQueue<string>();
var bag   = new System.Collections.Concurrent.ConcurrentBag<int>();

await Task.WhenAll(
    Task.Run(() => dict.TryAdd("key1", 1)),
    Task.Run(() => dict.TryAdd("key2", 2)));

// 3. Channel<T> — async-safe producer/consumer (preferred in .NET 5+)
var channel = System.Threading.Channels.Channel.CreateUnbounded<int>();

var producer = Task.Run(async () =>
{
    for (int i = 0; i < 5; i++)
    {
        await channel.Writer.WriteAsync(i);
        Console.WriteLine($"Sent: {i}");
    }
    channel.Writer.Complete();
});

var consumer = Task.Run(async () =>
{
    await foreach (int item in channel.Reader.ReadAllAsync())
        Console.WriteLine($"Received: {item}");
});

await Task.WhenAll(producer, consumer);

// 4. ThreadLocal<T> — per-thread copy (not shared, but partitions data)
var localRng = new ThreadLocal<Random>(() => new Random());
await Task.WhenAll(Enumerable.Range(0, 4).Select(_ =>
    Task.Run(() => Console.WriteLine(localRng.Value!.Next(100)))));

// 5. Immutable shared data — safest (no synchronization needed)
// Prefer record types and ImmutableList<T>, ImmutableDictionary<T,V>
using System.Collections.Immutable;
ImmutableList<int> immutable = ImmutableList<int>.Empty.Add(1).Add(2);
// Any thread can read immutable safely; Add() returns a new list
↥ back to top

Q. How do you implement a producer-consumer scenario in C#?

using System.Threading.Channels;

// … Modern approach: Channel<T> (preferred in .NET 5+)
var channel = Channel.CreateBounded<int>(capacity: 10);

async Task ProduceAsync()
{
    for (int i = 0; i < 20; i++)
    {
        await channel.Writer.WriteAsync(i);
        Console.WriteLine($"Produced: {i}");
        await Task.Delay(50);
    }
    channel.Writer.Complete();
}

async Task ConsumeAsync(int id)
{
    await foreach (int item in channel.Reader.ReadAllAsync())
    {
        Console.WriteLine($"Consumer {id} got: {item}");
        await Task.Delay(120);
    }
}

// One producer, two consumers
await Task.WhenAll(
    ProduceAsync(),
    ConsumeAsync(1),
    ConsumeAsync(2));

// Alternative: BlockingCollection<T> (older, synchronous API)
var collection = new System.Collections.Concurrent.BlockingCollection<int>(boundedCapacity: 5);

var producer = Task.Run(() =>
{
    for (int i = 0; i < 10; i++)
    {
        collection.Add(i); // blocks if full
        Console.WriteLine($"Added: {i}");
    }
    collection.CompleteAdding();
});

var consumer = Task.Run(() =>
{
    foreach (int item in collection.GetConsumingEnumerable()) // blocks if empty
        Console.WriteLine($"Consumed: {item}");
});

await Task.WhenAll(producer, consumer);
↥ back to top

Q. What is the CancellationToken and how is it used in multithreading?

CancellationToken provides a cooperative cancellation model — the producer (caller) signals cancellation; the consumer (worker) checks and responds to it. No thread is forcibly aborted.

// 1. Basic usage
using var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

var task = Task.Run(async () =>
{
    for (int i = 0; i < 100; i++)
    {
        token.ThrowIfCancellationRequested(); // throws OperationCanceledException
        Console.WriteLine($"Working {i}");
        await Task.Delay(100, token); // also cancellable
    }
}, token);

await Task.Delay(350);
cts.Cancel(); // signal cancellation

try   { await task; }
catch (OperationCanceledException) { Console.WriteLine("Task cancelled"); }

// 2. Timeout cancellation
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
// CancellationTokenSource.CreateLinkedTokenSource — combine multiple tokens
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
    cts.Token, timeoutCts.Token);

// 3. Register a callback on cancellation
linkedCts.Token.Register(() => Console.WriteLine("Cleanup on cancellation"));

// 4. Check without throwing
if (token.IsCancellationRequested)
{
    Console.WriteLine("Cancelled (non-throwing check)");
    return;
}

// 5. Pass to .NET APIs — most async methods accept CancellationToken
using var httpClient = new HttpClient();
try
{
    using var newCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
    string data = await httpClient.GetStringAsync("https://example.com", newCts.Token);
}
catch (TaskCanceledException) { Console.WriteLine("HTTP request timed out"); }

// 6. Thread-based (non-async) polling
void LongWork(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        Thread.Sleep(100); // do work
    }
    ct.ThrowIfCancellationRequested();
}
↥ back to top

Q. How do you use Concurrent collections in C#?

System.Collections.Concurrent provides thread-safe collections that avoid explicit lock statements.

using System.Collections.Concurrent;

// ConcurrentDictionary<TKey, TValue>
var dict = new ConcurrentDictionary<string, int>();
dict.TryAdd("Alice", 100);
dict.AddOrUpdate("Alice", 100, (key, old) => old + 50); // atomic update
int val = dict.GetOrAdd("Bob", key => 200);             // atomic get-or-add
Console.WriteLine(dict["Alice"]); // 150

// ConcurrentQueue<T> — FIFO, lock-free
var queue = new ConcurrentQueue<int>();
Parallel.For(0, 10, i => queue.Enqueue(i));
while (queue.TryDequeue(out int item))
    Console.Write($"{item} ");
Console.WriteLine();

// ConcurrentStack<T> — LIFO
var stack = new ConcurrentStack<int>();
stack.PushRange([1, 2, 3, 4, 5]);
if (stack.TryPop(out int top)) Console.WriteLine($"Popped: {top}"); // 5

// ConcurrentBag<T> — unordered, optimised for same-thread add/take
var bag = new ConcurrentBag<int>();
await Task.WhenAll(Enumerable.Range(0, 100).Select(i =>
    Task.Run(() => bag.Add(i))));
Console.WriteLine($"Bag count: {bag.Count}"); // 100

// BlockingCollection<T> — bounded buffer with blocking Add/Take
var bounded = new BlockingCollection<int>(boundedCapacity: 5);

var prod = Task.Run(() =>
{
    for (int i = 0; i < 10; i++)
    {
        bounded.Add(i);                         // blocks when full
        Console.WriteLine($"Produced: {i}");
    }
    bounded.CompleteAdding();
});

var cons = Task.Run(() =>
{
    foreach (int n in bounded.GetConsumingEnumerable()) // blocks when empty
        Console.WriteLine($"Consumed: {n}");
});

await Task.WhenAll(prod, cons);
↥ back to top

Q. What is the difference between Parallel.For and Task.Run?

  Parallel.For / Parallel.ForEach Task.Run
Purpose Data parallelism — divide a collection across cores Run a single unit of work asynchronously
Blocking Blocks the calling thread until all iterations complete Non-blocking — returns a Task
Partitioning Automatic (Partitioner) Manual
Degree of parallelism MaxDegreeOfParallelism option Manual via SemaphoreSlim
Use case CPU-bound loops over data Single async or CPU-bound job
// Parallel.For — best for CPU-bound data processing
var results = new int[10];
Parallel.For(0, 10, new ParallelOptions { MaxDegreeOfParallelism = 4 }, i =>
{
    results[i] = i * i; // safe because each i writes to a different index
    Console.WriteLine($"i={i} on thread {Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine(string.Join(", ", results));

// Parallel.ForEach
var files = Directory.GetFiles(".", "*.cs");
Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 2 }, file =>
{
    int lines = File.ReadLines(file).Count();
    Console.WriteLine($"{Path.GetFileName(file)}: {lines} lines");
});

// Task.Run — single async unit of work
var task = Task.Run(() =>
{
    long sum = 0;
    for (long i = 0; i < 1_000_000; i++) sum += i;
    return sum;
});
Console.WriteLine(await task);

//  Parallel.For with async — use Parallel.ForEachAsync (.NET 6+)
await Parallel.ForEachAsync(files, new ParallelOptions { MaxDegreeOfParallelism = 4 },
    async (file, ct) =>
    {
        string content = await File.ReadAllTextAsync(file, ct);
        Console.WriteLine($"{Path.GetFileName(file)}: {content.Length} chars");
    });
↥ back to top

Q. What are the advantages and disadvantages of multithreading?

Advantages:

Advantage Detail
Improved throughput Utilize multiple CPU cores for CPU-bound work
Responsiveness UI thread stays responsive while background work runs
Parallelism Independent tasks run simultaneously
Better resource utilisation Threads run while others wait on I/O
Scalability Scale to available hardware cores

Disadvantages:

Disadvantage Detail
Complexity Harder to design, debug, and reason about
Race conditions Unsynchronized shared state leads to bugs
Deadlocks / livelocks Threads block each other permanently
Overhead Context switches, synchronization, memory
Difficult testing Bugs are timing-dependent and non-reproducible
Priority inversion High-priority thread blocked by low-priority one
// When to use multithreading:
// … CPU-bound: image processing, data crunching, compression
// … Parallel independent tasks: batch file processing
// … Background work: keep UI responsive
// … I/O-bound: async/await without dedicated threads

// When to AVOID:
//  Simple sequential logic — adds complexity with no benefit
//  Shared state that\'s complex to synchronize
//  Very short tasks — thread creation overhead exceeds benefit

// Modern guideline:
// CPU-bound: Parallel.For, Parallel.ForEachAsync, Task.Run
// I/O-bound: async/await (no extra threads needed)
// Producer/consumer: Channel<T>
// Avoid raw Thread() — use Task-based APIs instead
↥ back to top

Q. How does multithreading improve performance over a single-threaded solution?

// Single-threaded: tasks run sequentially — total time = sum of each
var sw = System.Diagnostics.Stopwatch.StartNew();

int r1 = HeavyCompute(1);
int r2 = HeavyCompute(2);
int r3 = HeavyCompute(3);
int r4 = HeavyCompute(4);

sw.Stop();
Console.WriteLine($"Sequential: {sw.ElapsedMilliseconds} ms, results: {r1+r2+r3+r4}");

// Multi-threaded: tasks run in parallel — total time  max of each
sw.Restart();

int[] results = await Task.WhenAll(
    Task.Run(() => HeavyCompute(1)),
    Task.Run(() => HeavyCompute(2)),
    Task.Run(() => HeavyCompute(3)),
    Task.Run(() => HeavyCompute(4)));

sw.Stop();
Console.WriteLine($"Parallel:   {sw.ElapsedMilliseconds} ms, results: {results.Sum()}");

int HeavyCompute(int seed)
{
    Thread.Sleep(500); // simulate 500 ms CPU work
    return seed * seed;
}
// Sequential: ~2000 ms
// Parallel:   ~500 ms  (4x speedup on 4+ cores)

// I/O-bound: async/await saves threads entirely
sw.Restart();
var fetches = Enumerable.Range(1, 4).Select(i =>
    Task.Run(() => { Thread.Sleep(300); return i; })); // simulate I/O
int[] ioResults = await Task.WhenAll(fetches);
sw.Stop();
Console.WriteLine($"Async I/O: {sw.ElapsedMilliseconds} ms"); // ~300 ms

// Amdahl\'s Law: speedup is limited by the sequential portion
// If 20% of code is sequential, max speedup = 1 / 0.2 = 5x regardless of cores
↥ back to top

Q. When should multithreading be used and when should it be avoided in C#?

// … USE multithreading when:

// 1. CPU-bound parallel work — multiple independent CPU-intensive tasks
var primes = await Task.Run(() =>
    Enumerable.Range(2, 1_000_000)
              .AsParallel()
              .Where(IsPrime)
              .Count());

// 2. UI responsiveness — background work while UI stays responsive
// (WPF/MAUI: always run long work off the UI thread)
await Task.Run(() => ProcessLargeFile("data.csv")); // off UI thread

// 3. I/O-bound parallelism — multiple concurrent HTTP/DB calls
var responses = await Task.WhenAll(
    httpClient.GetStringAsync("https://api1.example.com"),
    httpClient.GetStringAsync("https://api2.example.com"));

// 4. Background services — polling, cleanup, monitoring
var cts = new CancellationTokenSource();
Task bgService = Task.Factory.StartNew(async () =>
{
    while (!cts.Token.IsCancellationRequested)
    {
        await DoMaintenanceAsync();
        await Task.Delay(TimeSpan.FromMinutes(5), cts.Token);
    }
}, TaskCreationOptions.LongRunning);

//  AVOID multithreading when:

// 1. Simple sequential logic — no gain, only complexity
// BAD:
int badResult = await Task.Run(() => 2 + 2);

// GOOD:
int goodResult = 2 + 2;

// 2. Tasks are too short — thread overhead > benefit
// BAD: threading a 1 s operation
// GOOD: batch small items, then parallelize the batch

// 3. Heavy shared state — if everything needs a lock, parallelism is lost

// 4. Ordering matters — parallel tasks don\'t preserve order

bool IsPrime(int n)
{
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0) return false;
    return true;
}
↥ back to top

Q. How can you ensure mutual exclusion without using lock or Monitor?

// 1. SemaphoreSlim(1,1) — async-compatible mutual exclusion
var sem = new SemaphoreSlim(1, 1);

async Task CriticalSectionAsync()
{
    await sem.WaitAsync(); // async — doesn\'t block a thread
    try { /* exclusive work */ await Task.Delay(100); }
    finally { sem.Release(); }
}

// 2. Mutex — cross-process mutual exclusion
using var mutex = new Mutex(false, "Global\\MyAppMutex");
mutex.WaitOne();
try { /* exclusive work */ }
finally { mutex.ReleaseMutex(); }

// 3. SpinLock — busy-wait for very short sections (no kernel transition)
var spinLock = new SpinLock(enableThreadOwnerTracking: false);
bool taken = false;
try
{
    spinLock.Enter(ref taken);
    // ultra-short critical section
    Console.WriteLine("SpinLock acquired");
}
finally { if (taken) spinLock.Exit(); }

// 4. Interlocked.CompareExchange — optimistic lock-free CAS
int lockFlag = 0;
while (Interlocked.CompareExchange(ref lockFlag, 1, 0) != 0)
    Thread.SpinWait(1); // spin until we set flag 0’1
try { /* exclusive work */ }
finally { Interlocked.Exchange(ref lockFlag, 0); }

// 5. ReaderWriterLockSlim — multiple readers, exclusive writer
var rwLock = new ReaderWriterLockSlim();
// Writer
rwLock.EnterWriteLock();
try { /* exclusive write */ }
finally { rwLock.ExitWriteLock(); }
// Reader
rwLock.EnterReadLock();
try { /* concurrent reads */ }
finally { rwLock.ExitReadLock(); }
↥ back to top

Q. Explain the difference between Barrier and CountdownEvent. Provide a real-world scenario for each.

  Barrier CountdownEvent
Purpose Synchronize N threads at each phase boundary Wait until N operations have signalled completion
Reusable … Automatically resets for each phase One-shot (or manually reset)
Participants Fixed at creation (can be added/removed) Count set at creation
Direction All threads wait for each other One thread waits; many threads signal
// Barrier — pipeline with phases
// Real-world: parallel rendering pipeline where all threads must finish
// Phase 1 (geometry) before any starts Phase 2 (shading)

int workers = 4;
using var barrier = new Barrier(participants: workers, postPhaseAction: b =>
    Console.WriteLine($"\n--- Phase {b.CurrentPhaseNumber + 1} complete ---\n"));

var tasks = Enumerable.Range(0, workers).Select(id => Task.Run(() =>
{
    Console.WriteLine($"Worker {id}: Phase 1 (geometry)");
    Thread.Sleep(Random.Shared.Next(100, 400));
    barrier.SignalAndWait(); // wait for all to finish Phase 1

    Console.WriteLine($"Worker {id}: Phase 2 (shading)");
    Thread.Sleep(Random.Shared.Next(100, 300));
    barrier.SignalAndWait(); // wait for all to finish Phase 2

    Console.WriteLine($"Worker {id}: Phase 3 (output)");
}));
await Task.WhenAll(tasks);

// CountdownEvent — wait for N async completions
// Real-world: download N files concurrently; proceed only when all are done

int fileCount = 5;
using var countdown = new CountdownEvent(initialCount: fileCount);

for (int i = 0; i < fileCount; i++)
{
    int fileId = i;
    Task.Run(() =>
    {
        Thread.Sleep(Random.Shared.Next(200, 600)); // simulate download
        Console.WriteLine($"File {fileId} downloaded");
        countdown.Signal(); // decrement the count
    });
}

countdown.Wait(); // block until count reaches 0
Console.WriteLine("All files downloaded — proceeding with processing");
↥ back to top

Q. What are the issues with Thread.Abort()? How do you gracefully stop a thread?

Thread.Abort() is removed in .NET Core / .NET 5+. It was unsafe because it injected a ThreadAbortException at an arbitrary point, potentially corrupting state, leaving locks acquired, or skipping finally blocks.

//  Thread.Abort — NOT available in .NET 5+
// var t = new Thread(...);
// t.Abort(); // throws PlatformNotSupportedException on .NET 5+

// … Graceful cancellation via CancellationToken (recommended)
using var cts = new CancellationTokenSource();

var worker = Task.Run(async () =>
{
    while (!cts.Token.IsCancellationRequested)
    {
        Console.WriteLine("Working...");
        await Task.Delay(300, cts.Token);
    }
    Console.WriteLine("Gracefully stopped");
}, cts.Token);

await Task.Delay(1000);
cts.Cancel(); // cooperative cancellation
try   { await worker; }
catch (OperationCanceledException) { Console.WriteLine("Task cancelled"); }

// … Volatile flag — simple polling (no Task)
public class BackgroundWorker
{
    private volatile bool _stop;
    private Thread? _thread;

    public void Start()
    {
        _thread = new Thread(() =>
        {
            while (!_stop)
            {
                Console.WriteLine("Tick");
                Thread.Sleep(200);
            }
            Console.WriteLine("Worker stopped");
        }) { IsBackground = true };
        _thread.Start();
    }

    public void Stop()
    {
        _stop = true;
        _thread?.Join(timeout: TimeSpan.FromSeconds(2));
    }
}

var bw = new BackgroundWorker();
bw.Start();
await Task.Delay(700);
bw.Stop();
↥ back to top

Q. How do you achieve thread synchronization using ReaderWriterLockSlim? What are its advantages over ReaderWriterLock?

ReaderWriterLockSlim allows multiple concurrent readers and exclusive writers, improving throughput for read-heavy workloads.

  ReaderWriterLock ReaderWriterLockSlim
Performance Slower Faster (optimised internals)
Recursive support Via flags Opt-in (LockRecursionPolicy)
Upgradeable read lock   EnterUpgradeableReadLock
Recommendation Legacy (avoid) … Use this
public class ThreadSafeCache<TKey, TValue> where TKey : notnull
{
    private readonly Dictionary<TKey, TValue> _dict = new();
    private readonly ReaderWriterLockSlim _lock = new();

    public TValue? Get(TKey key)
    {
        _lock.EnterReadLock(); // multiple readers concurrently
        try
        {
            return _dict.TryGetValue(key, out var val) ? val : default;
        }
        finally { _lock.ExitReadLock(); }
    }

    public void Set(TKey key, TValue value)
    {
        _lock.EnterWriteLock(); // exclusive — blocks all readers and writers
        try { _dict[key] = value; }
        finally { _lock.ExitWriteLock(); }
    }

    // Upgradeable lock — check then conditionally write (no double-locking)
    public TValue GetOrAdd(TKey key, Func<TKey, TValue> factory)
    {
        _lock.EnterUpgradeableReadLock();
        try
        {
            if (_dict.TryGetValue(key, out var existing)) return existing;

            _lock.EnterWriteLock(); // upgrade to write
            try
            {
                var value = factory(key);
                _dict[key] = value;
                return value;
            }
            finally { _lock.ExitWriteLock(); }
        }
        finally { _lock.ExitUpgradeableReadLock(); }
    }

    public void Dispose() => _lock.Dispose();
}

// Usage
var cache = new ThreadSafeCache<string, int>();
await Task.WhenAll(
    Task.Run(() => cache.Set("x", 42)),
    Task.Run(() => Console.WriteLine(cache.Get("x"))),
    Task.Run(() => Console.WriteLine(cache.GetOrAdd("y", _ => 99))));
↥ back to top

Q. Discuss the differences between volatile, Interlocked, and Thread.MemoryBarrier. When should each be used?

  volatile Interlocked Thread.MemoryBarrier
Prevents caching … (implicit) … (explicit fence)
Prevents reordering Partial (acquire/release) … (full fence)
Atomic compound ops    
Overhead Minimal Low (single CPU instruction) Low–Medium
Use case Simple flags; visibility Atomic read/modify/write Custom lock-free algorithms
// volatile — prevent caching of a simple flag
private volatile bool _shutdown = false;

void Worker()
{
    while (!_shutdown) { /* work */ }  // always reads from memory
}
void Stop() => _shutdown = true; // immediately visible

// Interlocked — atomic compound operation on a single variable
int counter = 0;
Interlocked.Increment(ref counter);               // atomic read + add + write
int old = Interlocked.Exchange(ref counter, 100); // atomic swap
int orig = Interlocked.CompareExchange(ref counter, 200, 100); // CAS

// Thread.MemoryBarrier — full memory fence in custom lock-free code
private int _data;
private int _flag;

void Produce()
{
    _data = 99;
    Thread.MemoryBarrier(); // STORE fence: _data write visible before _flag write
    _flag = 1;
}

int Consume()
{
    while (Volatile.Read(ref _flag) == 0) { } // spin
    Thread.MemoryBarrier(); // LOAD fence: _flag read before _data read
    return _data; // guaranteed to see 99
}

// Volatile.Read / Volatile.Write — explicit volatile semantics without field keyword
int val = Volatile.Read(ref _flag);
Volatile.Write(ref _flag, 1);

// Rule of thumb:
// One thread writes, one thread reads a simple flag  ’ volatile
// Atomic increment / compare-and-swap               ’ Interlocked
// Custom lock-free algorithm with ordering needs    ’ MemoryBarrier / Volatile.Read/Write
↥ back to top

Q. Explain thread-local storage and data partitioning in C# multithreading.

Thread-local storage (TLS) gives each thread its own private copy of a variable — no sharing, no synchronization needed.
Data partitioning divides a dataset into independent chunks and assigns each chunk to a separate thread.

// 1. ThreadLocal<T> — per-thread instance
var localRng = new ThreadLocal<Random>(() => new Random(), trackAllValues: true);

await Task.WhenAll(Enumerable.Range(0, 4).Select(i => Task.Run(() =>
{
    // Each thread has its own Random — no lock needed
    int roll = localRng.Value!.Next(1, 7);
    Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: rolled {roll}");
})));

// See all per-thread values
Console.WriteLine($"Instances created: {localRng.Values.Count}");
localRng.Dispose();

// 2. [ThreadStatic] — simpler but no initializer
[ThreadStatic] private static int _threadId;

// 3. Data partitioning — PLINQ
var numbers = Enumerable.Range(1, 10_000_000);
long sum = numbers.AsParallel()
                  .WithDegreeOfParallelism(4)
                  .Where(n => n % 2 == 0)
                  .Select(n => (long)n)
                  .Sum();
Console.WriteLine($"Sum of evens: {sum}");

// 4. Data partitioning — Parallel.For with thread-local accumulator (no shared state)
long total = 0;
Parallel.For(
    fromInclusive: 0L,
    toExclusive:   10_000_000L,
    localInit:    () => 0L,                          // per-thread local
    body:         (i, _, local) => local + i,        // accumulate locally
    localFinally: local => Interlocked.Add(ref total, local) // merge once
);
Console.WriteLine($"Parallel total: {total}");

// 5. Partitioner — custom partition strategy
var partitioner = Partitioner.Create(0, 10_000_000, rangeSize: 500_000);
Parallel.ForEach(partitioner, range =>
{
    long localSum = 0;
    for (long i = range.Item1; i < range.Item2; i++) localSum += i;
    Interlocked.Add(ref total, localSum);
});
↥ back to top

Q. How do you combine async/await with multithreading? How does TaskScheduler fit in?

async/await is primarily an I/O-bound model — it doesn't create new threads. When you need CPU-bound work alongside async, combine Task.Run with await. TaskScheduler controls where tasks execute.

// 1. CPU-bound work + async I/O together
async Task<int> ProcessFileAsync(string path, CancellationToken ct)
{
    // I/O-bound: no thread blocked
    string content = await File.ReadAllTextAsync(path, ct);

    // CPU-bound: offload to thread pool, don\'t block the async context
    int wordCount = await Task.Run(() => content.Split().Length, ct);
    return wordCount;
}

// 2. Concurrent CPU + I/O
var tasks = Directory.GetFiles(".", "*.cs")
    .Select(f => ProcessFileAsync(f, CancellationToken.None));
int[] counts = await Task.WhenAll(tasks);
Console.WriteLine($"Total words: {counts.Sum()}");

// 3. TaskScheduler — controls execution context
// Default: ThreadPoolTaskScheduler (Task.Run uses this)
// CurrentThread: runs on the current thread (synchronous; testing)
// LimitedConcurrency: caps concurrent tasks

public class LimitedConcurrencyLevelTaskScheduler(int maxParallelism)
    : TaskScheduler
{
    private readonly LinkedList<Task> _tasks = new();
    private int _running;

    protected override void QueueTask(Task task)
    {
        lock (_tasks) _tasks.AddLast(task);
        TryExecuteNextTask();
    }

    private void TryExecuteNextTask()
    {
        lock (_tasks)
        {
            if (_running >= maxParallelism || _tasks.Count == 0) return;
            _running++;
            var task = _tasks.First!.Value;
            _tasks.RemoveFirst();
            ThreadPool.QueueUserWorkItem(_ =>
            {
                TryExecuteTask(task);
                lock (_tasks) { _running--; TryExecuteNextTask(); }
            });
        }
    }

    protected override bool TryExecuteTaskInline(Task task, bool prev) => false;
    protected override IEnumerable<Task> GetScheduledTasks() { lock (_tasks) return [.._tasks]; }
}

var scheduler = new LimitedConcurrencyLevelTaskScheduler(maxParallelism: 2);
var factory   = new TaskFactory(CancellationToken.None,
    TaskCreationOptions.None, TaskContinuationOptions.None, scheduler);

await Task.WhenAll(Enumerable.Range(0, 6)
    .Select(i => factory.StartNew(() =>
    {
        Console.WriteLine($"Task {i} on thread {Thread.CurrentThread.ManagedThreadId}");
        Thread.Sleep(200);
    })));
↥ back to top

Q. What is parallelism? How do you control the degree of parallelism using the Parallel class?

Parallelism is executing multiple operations simultaneously on multiple CPU cores. Degree of parallelism (DOP) is how many threads/tasks run concurrently.

// Parallel.For with MaxDegreeOfParallelism
var options = new ParallelOptions
{
    MaxDegreeOfParallelism = 4,       // at most 4 threads
    CancellationToken      = CancellationToken.None,
};

var results = new int[20];
Parallel.For(0, 20, options, i =>
{
    results[i] = i * i;
    Console.WriteLine($"  [{i}] on thread {Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine(string.Join(", ", results));

// Parallel.ForEach — over collections
var files = Directory.EnumerateFiles(".", "*.cs").ToList();
Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 2 }, file =>
    Console.WriteLine($"{Path.GetFileName(file)}{new FileInfo(file).Length} bytes"));

// Parallel.ForEachAsync — async-compatible (.NET 6+)
await Parallel.ForEachAsync(files, new ParallelOptions { MaxDegreeOfParallelism = 3 },
    async (file, ct) =>
    {
        string content = await File.ReadAllTextAsync(file, ct);
        Console.WriteLine($"{Path.GetFileName(file)}: {content.Length} chars");
    });

// PLINQ — parallel LINQ
long sum = Enumerable.Range(1, 10_000_000)
    .AsParallel()
    .WithDegreeOfParallelism(Environment.ProcessorCount)
    .Where(n => n % 2 == 0)
    .Select(n => (long)n)
    .Sum();
Console.WriteLine($"Sum: {sum}");

// Choosing DOP:
// CPU-bound: Environment.ProcessorCount  (fully utilise all cores)
// I/O-bound: higher than CPU count is fine (threads spend time waiting)
// Mixed:     experiment; start with 2 — ProcessorCount for I/O

Console.WriteLine($"CPU cores: {Environment.ProcessorCount}");
↥ back to top

Q. Describe lock contention and how to mitigate it.

Lock contention occurs when multiple threads compete to acquire the same lock. The thread that can't acquire the lock blocks, waiting — wasting CPU time and reducing throughput.

// High contention — all threads fight for one lock
object sharedLock = new();
int counter = 0;

// BAD: all 1000 tasks contend on a single lock
await Task.WhenAll(Enumerable.Range(0, 1000).Select(_ =>
    Task.Run(() => { lock (sharedLock) counter++; })));

// … Mitigation 1: Interlocked — no lock needed for simple atomic ops
int atomicCounter = 0;
await Task.WhenAll(Enumerable.Range(0, 1000).Select(_ =>
    Task.Run(() => Interlocked.Increment(ref atomicCounter))));

// … Mitigation 2: Lock striping — partition data across multiple locks
const int Stripes = 16;
var locks    = Enumerable.Range(0, Stripes).Select(_ => new object()).ToArray();
var counters = new int[Stripes];

await Task.WhenAll(Enumerable.Range(0, 1000).Select(i =>
    Task.Run(() =>
    {
        int stripe = i % Stripes;
        lock (locks[stripe]) counters[stripe]++;
    })));
Console.WriteLine($"Total: {counters.Sum()}"); // 1000

// … Mitigation 3: ReaderWriterLockSlim — allow concurrent reads
var rwl = new ReaderWriterLockSlim();
var dict = new Dictionary<string, int> { ["key"] = 0 };

// Many readers can proceed simultaneously
await Task.WhenAll(Enumerable.Range(0, 100).Select(_ => Task.Run(() =>
{
    rwl.EnterReadLock();
    try { _ = dict["key"]; }
    finally { rwl.ExitReadLock(); }
})));

// … Mitigation 4: Reduce lock scope — keep critical section minimal
int result;
lock (sharedLock) result = counter; // read fast under lock
Console.WriteLine(ExpensiveProcess(result)); // heavy work OUTSIDE lock

// … Mitigation 5: ConcurrentDictionary — built-in lock striping
var cd = new System.Collections.Concurrent.ConcurrentDictionary<int, int>();
await Task.WhenAll(Enumerable.Range(0, 1000).Select(i =>
    Task.Run(() => cd.AddOrUpdate(i % 10, 1, (_, v) => v + 1))));

int ExpensiveProcess(int v) => v * 2;
↥ back to top

Q. What is lazy initialization in C# multithreading and how does it affect startup performance?

Lazy initialization defers the creation of an expensive object until it is first accessed. This reduces startup time and avoids allocating resources that may never be needed.

// 1. Lazy<T> — thread-safe by default (LazyThreadSafetyMode.ExecutionAndPublication)
var heavyService = new Lazy<DatabaseService>(() =>
{
    Console.WriteLine("Initializing DatabaseService...");
    return new DatabaseService("Server=localhost;");
});

Console.WriteLine("App started (no DB init yet)");
// DB not initialized until first .Value access
Console.WriteLine(heavyService.Value.Query("SELECT 1")); // initialized here
Console.WriteLine(heavyService.Value.Query("SELECT 2")); // reuses same instance

// 2. Thread-safety modes
var lazy1 = new Lazy<int>(() => 42,
    LazyThreadSafetyMode.ExecutionAndPublication); // default — safe, single init
var lazy2 = new Lazy<int>(() => 42,
    LazyThreadSafetyMode.PublicationOnly);          // allows multiple inits, first wins
var lazy3 = new Lazy<int>(() => 42,
    LazyThreadSafetyMode.None);                     // no thread safety — fastest, single-thread only

// 3. Lazy<T> in a service / singleton
public sealed class AppServices
{
    private static readonly Lazy<AppServices> _instance =
        new(() => new AppServices(), LazyThreadSafetyMode.ExecutionAndPublication);

    public static AppServices Instance => _instance.Value;
    private AppServices() { Console.WriteLine("AppServices initialized"); }
    public void DoWork() => Console.WriteLine("Working");
}

AppServices.Instance.DoWork(); // initialized on first access

// 4. LazyInitializer — static helper, struct-friendly (no wrapper object)
DatabaseService? _db = null;
DatabaseService db = LazyInitializer.EnsureInitialized(
    ref _db, () => new DatabaseService("Server=prod;"));

// 5. Impact on startup
// Without lazy: all services created at startup — slow, wastes memory for unused services
// With lazy:    only what\'s needed is created — faster startup, lower memory footprint

class DatabaseService(string connStr)
{
    public string Query(string sql)
    {
        Console.WriteLine($"Query [{sql}] on {connStr}");
        return "result";
    }
}
↥ back to top

Q. Explain SpinLock in C# multithreading. How does it differ from lock / Monitor?

SpinLock is a mutual exclusion primitive that busy-waits (spins) in a tight loop rather than yielding the thread to the OS. This avoids kernel transitions, making it faster for very short critical sections — but wasteful for longer ones.

  lock / Monitor SpinLock
Blocking Suspends thread (kernel sleep) Busy-wait (CPU spinning)
Best for Sections taking > ~1 s Sections taking < ~1 s
CPU usage while waiting Low (thread suspended) High (continuous spin)
Overhead per acquire Higher (kernel transition) Lower (no kernel call)
Struct Class struct — avoid copying
Thread affinity No Must release on same thread
// SpinLock usage
var spinLock = new SpinLock(enableThreadOwnerTracking: false);
int sharedCounter = 0;

await Task.WhenAll(Enumerable.Range(0, 1000).Select(_ => Task.Run(() =>
{
    bool taken = false;
    try
    {
        spinLock.Enter(ref taken); // busy-wait until acquired
        sharedCounter++;           // very short critical section
    }
    finally
    {
        if (taken) spinLock.Exit(useMemoryBarrier: false);
    }
})));

Console.WriteLine(sharedCounter); // 1000

// SpinWait — adaptive spinning with back-off (yield after many spins)
var sw = new SpinWait();
volatile bool ready = false;
Task.Run(() => { Thread.Sleep(100); ready = true; });

while (!ready)
    sw.SpinOnce(); // spins first, then yields, then sleeps
Console.WriteLine("Ready!");

// TryEnter — non-blocking
bool acquired = false;
spinLock.TryEnter(ref acquired);
if (acquired)
{
    try { /* work */ }
    finally { spinLock.Exit(); }
}

//  Rules:
// - Never use SpinLock for I/O-bound or blocking code
// - Never await inside a SpinLock (deadlock risk on thread pool)
// - Don\'t copy the SpinLock struct — always pass by ref
// - Use Interlocked instead when operating on a single variable
↥ back to top

Q. How are threads different from TPL?

  Raw Thread Task Parallel Library (TPL)
Abstraction Low-level OS thread High-level task abstraction
Thread reuse No — new thread each time Yes — reuses thread pool threads
Return values Not built-in Task<T> returns results
Exception handling Manual (inside thread body) Propagated via await / .Result
Cancellation Manual flag/volatile CancellationToken built-in
Async/await Not supported Native support
Composition Manual Join, no chaining WhenAll, WhenAny, continuations
Parallel loops Manual partitioning Parallel.For, Parallel.ForEach
Best for Long-running, dedicated background threads Everything else
// Thread — low-level, full control
var thread = new Thread(() =>
{
    Console.WriteLine($"Raw thread: {Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(200);
    Console.WriteLine("Thread done");
});
thread.IsBackground = true;
thread.Start();
thread.Join();

// TPL — high-level, composable, async-friendly
int result = await Task.Run(() =>
{
    Console.WriteLine($"TPL thread: {Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(200);
    return 42;
});
Console.WriteLine($"Task result: {result}");

// TPL continuation chaining
var pipeline = Task.Run(() => "raw data")
    .ContinueWith(t => t.Result.ToUpper())
    .ContinueWith(t => $"Processed: {t.Result}");
Console.WriteLine(await pipeline);

// TPL: parallel loop — 4 cores, no manual thread management
await Parallel.ForEachAsync(Enumerable.Range(0, 8), async (i, ct) =>
{
    await Task.Delay(100, ct);
    Console.WriteLine($"Item {i} done");
});

Q. What is the difference between Task and Thread in C#?

Thread is a low-level OS construct for concurrent execution. Task is a higher-level abstraction from the Task Parallel Library (TPL) that runs work on the thread pool and supports async/await.

Feature Thread Task
Abstraction level Low-level (OS thread) High-level (thread pool / async)
Creation cost High (new OS thread each time) Low (reuses thread pool threads)
Return value No built-in support Task<T> returns a result
Exception handling Manual (unhandled = crash) Propagated via await / .Result
Cancellation Manual (Thread.Abort removed) CancellationToken built-in
Async/await Not supported Native support
Recommended for Long-running dedicated work Everything else (preferred)

Thread example (rare in modern .NET):

var thread = new Thread(() =>
    Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}"));
thread.IsBackground = true;
thread.Start();
thread.Join();

Task example (preferred):

var result = await Task.Run(() =>
{
    Console.WriteLine($"Thread pool ID: {Thread.CurrentThread.ManagedThreadId}");
    return 42;
});
Console.WriteLine(result); // Output: 42

Long-running task (equivalent to a dedicated thread):

var longRunning = Task.Factory.StartNew(() =>
{
    while (true) { /* background service loop */ }
}, TaskCreationOptions.LongRunning);
↥ back to top

Q. What is ConfigureAwait(false) and when should it be used?

When a Task is awaited, by default .NET tries to resume on the original synchronisation context (e.g., the UI thread, or an ASP.NET Classic request context). ConfigureAwait(false) instructs the runtime to resume on any available thread-pool thread instead, which avoids unnecessary context switches and potential deadlocks.

// ” 1. Default behaviour (ConfigureAwait(true) / omitted) ”———————
// Resumes on the captured synchronisation context (e.g. UI thread)
async Task LoadAndDisplayAsync()
{
    var data = await FetchDataAsync();   // resumes on UI thread  important for WPF/WinForms
    label.Text = data;                   // … safe — UI update on UI thread
}

// ” 2. Library code — always use ConfigureAwait(false) ”——————————
// Library methods should NOT capture the caller\'s context
public static async Task<string> FetchDataAsync(string url)
{
    using var client = new HttpClient();
    // ConfigureAwait(false) — resume on any thread pool thread
    string json = await client.GetStringAsync(url).ConfigureAwait(false);
    return json;   // no context-sensitive work here
}

// ” 3. Deadlock scenario (ASP.NET Classic / WPF without ConfigureAwait) ”
// BAD: .Result on async method in single-threaded context causes deadlock
// string result = FetchDataAsync("https://example.com").Result; //  DEADLOCK

// GOOD: await end-to-end, or use ConfigureAwait(false) in the library
public static async Task<string> SafeFetchAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url).ConfigureAwait(false);
}

// ” 4. ASP.NET Core — no SynchronisationContext, so ConfigureAwait(false)
//    is not required for correctness, but still a good habit in library code
public async Task<IActionResult> GetAsync()
{
    // In ASP.NET Core, SynchronisationContext is null — both are equivalent
    var data = await FetchDataAsync("https://api.example.com/data");
    return Ok(data);
}

// ” 5. ConfigureAwait in a loop ”—————————————————————————————————
public static async Task ProcessItemsAsync(IEnumerable<int> ids)
{
    foreach (int id in ids)
    {
        var result = await LoadItemAsync(id).ConfigureAwait(false);
        Console.WriteLine(result);
    }
}

static Task<string> LoadItemAsync(int id) => Task.FromResult($"Item-{id}");

When to use / not use ConfigureAwait(false):

Scenario Use ConfigureAwait(false)? Reason
Library / NuGet package code … Always Don't impose context on callers
ASP.NET Core controller / middleware Optional No SynchronisationContext
WPF / WinForms UI method No Need to return to UI thread
ASP.NET Classic (System.Web) … Yes Avoid deadlocks on captured context
Unit test with async … Yes Test runners may have a context
↥ back to top

Q. What is IAsyncEnumerable<T> and how do you use await foreach in C#?

IAsyncEnumerable<T> (C# 8 / .NET Standard 2.1+) enables asynchronous streaming — producing and consuming items one at a time without buffering the entire result set in memory. It combines the pull-based iteration of IEnumerable<T> with asynchrony.

using System.Runtime.CompilerServices;

// ” 1. Producing an async stream ”————————————————————————————————
// Use `yield return` inside an `async` method returning IAsyncEnumerable<T>
static async IAsyncEnumerable<int> GenerateNumbersAsync(
    int count,
    [EnumeratorCancellation] CancellationToken ct = default)
{
    for (int i = 1; i <= count; i++)
    {
        ct.ThrowIfCancellationRequested();
        await Task.Delay(50, ct);   // simulate async work (DB query, HTTP, etc.)
        yield return i;
    }
}

// ” 2. Consuming with `await foreach` ”———————————————————————————
await foreach (int number in GenerateNumbersAsync(5))
    Console.WriteLine(number);   // prints 1..5 as they arrive

// ” 3. CancellationToken support ”————————————————————————————————
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
try
{
    await foreach (int n in GenerateNumbersAsync(100, cts.Token))
        Console.WriteLine(n);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Stream cancelled.");
}

// ” 4. ConfigureAwait on IAsyncEnumerable ”———————————————————————
await foreach (int n in GenerateNumbersAsync(5).ConfigureAwait(false))
    Console.WriteLine(n);

// ” 5. Real-world: streaming database rows ”——————————————————————
// (EF Core 3+ supports IAsyncEnumerable via AsAsyncEnumerable())
// async IAsyncEnumerable<Order> StreamOrdersAsync(AppDbContext db)
// {
//     await foreach (var order in db.Orders.AsAsyncEnumerable())
//         yield return order;
// }

// ” 6. Stream large file lines without loading all into memory ”——
static async IAsyncEnumerable<string> ReadLinesAsync(
    string path,
    [EnumeratorCancellation] CancellationToken ct = default)
{
    await using var fs = File.OpenRead(path);
    using var reader = new StreamReader(fs);
    string? line;
    while ((line = await reader.ReadLineAsync(ct)) is not null)
        yield return line;
}

// ” 7. LINQ-style on async streams (System.Linq.Async NuGet) ”————
// var evens = GenerateNumbersAsync(10).Where(n => n % 2 == 0);
// await foreach (var n in evens) Console.WriteLine(n);

// ” 8. Collect to list when needed ”——————————————————————————————
var items = new List<int>();
await foreach (int n in GenerateNumbersAsync(5))
    items.Add(n);
Console.WriteLine(string.Join(", ", items));  // 1, 2, 3, 4, 5

IAsyncEnumerable<T> vs alternatives:

Approach Buffering Back-pressure Best for
Task<List<T>> All items at once No Small result sets
IAsyncEnumerable<T> One item at a time Yes (pull) Large / infinite streams
Channel<T> Configurable Yes Producer-consumer pipelines
IObservable<T> (Rx) Push-based Complex Event-driven streams
↥ back to top

Q. What are the pitfalls of async void methods in C#?

async void is allowed only for event handlers. Using it anywhere else creates silent, hard-to-debug failures because exceptions escape the caller's context and cannot be awaited.

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// PROBLEM 1 — Unhandled exceptions crash the process
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
async void FireAndForget()                 //  async void — avoid
{
    await Task.Delay(100);
    throw new InvalidOperationException("Oops!");  // crashes the process — cannot be caught by caller
}

try
{
    FireAndForget();    // returns immediately — exception is NOT catchable here
}
catch (Exception)
{
    //  Never reached — the exception happens after the await
    Console.WriteLine("This will never print");
}

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// PROBLEM 2 — Cannot be awaited or composed
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
async void LoadAsync() { await Task.Delay(500); Console.WriteLine("Done"); }

// await LoadAsync();     //  compile error — void is not awaitable
// Task t = LoadAsync();  //  compile error — returns void, not Task

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// CORRECT ALTERNATIVES
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••

// ” 1. Return Task — preferred for all non-event-handler async methods
async Task LoadDataAsync()
{
    await Task.Delay(100);
    Console.WriteLine("Data loaded");
}
await LoadDataAsync();   // … awaitable, exception propagates normally

// ” 2. async void is ONLY acceptable for event handlers
// (because event delegates have a void return signature)
public class MyForm
{
    private Button _btn = new Button();

    public MyForm()
    {
        _btn.Click += OnButtonClickAsync;   // … event handler — async void OK
    }

    private async void OnButtonClickAsync(object? sender, EventArgs e)
    {
        try
        {
            await LoadDataAsync();
        }
        catch (Exception ex)
        {
            // … Always wrap async void event handlers in try/catch
            Console.Error.WriteLine($"Event handler error: {ex.Message}");
        }
    }
}

// ” 3. Fire-and-forget with proper error handling ”———————————————
static Task StartBackgroundWork()
{
    return Task.Run(async () =>
    {
        try
        {
            await Task.Delay(100);
            Console.WriteLine("Background work done");
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Background error: {ex.Message}");
        }
    });
}

_ = StartBackgroundWork();   // discard Task intentionally — fire-and-forget pattern

// ” 4. Top-level async in older frameworks ”——————————————————————
// BEFORE C# 7.1: Main couldn\'t be async ’ temptation to use async void
// async void Main() { }  // 

// C# 7.1+: async Main is fully supported
// static async Task Main(string[] args) { await DoWorkAsync(); }  // …

async void rules:

Rule Reason
Never use async void except for event handlers Exceptions crash the process
Always try/catch inside async void event handlers Last line of defence
Replace async void with async Task everywhere else Awaitable, composable, testable
For fire-and-forget, use _ = task with internal error handling Explicitly marks the intent
↥ back to top

# 7. FILE HANDLING


Q. What is File Handling in C#.Net?

File handling is the ability to create, read, write, append, copy, move, and delete files and directories from C# code. The primary namespaces are:

Namespace Contents
System.IO File, FileInfo, Directory, DirectoryInfo, Stream, StreamReader, StreamWriter, FileStream, MemoryStream, BinaryReader, BinaryWriter
System.Text.Json JsonSerializer, Utf8JsonReader, Utf8JsonWriter
System.Xml XmlReader, XmlWriter, XDocument
// Quick overview of the main file API
using System.IO;

// Static helper class — convenient for one-off operations
File.WriteAllText("note.txt", "Hello, .NET 10!");
string content = File.ReadAllText("note.txt");
Console.WriteLine(content); // Hello, .NET 10!

// Async versions (preferred in modern code)
await File.WriteAllTextAsync("note.txt", "Hello async!");
string text = await File.ReadAllTextAsync("note.txt");

// Check existence before operating
if (File.Exists("note.txt"))
    File.Delete("note.txt");
↥ back to top

Q. How do you read a file in C#?

// 1. File.ReadAllText — small files, reads entire file as one string
string text = await File.ReadAllTextAsync("data.txt");

// 2. File.ReadAllLines — reads into string array (one element per line)
string[] lines = await File.ReadAllLinesAsync("data.txt");
foreach (string line in lines) Console.WriteLine(line);

// 3. File.ReadAllBytes — binary content
byte[] bytes = await File.ReadAllBytesAsync("image.png");

// 4. StreamReader — large files, line-by-line streaming (low memory)
await using var reader = new StreamReader("large.csv");
string? line;
while ((line = await reader.ReadLineAsync()) is not null)
    Console.WriteLine(line);

// 5. File.ReadLines — lazy IEnumerable<string>, never loads entire file
foreach (string l in File.ReadLines("data.txt"))
    Console.WriteLine(l); // reads one line at a time

// 6. FileStream with buffer — lowest level, maximum control
await using var fs = new FileStream("data.bin",
    FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true);
byte[] buf = new byte[4096];
int bytesRead;
while ((bytesRead = await fs.ReadAsync(buf)) > 0)
    Console.WriteLine($"Read {bytesRead} bytes");

// 7. Pipes — zero-copy for high-throughput reading (.NET 5+)
using System.IO.Pipelines;
await using var pipeFs = File.OpenRead("data.bin");
var reader2 = PipeReader.Create(pipeFs);
while (true)
{
    var result = await reader2.ReadAsync();
    reader2.AdvanceTo(result.Buffer.End);
    if (result.IsCompleted) break;
}
↥ back to top

Q. How do you write to a file in C#?

// 1. File.WriteAllText — overwrites (or creates) the file
await File.WriteAllTextAsync("output.txt", "Hello, .NET 10!");

// 2. File.WriteAllLines — writes each string as a line
string[] lines = ["Line 1", "Line 2", "Line 3"];
await File.WriteAllLinesAsync("output.txt", lines);

// 3. File.WriteAllBytes — binary data
byte[] data = [0x50, 0x4B, 0x03, 0x04]; // ZIP magic bytes
await File.WriteAllBytesAsync("archive.bin", data);

// 4. StreamWriter — write line-by-line (useful for logging)
await using var writer = new StreamWriter("log.txt", append: false);
await writer.WriteLineAsync($"{DateTime.UtcNow:O} - App started");
await writer.WriteLineAsync("Processing...");
// Flushed and closed at end of using block

// 5. FileStream — raw bytes, full control
await using var fs = new FileStream("data.bin",
    FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true);
byte[] bytes = System.Text.Encoding.UTF8.GetBytes("Hello binary world");
await fs.WriteAsync(bytes);

// 6. File.OpenWrite / File.Create — quick FileStream shortcuts
await using var quick = File.Create("temp.txt");
await quick.WriteAsync("quick write"u8.ToArray());

// 7. Atomic write pattern — write to temp, then rename (avoids partial writes)
string target = "config.json";
string temp   = target + ".tmp";
await File.WriteAllTextAsync(temp, System.Text.Json.JsonSerializer.Serialize(new { key = "val" }));
File.Move(temp, target, overwrite: true); // atomic on same volume
↥ back to top

Q. What is the difference between File.ReadAllText and File.ReadAllLines in C#?

  File.ReadAllText File.ReadAllLines
Returns string (entire file) string[] (one element per line)
Memory Single string allocation Array of strings
Line endings Preserved in string Stripped (used as delimiter)
Use case Small config/text files CSV, log files, line-by-line processing
Async ReadAllTextAsync ReadAllLinesAsync
// File content: "Hello\nWorld\nFoo"

// ReadAllText — one string with newlines intact
string all = await File.ReadAllTextAsync("data.txt");
Console.WriteLine(all.Length);      // includes \n characters
Console.WriteLine(all.Contains('\n')); // true

// ReadAllLines — array, newlines stripped
string[] lines = await File.ReadAllLinesAsync("data.txt");
Console.WriteLine(lines.Length);    // 3
Console.WriteLine(lines[0]);        // Hello
Console.WriteLine(lines[1]);        // World

// File.ReadLines — lazy (no full load) — best for large files
int lineCount = 0;
foreach (string line in File.ReadLines("data.txt"))
    lineCount++;
Console.WriteLine(lineCount); // 3

// When to choose:
// ReadAllText  ’ parse JSON/XML/config as a whole string
// ReadAllLines ’ process CSV/log line by line but file fits in memory
// ReadLines    ’ large files that don\'t fit in memory (streaming)
↥ back to top

Q. How do you append text to an existing file in C#?

// 1. File.AppendAllText — simplest
await File.AppendAllTextAsync("log.txt", $"{DateTime.UtcNow:O} - event\n");

// 2. File.AppendAllLines — appends multiple lines
string[] newLines = ["Entry 1", "Entry 2"];
await File.AppendAllLinesAsync("log.txt", newLines);

// 3. StreamWriter with append: true
await using var writer = new StreamWriter("log.txt", append: true);
await writer.WriteLineAsync($"[{DateTime.UtcNow:HH:mm:ss}] Application started");

// 4. FileStream with FileMode.Append
await using var fs = new FileStream("log.txt",
    FileMode.Append, FileAccess.Write, FileShare.None, 4096, useAsync: true);
byte[] entry = System.Text.Encoding.UTF8.GetBytes("appended line\n");
await fs.WriteAsync(entry);

// 5. High-frequency logging — keep StreamWriter open (don\'t reopen each write)
public sealed class FileLogger : IAsyncDisposable
{
    private readonly StreamWriter _writer;
    public FileLogger(string path) =>
        _writer = new StreamWriter(path, append: true) { AutoFlush = false };

    public async Task LogAsync(string message)
    {
        await _writer.WriteLineAsync($"{DateTime.UtcNow:O} {message}");
        await _writer.FlushAsync(); // or batch and flush periodically
    }

    public async ValueTask DisposeAsync() => await _writer.DisposeAsync();
}

await using var logger = new FileLogger("app.log");
await logger.LogAsync("Server started");
↥ back to top

Q. How do you check if a file exists in C#?

// 1. File.Exists — synchronous check (thread-safe to call)
if (File.Exists("config.json"))
{
    string config = await File.ReadAllTextAsync("config.json");
    Console.WriteLine(config);
}
else
{
    Console.WriteLine("Config not found, using defaults");
}

// 2. Directory.Exists — for directories
if (!Directory.Exists("logs"))
    Directory.CreateDirectory("logs");

// 3. FileInfo.Exists — when you need other file metadata too
var fi = new FileInfo("data.csv");
if (fi.Exists)
    Console.WriteLine($"Size: {fi.Length} bytes, Modified: {fi.LastWriteTimeUtc}");

// 4.  TOCTOU race condition — check-then-use is not atomic
// Between Exists() check and the Open(), file could be deleted.
// Safer: just open and handle the exception
try
{
    string content = await File.ReadAllTextAsync("data.txt");
    // process...
}
catch (FileNotFoundException)
{
    Console.WriteLine("File not found — skipping");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("No permission to read file");
}

// 5. Path combiners — avoid hardcoded separators
string basePath = AppContext.BaseDirectory;
string filePath = Path.Combine(basePath, "data", "config.json");
bool exists     = File.Exists(filePath);
Console.WriteLine($"{filePath} exists: {exists}");
↥ back to top

Q. What is the purpose of the StreamReader and StreamWriter classes in C#?

StreamReader and StreamWriter are text-oriented wrappers around a Stream that handle character encoding automatically. They read/write decoded text rather than raw bytes.

// StreamReader — read text from any Stream
// 1. From file path (convenience constructor)
await using var reader = new StreamReader("data.txt", System.Text.Encoding.UTF8);

// Read entire content
string all = await reader.ReadToEndAsync();

// Read line by line
string? line;
while ((line = await reader.ReadLineAsync()) is not null)
    Console.WriteLine(line);

// Check end-of-stream
Console.WriteLine($"AtEnd: {reader.EndOfStream}");

// 2. From any Stream (e.g., HTTP response, MemoryStream)
using var response = await new HttpClient().GetStreamAsync("https://example.com");
using var sr       = new StreamReader(response);
string html        = await sr.ReadToEndAsync();

// StreamWriter — write text to any Stream
// 1. To file
await using var writer = new StreamWriter("output.txt",
    append: false,
    encoding: System.Text.Encoding.UTF8);

await writer.WriteAsync("Hello ");
await writer.WriteLineAsync("World");
await writer.WriteLineAsync($"Time: {DateTime.UtcNow}");
// writer.FlushAsync() called automatically on dispose

// 2. AutoFlush — flush after every write (useful for log files)
var logWriter = new StreamWriter("log.txt", append: true) { AutoFlush = true };
await logWriter.WriteLineAsync("Started");
await logWriter.DisposeAsync();

// 3. Write to MemoryStream (no file I/O)
await using var ms      = new MemoryStream();
await using var msWriter = new StreamWriter(ms, leaveOpen: true);
await msWriter.WriteLineAsync("in-memory text");
await msWriter.FlushAsync();
ms.Position = 0;
using var msReader = new StreamReader(ms);
Console.WriteLine(await msReader.ReadToEndAsync()); // in-memory text
↥ back to top

Q. How do you handle exceptions when working with files in C#?

// Common file I/O exceptions:
// FileNotFoundException    — file does not exist
// DirectoryNotFoundException — directory does not exist
// UnauthorizedAccessException — no read/write permission
// IOException              — disk full, file locked, I/O error
// PathTooLongException     — path exceeds OS limit
// NotSupportedException    — invalid path format

public async Task<string?> ReadFileSafeAsync(string path)
{
    try
    {
        return await File.ReadAllTextAsync(path);
    }
    catch (FileNotFoundException)
    {
        Console.WriteLine($"File not found: {path}");
        return null;
    }
    catch (UnauthorizedAccessException)
    {
        Console.WriteLine($"Access denied: {path}");
        return null;
    }
    catch (IOException ex)
    {
        Console.WriteLine($"I/O error reading {path}: {ex.Message}");
        return null;
    }
}

// Ensure streams are always closed — use 'await using' or 'using'
public async Task WriteFileSafeAsync(string path, string content)
{
    await using var writer = new StreamWriter(path); // disposed even on exception
    await writer.WriteAsync(content);
}

// File sharing conflicts — retry with backoff
public async Task<string> ReadWithRetryAsync(string path, int maxAttempts = 3)
{
    for (int attempt = 1; attempt <= maxAttempts; attempt++)
    {
        try
        {
            return await File.ReadAllTextAsync(path);
        }
        catch (IOException) when (attempt < maxAttempts)
        {
            await Task.Delay(200 * attempt); // exponential backoff
        }
    }
    throw new IOException($"Could not read {path} after {maxAttempts} attempts");
}

// Check before delete (avoids exception for common case)
public void DeleteIfExists(string path)
{
    try { File.Delete(path); }
    catch (FileNotFoundException) { /* already gone — fine */ }
}
↥ back to top

Q. How do you delete a file in C#?

// 1. File.Delete — throws if path is a directory or access denied; silent if not found
File.Delete("temp.txt");

// 2. Safe delete — suppress FileNotFoundException
public static void DeleteIfExists(string path)
{
    try { File.Delete(path); }
    catch (FileNotFoundException) { }
}

// 3. Check + delete (note: TOCTOU race — prefer try/catch above)
if (File.Exists("temp.txt"))
    File.Delete("temp.txt");

// 4. FileInfo.Delete
var fi = new FileInfo("old.log");
if (fi.Exists) fi.Delete();

// 5. Delete directory (empty)
Directory.Delete("emptyFolder");

// 6. Delete directory and all contents recursively
Directory.Delete("outputFolder", recursive: true);

// 7. Move to recycle bin (Windows only — via P/Invoke or FileSystem.DeleteFile)
// dotnet add package Microsoft.VisualBasic (included in .NET)
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(
    "file.txt",
    Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
    Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);

// 8. Delete multiple files matching a pattern
foreach (string file in Directory.GetFiles("logs", "*.log"))
    File.Delete(file);

// Or with LINQ
Directory.EnumerateFiles("logs", "*.tmp")
         .ToList()
         .ForEach(File.Delete);
↥ back to top

Q. What is the difference between FileStream and MemoryStream in C#?

  FileStream MemoryStream
Backing store File on disk In-memory byte array
Persistence Data persists after app exits Lost when stream is disposed/app exits
Size limit Disk capacity Available RAM
Performance Slower (disk I/O) Very fast (RAM)
Async useAsync: true … (but completes synchronously)
Use case Read/write actual files Temporary buffers, unit testing, serialisation
Seek … (seekable) … (seekable)
// FileStream — backed by disk
await using var fs = new FileStream("data.bin",
    FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, useAsync: true);
await fs.WriteAsync("Hello file"u8.ToArray());
fs.Position = 0;
var buf = new byte[10];
await fs.ReadAsync(buf);
Console.WriteLine(System.Text.Encoding.UTF8.GetString(buf)); // Hello file

// MemoryStream — in-memory, no file
await using var ms = new MemoryStream(capacity: 256);
await ms.WriteAsync("Hello memory"u8.ToArray());
ms.Position = 0;
var mbuf = new byte[12];
await ms.ReadAsync(mbuf);
Console.WriteLine(System.Text.Encoding.UTF8.GetString(mbuf)); // Hello memory

// Common pattern: serialise to MemoryStream, then copy to FileStream
var obj = new { Name = "Alice", Age = 30 };
await using var output = new MemoryStream();
await System.Text.Json.JsonSerializer.SerializeAsync(output, obj);
output.Position = 0;
await using var file = File.Create("person.json");
await output.CopyToAsync(file);

// MemoryStream.ToArray() — get all bytes
byte[] allBytes = ms.ToArray(); // independent copy
// GetBuffer() — get underlying buffer (may have extra bytes past Length)
↥ back to top

Q. How do you copy a file in C#?

// 1. File.Copy — simplest
File.Copy("source.txt", "destination.txt", overwrite: true);

// 2. Async copy via streams (for large files with progress)
public static async Task CopyFileAsync(
    string source, string dest, IProgress<long>? progress = null,
    CancellationToken ct = default)
{
    await using var src  = new FileStream(source, FileMode.Open,   FileAccess.Read,  FileShare.Read,  65536, useAsync: true);
    await using var dst  = new FileStream(dest,   FileMode.Create, FileAccess.Write, FileShare.None, 65536, useAsync: true);
    var buffer = new byte[65536];
    long total = 0;
    int  read;
    while ((read = await src.ReadAsync(buffer, ct)) > 0)
    {
        await dst.WriteAsync(buffer.AsMemory(0, read), ct);
        total += read;
        progress?.Report(total);
    }
}

// Usage
await CopyFileAsync("big.iso", "backup.iso",
    new Progress<long>(bytes => Console.Write($"\r{bytes / 1_048_576} MB")));

// 3. FileInfo.CopyTo
var fi = new FileInfo("source.txt");
fi.CopyTo("destination.txt", overwrite: true);

// 4. Copy a directory recursively (.NET 7+)
// dotnet: Directory has no built-in recursive copy; use helper
static void CopyDirectory(string src, string dst)
{
    Directory.CreateDirectory(dst);
    foreach (string file in Directory.GetFiles(src))
        File.Copy(file, Path.Combine(dst, Path.GetFileName(file)), overwrite: true);
    foreach (string dir in Directory.GetDirectories(src))
        CopyDirectory(dir, Path.Combine(dst, Path.GetFileName(dir)));
}
↥ back to top

Q. How do you move a file in C#?

// 1. File.Move — rename or move; throws if destination exists (use overwrite param)
File.Move("old.txt", "new.txt");                     // error if new.txt exists
File.Move("old.txt", "new.txt", overwrite: true);    // overwrites if exists

// 2. Move to a different directory
File.Move("C:/source/report.pdf", "D:/archive/report.pdf", overwrite: true);

// 3. FileInfo.MoveTo
var fi = new FileInfo("data.csv");
fi.MoveTo("archive/data.csv", overwrite: true);

// 4. Move directory
Directory.Move("OldFolder", "NewFolder"); // must be on same volume

// 5. Atomic move (same volume — rename is atomic on Windows/Linux)
File.Move("config.json.tmp", "config.json", overwrite: true);
// On same filesystem this is a rename — atomic, no partial-write window

// 6. Cross-volume move (copy + delete)
public static async Task MoveAcrossVolumesAsync(string src, string dst, CancellationToken ct = default)
{
    await using var source = new FileStream(src, FileMode.Open, FileAccess.Read, FileShare.None, 65536, useAsync: true);
    await using var dest   = new FileStream(dst, FileMode.Create, FileAccess.Write, FileShare.None, 65536, useAsync: true);
    await source.CopyToAsync(dest, ct);
    source.Close(); // close before deleting
    File.Delete(src);
}

// 7. Rename all files in a folder
foreach (string file in Directory.EnumerateFiles("reports", "*.txt"))
{
    string newName = Path.ChangeExtension(file, ".md");
    File.Move(file, newName, overwrite: false);
}
↥ back to top

Q. What is the FileInfo class and how is it used?

FileInfo provides instance-based file operations and rich metadata about a single file. Unlike the static File class, it performs only one security check at construction time — useful when you need to perform multiple operations on the same file.

var fi = new FileInfo("report.csv");

// Metadata — no security check on each property
Console.WriteLine($"Name:       {fi.Name}");           // report.csv
Console.WriteLine($"Full path:  {fi.FullName}");
Console.WriteLine($"Directory:  {fi.DirectoryName}");
Console.WriteLine($"Extension:  {fi.Extension}");      // .csv
Console.WriteLine($"Size:       {fi.Length} bytes");
Console.WriteLine($"Created:    {fi.CreationTimeUtc}");
Console.WriteLine($"Modified:   {fi.LastWriteTimeUtc}");
Console.WriteLine($"Read-only:  {fi.IsReadOnly}");
Console.WriteLine($"Exists:     {fi.Exists}");

// Operations
if (fi.Exists)
{
    // Open for reading
    await using StreamReader reader = fi.OpenText();
    string content = await reader.ReadToEndAsync();

    // Copy
    FileInfo copy = fi.CopyTo("report_backup.csv", overwrite: true);
    Console.WriteLine($"Copy size: {copy.Length}");

    // Move / rename
    fi.MoveTo("archive/report.csv", overwrite: true);

    // Delete
    fi.Delete();
}

// Create / open with specific FileStream options
await using FileStream fs = fi.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);

// Refresh metadata cache (if file changed externally)
fi.Refresh();
Console.WriteLine($"Updated size: {fi.Length}");

// DirectoryInfo — same concept for directories
var di = new DirectoryInfo("logs");
di.Create(); // no-op if already exists
foreach (FileInfo logFile in di.GetFiles("*.log"))
    Console.WriteLine($"{logFile.Name}: {logFile.Length} bytes");
↥ back to top

Q. How do you read and write binary files in C#?

// ” Writing binary data ”————————————————————————————————————————————

// 1. File.WriteAllBytes
byte[] raw = [0x89, 0x50, 0x4E, 0x47]; // PNG magic bytes
await File.WriteAllBytesAsync("header.bin", raw);

// 2. BinaryWriter — write typed primitives
await using var fs     = new FileStream("data.bin", FileMode.Create, FileAccess.Write, FileShare.None, 4096, true);
await using var writer = new BinaryWriter(fs, System.Text.Encoding.UTF8, leaveOpen: true);

writer.Write(42);              // int   (4 bytes)
writer.Write(3.14);            // double (8 bytes)
writer.Write("Hello");         // length-prefixed string
writer.Write(true);            // bool   (1 byte)
writer.Write((byte)0xFF);      // byte

// 3. Span<byte> with FileStream (best performance, .NET 5+)
await using var write = File.OpenWrite("span.bin");
Span<byte> span = stackalloc byte[8];
System.Buffers.Binary.BinaryPrimitives.WriteInt64LittleEndian(span, 123456789L);
await write.WriteAsync(span.ToArray());

// ” Reading binary data ”————————————————————————————————————————————

// 1. File.ReadAllBytes
byte[] bytes = await File.ReadAllBytesAsync("data.bin");

// 2. BinaryReader — read matching typed data
await using var rfs    = new FileStream("data.bin", FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);
await using var reader = new BinaryReader(rfs, System.Text.Encoding.UTF8, leaveOpen: true);

int    number = reader.ReadInt32();    // 42
double pi     = reader.ReadDouble();   // 3.14
string text   = reader.ReadString();   // Hello
bool   flag   = reader.ReadBoolean();  // true
byte   b      = reader.ReadByte();     // 0xFF

Console.WriteLine($"{number}, {pi}, {text}, {flag}, {b:X2}");

// 3. Struct serialisation via MemoryMarshal (zero-copy)
[System.Runtime.InteropServices.StructLayout(
    System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
struct Header { public int Magic; public short Version; public long Timestamp; }

await using var hfs = File.OpenRead("header.bin");
byte[] hbuf = new byte[System.Runtime.InteropServices.Marshal.SizeOf<Header>()];
await hfs.ReadExactlyAsync(hbuf);
Header header = System.Runtime.InteropServices.MemoryMarshal
    .Read<Header>(hbuf);
↥ back to top

Q. How do you work with directories in C#?

// 1. Create directory (and parents)
Directory.CreateDirectory("logs/2026/april"); // creates entire path, no error if exists

// 2. Check existence
bool exists = Directory.Exists("logs");

// 3. Delete
Directory.Delete("emptyFolder");                  // must be empty
Directory.Delete("fullFolder", recursive: true);  // deletes all contents

// 4. List contents
string[] files   = Directory.GetFiles("logs");               // non-recursive
string[] allCsvs = Directory.GetFiles("data", "*.csv", SearchOption.AllDirectories);

// Lazy enumeration (better for large directories)
foreach (string file in Directory.EnumerateFiles("logs", "*.log", new EnumerationOptions
{
    RecurseSubdirectories = true,
    IgnoreInaccessible    = true,
    MatchCasing           = MatchCasing.CaseInsensitive,
}))
{
    Console.WriteLine(file);
}

// 5. Move / rename directory
Directory.Move("OldName", "NewName");

// 6. Get directory info
var di = new DirectoryInfo("logs");
Console.WriteLine($"Created: {di.CreationTimeUtc}");
Console.WriteLine($"Files:   {di.GetFiles().Length}");
Console.WriteLine($"Parent:  {di.Parent?.FullName}");

// 7. Special folders
string desktop  = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string appData  = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string tempPath = Path.GetTempPath();
string tempFile = Path.GetTempFileName(); // creates a 0-byte temp file

Console.WriteLine($"Temp:    {tempPath}");
Console.WriteLine($"AppData: {appData}");

// 8. Path utilities
string full    = Path.GetFullPath("../relative/path");
string combined = Path.Combine("C:/data", "reports", "2026.csv");
string dir     = Path.GetDirectoryName(combined)!;
string name    = Path.GetFileNameWithoutExtension(combined); // 2026
string ext     = Path.GetExtension(combined);                // .csv
↥ back to top

Q. How do you get the size of a file in C#?

// 1. FileInfo.Length — most common
var fi = new FileInfo("video.mp4");
Console.WriteLine($"Size: {fi.Length:N0} bytes");
Console.WriteLine($"Size: {fi.Length / 1_048_576.0:F2} MB");

// 2. File.OpenRead + Stream.Length
await using var fs = File.OpenRead("video.mp4");
Console.WriteLine($"Stream length: {fs.Length:N0} bytes");

// 3. Static helper that formats size
static string FormatSize(long bytes) => bytes switch
{
    < 1_024                 => $"{bytes} B",
    < 1_048_576             => $"{bytes / 1_024.0:F1} KB",
    < 1_073_741_824         => $"{bytes / 1_048_576.0:F2} MB",
    _                       => $"{bytes / 1_073_741_824.0:F2} GB",
};
Console.WriteLine(FormatSize(new FileInfo("video.mp4").Length));

// 4. Total size of a directory (recursive)
static long GetDirectorySize(string path) =>
    Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
             .Sum(f => new FileInfo(f).Length);

Console.WriteLine(FormatSize(GetDirectorySize("C:/Users/me/Documents")));

// 5. Check before read (avoid loading huge files)
const long MaxAllowedBytes = 10 * 1_048_576; // 10 MB
var info = new FileInfo("upload.bin");
if (!info.Exists)         throw new FileNotFoundException();
if (info.Length > MaxAllowedBytes)
    throw new InvalidOperationException($"File too large: {FormatSize(info.Length)}");
string content = await File.ReadAllTextAsync(info.FullName);
↥ back to top

Q. What are DLL files, and what are the advantages of using them?

A DLL (Dynamic Link Library) is a compiled binary (.dll) containing reusable code — types, methods, resources — that can be loaded and used by multiple applications at runtime.

In .NET, every class library project compiles to a DLL. The runtime loads assemblies on demand via the CLR.

// Create a class library (DLL)
// dotnet new classlib -n MathLibrary
namespace MathLibrary;

public static class Calculator
{
    public static int Add(int a, int b)      => a + b;
    public static double Sqrt(double value)  => Math.Sqrt(value);
}

// Reference it in another project
// dotnet add reference ../MathLibrary/MathLibrary.csproj
// Or via NuGet: dotnet add package MathLibrary

using MathLibrary;
Console.WriteLine(Calculator.Add(3, 4));  // 7
Console.WriteLine(Calculator.Sqrt(16));   // 4

// Load a DLL at runtime (plugin architecture)
using System.Reflection;

var assembly = Assembly.LoadFrom("plugins/MyPlugin.dll");
var type     = assembly.GetType("MyPlugin.PluginEntry")!;
var instance = Activator.CreateInstance(type)!;
var method   = type.GetMethod("Run")!;
method.Invoke(instance, null);

Advantages of DLL files:

Advantage Detail
Code reuse Share library across multiple apps without copying source
Separation of concerns Isolate layers (data, business, UI) into separate assemblies
Versioning Update a DLL independently without rebuilding all consumers
Lazy loading CLR loads DLLs on first use — faster startup
Plugin architecture Load DLLs dynamically at runtime
Reduced memory OS can share pages of the same DLL across processes
Encapsulation internal types hidden from consumers
↥ back to top

Q. What is a MemoryStream in C#?

MemoryStream is a Stream implementation that stores data in in-memory byte arrays — no file system or network I/O. It is seekable, readable, and writable.

// 1. Basic write and read
await using var ms = new MemoryStream();

// Write
byte[] data = System.Text.Encoding.UTF8.GetBytes("Hello MemoryStream!");
await ms.WriteAsync(data);

// Reset position to read from beginning
ms.Position = 0;
using var reader = new StreamReader(ms, leaveOpen: true);
Console.WriteLine(await reader.ReadToEndAsync()); // Hello MemoryStream!

// 2. Pre-populated (wraps existing byte array — read-only)
byte[] existing = [1, 2, 3, 4, 5];
using var ro = new MemoryStream(existing);
Console.WriteLine(ro.ReadByte()); // 1

// 3. Serialise an object to bytes
var product = new { Id = 1, Name = "Laptop" };
await using var output = new MemoryStream();
await System.Text.Json.JsonSerializer.SerializeAsync(output, product);
byte[] json = output.ToArray(); // independent copy
Console.WriteLine(System.Text.Encoding.UTF8.GetString(json));
// {"Id":1,"Name":"Laptop"}

// 4. Use as pipe between two operations (no temp file)
await using var compressed = new MemoryStream();
await using (var gzip = new System.IO.Compression.GZipStream(
    compressed, System.IO.Compression.CompressionLevel.Fastest, leaveOpen: true))
{
    await gzip.WriteAsync("large text data to compress..."u8.ToArray());
}
Console.WriteLine($"Compressed: {compressed.Length} bytes");

// 5. ToArray vs GetBuffer
// ToArray()  — allocates a new byte[] trimmed to Length
// GetBuffer()— returns internal buffer (may have excess capacity, no copy)
byte[] trimmed  = ms.ToArray();          // safe, trimmed
byte[] raw      = ms.GetBuffer();        // fast, may be larger than ms.Length
long   capacity = ms.Capacity;
↥ back to top

Q. What is the purpose of the FileStream class in C#?

FileStream provides low-level, byte-oriented access to files on disk. It is the foundation for all higher-level file classes (StreamReader, StreamWriter, BinaryReader, etc.) and offers fine-grained control over file mode, access, sharing, buffering, and async I/O.

// Constructor options
var fs = new FileStream(
    path:        "data.bin",
    mode:        FileMode.OpenOrCreate,  // Create, Open, Append, Truncate, CreateNew
    access:      FileAccess.ReadWrite,   // Read, Write, ReadWrite
    share:       FileShare.None,         // None, Read, Write, ReadWrite, Delete
    bufferSize:  4096,
    useAsync:    true);                  // enable async I/O (important on Windows)
await using var _ = fs;

// Write bytes
await fs.WriteAsync("Hello FileStream"u8.ToArray());

// Seek to start
fs.Seek(0, SeekOrigin.Begin);
// or: fs.Position = 0;

// Read bytes
var buf = new byte[16];
int read = await fs.ReadAsync(buf);
Console.WriteLine(System.Text.Encoding.UTF8.GetString(buf, 0, read)); // Hello FileStream

// Properties
Console.WriteLine($"Length:   {fs.Length}");
Console.WriteLine($"Position: {fs.Position}");
Console.WriteLine($"CanRead:  {fs.CanRead}");
Console.WriteLine($"CanWrite: {fs.CanWrite}");
Console.WriteLine($"CanSeek:  {fs.CanSeek}");

// Flush to disk — ensure OS buffers are written
await fs.FlushAsync();

// Practical: copy file with progress
static async Task CopyWithProgressAsync(string src, string dst, IProgress<double>? p = null)
{
    await using var r = new FileStream(src, FileMode.Open, FileAccess.Read, FileShare.Read, 65536, true);
    await using var w = new FileStream(dst, FileMode.Create, FileAccess.Write, FileShare.None, 65536, true);
    var buf = new byte[65536];
    long total = 0, len = r.Length;
    int n;
    while ((n = await r.ReadAsync(buf)) > 0)
    {
        await w.WriteAsync(buf.AsMemory(0, n));
        total += n;
        p?.Report((double)total / len * 100);
    }
}
↥ back to top

Q. What is the difference between StreamReader and StreamWriter?

  StreamReader StreamWriter
Direction Read text from a Stream Write text to a Stream
Base class TextReader TextWriter
Key methods Read, ReadLine, ReadToEnd, ReadLineAsync Write, WriteLine, WriteAsync, WriteLineAsync
Encoding Detects BOM or uses specified encoding Uses specified encoding (default UTF-8 with BOM)
AutoFlush N/A AutoFlush property — flush on every write
EndOfStream EndOfStream property N/A
// StreamReader — reading
await using var reader = new StreamReader("data.txt", System.Text.Encoding.UTF8);

// Read one character
int ch = reader.Read(); // returns -1 at end

// Read one line
string? line = await reader.ReadLineAsync();

// Read entire file
string all = await reader.ReadToEndAsync();

// Iterate lines
while (!reader.EndOfStream)
    Console.WriteLine(await reader.ReadLineAsync());

// StreamWriter — writing
await using var writer = new StreamWriter("output.txt",
    append: false, encoding: System.Text.Encoding.UTF8)
{
    AutoFlush = false, // batch writes for performance
    NewLine   = "\n",  // Unix-style line endings
};

await writer.WriteAsync("no newline");
await writer.WriteLineAsync("with newline"); // appends NewLine
await writer.FlushAsync(); // explicit flush when AutoFlush = false

// Both wrap the same FileStream
await using var shared = new FileStream("rw.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite);
await using var sw = new StreamWriter(shared, leaveOpen: true);
await sw.WriteLineAsync("written");
await sw.FlushAsync();
shared.Position = 0;
using var sr = new StreamReader(shared, leaveOpen: true);
Console.WriteLine(await sr.ReadToEndAsync()); // written
↥ back to top

Q. What is a BinaryReader in C#?

BinaryReader reads primitive types from a stream in binary format — the exact byte representation of int, double, string, bool, etc. It is the reading counterpart of BinaryWriter.

// Typical workflow: write with BinaryWriter, read with BinaryReader
// Writing
await using var writeFs = new FileStream("record.bin", FileMode.Create);
using var bw = new BinaryWriter(writeFs, System.Text.Encoding.UTF8);

bw.Write(1001);             // int   — 4 bytes
bw.Write("Alice");          // string — length prefix + UTF-8 bytes
bw.Write(95_000.50m);       // decimal — 16 bytes
bw.Write(true);             // bool   — 1 byte
bw.Write(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }); // raw bytes

// Reading — must read in the SAME order as written
await using var readFs = new FileStream("record.bin", FileMode.Open);
using var br = new BinaryReader(readFs, System.Text.Encoding.UTF8);

int     id      = br.ReadInt32();       // 1001
string  name    = br.ReadString();      // Alice
decimal salary  = br.ReadDecimal();     // 95000.50
bool    active  = br.ReadBoolean();     // true
byte[]  magic   = br.ReadBytes(4);      // { 0xDE, 0xAD, 0xBE, 0xEF }

Console.WriteLine($"Id={id}, Name={name}, Salary={salary:C}, Active={active}");

// Available Read methods:
// ReadByte / ReadBytes(n)
// ReadInt16 / ReadInt32 / ReadInt64
// ReadUInt16 / ReadUInt32 / ReadUInt64
// ReadSingle (float) / ReadDouble
// ReadDecimal
// ReadBoolean
// ReadChar / ReadChars(n)
// ReadString (length-prefixed)
// PeekChar — look without advancing position

// Safe read with bounds check
try { int val = br.ReadInt32(); }
catch (EndOfStreamException) { Console.WriteLine("Unexpected end of file"); }
↥ back to top

Q. What is the purpose of the BinaryWriter class in C#?

BinaryWriter writes primitive types to a stream as their raw binary representation — compact, fast, and order-dependent. Ideal for custom binary file formats and inter-process/network protocols.

await using var fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write);
using var bw = new BinaryWriter(fs, System.Text.Encoding.UTF8, leaveOpen: false);

// Write different types
bw.Write((byte)0xFF);          // 1 byte
bw.Write((short)32767);        // 2 bytes
bw.Write(42);                  // int — 4 bytes
bw.Write(123456789L);          // long — 8 bytes
bw.Write(3.14f);               // float — 4 bytes
bw.Write(3.141592653589793);   // double — 8 bytes
bw.Write(9999.99m);            // decimal — 16 bytes
bw.Write(true);                // bool — 1 byte
bw.Write('A');                 // char — 2 bytes (UTF-16)
bw.Write("Hello");             // length-prefixed string
bw.Write(new byte[] { 1, 2, 3 }); // raw byte array (no length prefix)
bw.Write(new byte[] { 10, 20, 30 }, offset: 0, count: 2); // partial array

// Flush and inspect size
bw.Flush();
Console.WriteLine($"File size: {fs.Length} bytes");

// Endianness: BinaryWriter/Reader use little-endian by default
// For big-endian, use BinaryPrimitives:
Span<byte> buf = stackalloc byte[4];
System.Buffers.Binary.BinaryPrimitives.WriteInt32BigEndian(buf, 42);
fs.Write(buf);

// Practical: write a custom binary file header
public static void WriteFileHeader(BinaryWriter bw, int version, int recordCount)
{
    bw.Write("MYAPP"u8.ToArray());    // 5-byte magic
    bw.Write((byte)version);           // format version
    bw.Write(recordCount);             // number of records
    bw.Write(DateTimeOffset.UtcNow.ToUnixTimeSeconds()); // timestamp
}
↥ back to top

Q. What is the difference between TextReader and TextWriter?

TextReader and TextWriter are abstract base classes for reading/writing sequences of characters. They decouple code from the underlying I/O source.

  TextReader TextWriter
Direction Read characters/strings Write characters/strings
Key methods Read, ReadLine, ReadToEnd, ReadBlock Write, WriteLine, Flush
Concrete types StreamReader, StringReader StreamWriter, StringWriter
Async ReadAsync, ReadLineAsync, ReadToEndAsync WriteAsync, WriteLineAsync, FlushAsync
Null TextReader.Null (discards reads) TextWriter.Null (discards writes)
// TextReader — accept any source
static async Task ProcessTextAsync(TextReader reader)
{
    string? line;
    while ((line = await reader.ReadLineAsync()) is not null)
        Console.WriteLine(line.ToUpper());
}

// Works with StreamReader (file)
await using var fileReader = new StreamReader("data.txt");
await ProcessTextAsync(fileReader);

// Works with StringReader (in-memory string)
using var stringReader = new StringReader("Hello\nWorld\n.NET 10");
await ProcessTextAsync(stringReader);

// TextWriter — accept any destination
static async Task WriteOutputAsync(TextWriter writer, IEnumerable<string> lines)
{
    foreach (var line in lines)
        await writer.WriteLineAsync(line);
    await writer.FlushAsync();
}

// Write to file
await using var fileWriter = new StreamWriter("output.txt");
await WriteOutputAsync(fileWriter, ["Line 1", "Line 2"]);

// Write to string (in-memory)
await using var stringWriter = new StringWriter();
await WriteOutputAsync(stringWriter, ["Line 1", "Line 2"]);
Console.WriteLine(stringWriter.ToString()); // Line 1\nLine 2\n

// Console.Out and Console.In are TextWriter/TextReader
await WriteOutputAsync(Console.Out, ["Hello console"]);
await ProcessTextAsync(Console.In); // reads from stdin
↥ back to top

Q. What is a StringReader in C#?

StringReader is a TextReader that reads from an in-memory string instead of a file or network stream. It's useful for parsing strings using reader-based APIs without temporary files.

// 1. Basic line-by-line reading
string multiline = "Line 1\nLine 2\nLine 3";
using var reader = new StringReader(multiline);

string? line;
while ((line = reader.ReadLine()) is not null)
    Console.WriteLine(line);
// Line 1
// Line 2
// Line 3

// 2. Use async API (StringReader.ReadLineAsync returns immediately — no I/O)
using var asyncReader = new StringReader("Hello\nWorld");
while ((line = await asyncReader.ReadLineAsync()) is not null)
    Console.WriteLine(line);

// 3. Read one character at a time
using var cr = new StringReader("ABC");
int ch;
while ((ch = cr.Read()) != -1)
    Console.Write((char)ch); // A B C

// 4. PeekChar — look without advancing
using var pr = new StringReader("XYZ");
Console.WriteLine((char)pr.Peek()); // X — not consumed
Console.WriteLine((char)pr.Read()); // X — consumed

// 5. Parse CSV-like content without creating a temp file
static IEnumerable<string[]> ParseCsv(string csv)
{
    using var reader = new StringReader(csv);
    string? line;
    while ((line = reader.ReadLine()) is not null)
        yield return line.Split(',');
}

string data = "Alice,30,Engineer\nBob,25,Designer\nCarol,35,Manager";
foreach (var row in ParseCsv(data))
    Console.WriteLine($"Name={row[0]}, Age={row[1]}, Role={row[2]}");

// 6. Feed into XML/JSON parsers that accept TextReader
using var xmlReader = System.Xml.XmlReader.Create(new StringReader("<root><item>1</item></root>"));
while (xmlReader.Read())
    if (xmlReader.NodeType == System.Xml.XmlNodeType.Text)
        Console.WriteLine(xmlReader.Value); // 1
↥ back to top

Q. What is the purpose of the StringWriter class in C#?

StringWriter is a TextWriter that writes to an in-memory StringBuilder. It lets you build strings using writer-based APIs without creating temporary files.

// 1. Build a string line by line
await using var sw = new StringWriter();
await sw.WriteLineAsync("Dear Alice,");
await sw.WriteLineAsync("Your order has shipped.");
await sw.WriteLineAsync("Regards, Shop");
string email = sw.ToString();
Console.WriteLine(email);

// 2. Use as a drop-in for any TextWriter parameter
static void GenerateReport(TextWriter writer, IEnumerable<string> items)
{
    writer.WriteLine("=== Report ===");
    int i = 1;
    foreach (var item in items)
        writer.WriteLine($"{i++}. {item}");
}

// Write to console
GenerateReport(Console.Out, ["Apple", "Banana"]);

// Write to string
await using var capture = new StringWriter();
GenerateReport(capture, ["Apple", "Banana"]);
string report = capture.ToString();

// Write to file
await using var file = new StreamWriter("report.txt");
GenerateReport(file, ["Apple", "Banana"]);

// 3. Serialise XML to string
await using var xmlSw = new StringWriter();
using var xmlWriter = System.Xml.XmlWriter.Create(xmlSw,
    new System.Xml.XmlWriterSettings { Indent = true, Async = true });
await xmlWriter.WriteStartElementAsync(null, "root", null);
await xmlWriter.WriteElementStringAsync(null, "name", null, "Alice");
await xmlWriter.WriteEndElementAsync();
await xmlWriter.FlushAsync();
Console.WriteLine(xmlSw.ToString());
// <root>
//   <name>Alice</name>
// </root>

// 4. Access the underlying StringBuilder directly
await using var sbw = new StringWriter();
await sbw.WriteAsync("Hello");
sbw.GetStringBuilder().Append(" World"); // direct access
Console.WriteLine(sbw.ToString()); // Hello World
↥ back to top

Q. What is the difference between XmlReader and XmlWriter?

  XmlReader XmlWriter
Direction Forward-only read Forward-only write
Model Pull-parser (streaming, low memory) Streaming writer
Memory O(1) — reads one node at a time O(1) — writes one node at a time
Random access — forward only — forward only
Alternatives XDocument.Load (LINQ to XML, in-memory) XDocument.Save (LINQ to XML)
// XmlWriter — generate XML
var settings = new System.Xml.XmlWriterSettings
{
    Indent     = true,
    Async      = true,
    Encoding   = System.Text.Encoding.UTF8,
    OmitXmlDeclaration = false,
};

await using var sw = new StringWriter();
await using (var xw = System.Xml.XmlWriter.Create(sw, settings))
{
    await xw.WriteStartDocumentAsync();
    await xw.WriteStartElementAsync(null, "catalog", null);

    await xw.WriteStartElementAsync(null, "product", null);
    await xw.WriteAttributeStringAsync(null, "id", null, "1");
    await xw.WriteElementStringAsync(null, "name",  null, "Laptop");
    await xw.WriteElementStringAsync(null, "price", null, "1200");
    await xw.WriteEndElementAsync(); // product

    await xw.WriteEndElementAsync(); // catalog
    await xw.WriteEndDocumentAsync();
}
Console.WriteLine(sw.ToString());

// XmlReader — parse XML (streaming, low memory)
string xml = """
    <catalog>
      <product id="1"><name>Laptop</name><price>1200</price></product>
      <product id="2"><name>Phone</name><price>800</price></product>
    </catalog>
    """;

using var xmlReader = System.Xml.XmlReader.Create(
    new StringReader(xml),
    new System.Xml.XmlReaderSettings { Async = true });

while (await xmlReader.ReadAsync())
{
    if (xmlReader.NodeType == System.Xml.XmlNodeType.Element
        && xmlReader.Name == "name")
    {
        await xmlReader.ReadAsync(); // move to text node
        Console.WriteLine(xmlReader.Value); // Laptop, Phone
    }
}

// For simple scenarios, LINQ to XML (XDocument) is easier
var doc = System.Xml.Linq.XDocument.Parse(xml);
var names = doc.Descendants("name").Select(e => e.Value);
foreach (var n in names) Console.WriteLine(n); // Laptop, Phone
↥ back to top

Q. What is a JsonReader in C#?

In modern .NET 10, the JSON reader is System.Text.Json.Utf8JsonReader — a high-performance, forward-only, ref struct that parses UTF-8 JSON without allocations. (Newtonsoft.Json.JsonReader is the legacy alternative.)

using System.Text.Json;

// 1. Utf8JsonReader — low-level, zero-allocation streaming
byte[] jsonBytes = """{"id":1,"name":"Alice","scores":[95,87,92]}"""u8.ToArray();
var reader = new Utf8JsonReader(jsonBytes, isFinalBlock: true, state: default);

while (reader.Read())
{
    switch (reader.TokenType)
    {
        case JsonTokenType.PropertyName:
            Console.Write($"{reader.GetString()}: ");
            break;
        case JsonTokenType.String:
            Console.WriteLine(reader.GetString());
            break;
        case JsonTokenType.Number:
            Console.WriteLine(reader.GetInt32());
            break;
        case JsonTokenType.StartArray:
        case JsonTokenType.EndArray:
        case JsonTokenType.StartObject:
        case JsonTokenType.EndObject:
            Console.WriteLine(reader.TokenType);
            break;
    }
}

// 2. JsonSerializer.Deserialize — high-level (most common)
string json = """{"id":1,"name":"Alice"}""";
var person = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine($"{person!.Id}: {person.Name}"); // 1: Alice

// 3. JsonDocument — DOM-style read without strong typing
using var doc = JsonDocument.Parse(json);
JsonElement root = doc.RootElement;
Console.WriteLine(root.GetProperty("name").GetString()); // Alice

// 4. Streaming deserialisation for large JSON arrays
await using var stream = File.OpenRead("products.json");
await foreach (var product in JsonSerializer.DeserializeAsyncEnumerable<Product>(stream))
    Console.WriteLine(product!.Name);

record Person(int Id, string Name);
record Product(int Id, string Name, decimal Price);
↥ back to top

Q. What is the purpose of the JsonWriter class in C#?

In .NET 10, the JSON writer is System.Text.Json.Utf8JsonWriter — a high-performance, forward-only writer that produces UTF-8 JSON directly into a buffer or stream without intermediate string allocations.

using System.Text.Json;

// 1. Utf8JsonWriter — low-level, high-performance
await using var ms = new System.IO.MemoryStream();
await using var writer = new Utf8JsonWriter(ms,
    new JsonWriterOptions { Indented = true });

writer.WriteStartObject();
writer.WriteNumber("id", 42);
writer.WriteString("name", "Alice");
writer.WriteBoolean("active", true);
writer.WriteNull("middleName");

writer.WriteStartArray("scores");
writer.WriteNumberValue(95);
writer.WriteNumberValue(87);
writer.WriteNumberValue(92);
writer.WriteEndArray();

writer.WriteStartObject("address");
writer.WriteString("city",    "London");
writer.WriteString("country", "UK");
writer.WriteEndObject();

writer.WriteEndObject();
await writer.FlushAsync();

string json = System.Text.Encoding.UTF8.GetString(ms.ToArray());
Console.WriteLine(json);

// 2. JsonSerializer.Serialize — high-level (most common)
var person = new { Id = 42, Name = "Alice", Scores = new[] { 95, 87, 92 } };
string json2 = JsonSerializer.Serialize(person,
    new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json2);

// 3. Source-generated serialiser (.NET 10) — best performance, trimming-safe
[JsonSerializable(typeof(Product))]
internal partial class ProductContext : JsonSerializerContext { }

var product = new Product(1, "Laptop", 1200m);
string json3 = JsonSerializer.Serialize(product, ProductContext.Default.Product);
Console.WriteLine(json3); // {"Id":1,"Name":"Laptop","Price":1200}

// 4. Write directly to a file stream
await using var file = File.Create("output.json");
await using var fw = new Utf8JsonWriter(file, new JsonWriterOptions { Indented = true });
fw.WriteStartObject();
fw.WriteString("generated", DateTime.UtcNow.ToString("O"));
fw.WriteEndObject();
await fw.FlushAsync();

record Product(int Id, string Name, decimal Price);
↥ back to top

Q. What is the difference between DataContractSerializer and XmlSerializer?

  XmlSerializer DataContractSerializer
Namespace System.Xml.Serialization System.Runtime.Serialization
Opt-in/out Opt-out ([XmlIgnore]) Opt-in ([DataMember])
Private members Not serialised … With [DataMember]
Inheritance Uses [XmlInclude] Uses [KnownType]
XML output More customisable (element names, attributes) Less customisable, more strict
Performance Slower (reflection-based) Faster (generated code)
Null handling Omits null elements by default Serialises null with xsi:nil="true"
Interfaces Cannot serialise Cannot serialise
Modern recommendation Use System.Text.Json instead Use System.Text.Json instead
using System.Xml.Serialization;
using System.Runtime.Serialization;

// XmlSerializer — attribute-heavy, customisable output
[Serializable]
public class ProductXml
{
    [XmlAttribute("product-id")]   public int Id { get; set; }
    [XmlElement("product-name")]   public string Name { get; set; } = "";
    [XmlIgnore]                    public string InternalCode { get; set; } = "";
}

var xmlSer = new XmlSerializer(typeof(ProductXml));
await using var sw = new StringWriter();
xmlSer.Serialize(sw, new ProductXml { Id = 1, Name = "Laptop" });
Console.WriteLine(sw.ToString());
// <ProductXml product-id="1"><product-name>Laptop</product-name></ProductXml>

// DataContractSerializer — WCF-style, opt-in
[DataContract]
public class ProductDcs
{
    [DataMember(Order = 1)] public int    Id   { get; set; }
    [DataMember(Order = 2)] public string Name { get; set; } = "";
    /* Not decorated — NOT serialised */   public string Internal { get; set; } = "";
}

var dcSer = new DataContractSerializer(typeof(ProductDcs));
await using var ms = new MemoryStream();
dcSer.WriteObject(ms, new ProductDcs { Id = 1, Name = "Laptop" });
ms.Position = 0;
var restored = (ProductDcs)dcSer.ReadObject(ms)!;
Console.WriteLine($"{restored.Id}: {restored.Name}");

// … Modern recommendation: use System.Text.Json for new code
var json = System.Text.Json.JsonSerializer.Serialize(new { Id = 1, Name = "Laptop" });
Console.WriteLine(json); // {"Id":1,"Name":"Laptop"}
↥ back to top

Q. What is a BinaryFormatter in C#?

BinaryFormatter is obsolete and disabled by default since .NET 5, removed in .NET 9. It had critical security vulnerabilities (arbitrary code execution via deserialization gadget chains). Do not use it in new or existing code.

//  BinaryFormatter — OBSOLETE, INSECURE, REMOVED in .NET 9
// DO NOT USE:
// var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
// formatter.Serialize(stream, obj);   // throws NotSupportedException in .NET 9

// … Modern replacements:

// 1. System.Text.Json — JSON (recommended for most scenarios)
var obj = new { Id = 1, Name = "Alice" };
string json = System.Text.Json.JsonSerializer.Serialize(obj);
byte[] jsonBytes = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(obj);

// 2. MessagePack — binary, compact, fast
// dotnet add package MessagePack
// byte[] msgpack = MessagePackSerializer.Serialize(obj);

// 3. System.Runtime.Serialization with DataContractSerializer (XML or JSON)
// (still available but verbose — prefer System.Text.Json)

// 4. MemoryPack — zero-encoding binary (.NET 10 friendly)
// dotnet add package MemoryPack
// byte[] packed = MemoryPackSerializer.Serialize(obj);

// 5. Custom binary with BinaryWriter (controlled, secure)
await using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms);
bw.Write(1);       // Id
bw.Write("Alice"); // Name

ms.Position = 0;
using var br = new BinaryReader(ms);
Console.WriteLine($"{br.ReadInt32()}: {br.ReadString()}"); // 1: Alice
↥ back to top

Q. What is the purpose of the SoapFormatter class in C#?

SoapFormatter is obsolete and removed in .NET Core / .NET 5+. It was a WCF/SOAP-era serialiser that formatted object graphs as SOAP XML. Like BinaryFormatter, it had security vulnerabilities.

//  SoapFormatter — .NET Framework only, OBSOLETE
// System.Runtime.Serialization.Formatters.Soap.SoapFormatter
// Not available in .NET 5+ / .NET Core at all

// … Modern alternatives for SOAP/XML scenarios:

// 1. XmlSerializer — clean XML output (see above)
var ser = new System.Xml.Serialization.XmlSerializer(typeof(MyDto));
await using var sw = new StringWriter();
ser.Serialize(sw, new MyDto { Id = 1, Name = "Alice" });
Console.WriteLine(sw.ToString());

// 2. DataContractSerializer — WCF-compatible XML
var dcSer = new System.Runtime.Serialization.DataContractSerializer(typeof(MyDto));
await using var ms = new MemoryStream();
dcSer.WriteObject(ms, new MyDto { Id = 1, Name = "Alice" });

// 3. System.Text.Json — modern preferred serialiser
string json = System.Text.Json.JsonSerializer.Serialize(new MyDto { Id = 1, Name = "Alice" });

// 4. gRPC (Protobuf) — for service-to-service communication replacing SOAP
// dotnet add package Google.Protobuf Grpc.AspNetCore

public class MyDto
{
    public int    Id   { get; set; }
    public string Name { get; set; } = "";
}
↥ back to top

Q. What is the difference between BinaryFormatter and SoapFormatter?

  BinaryFormatter SoapFormatter
Format Compact binary (proprietary) SOAP XML (verbose)
Interop .NET only Somewhat interoperable via SOAP
Performance Faster, smaller payload Slower, larger payload
Security Critical vulnerabilities Critical vulnerabilities
Status in .NET 9+ Removed Removed (never in .NET Core)
Replacement System.Text.Json, BinaryWriter, MessagePack XmlSerializer, DataContractSerializer, gRPC
//  Both are obsolete — DO NOT USE in new code.

// … Choose the right modern serialiser based on needs:

// Scenario ’ Recommended serialiser
// REST API payloads         ’ System.Text.Json
// Configuration files       ’ System.Text.Json / YAML
// Compact binary IPC        ’ MessagePack / MemoryPack
// Custom binary protocol    ’ BinaryWriter + BinaryReader
// XML interop / SOAP legacy ’ XmlSerializer / DataContractSerializer
// Service-to-service RPC    ’ gRPC (Protobuf)

// Example: MessagePack (compact binary, fast, secure)
// dotnet add package MessagePack

// [MessagePackObject]
// public class Person
// {
//     [Key(0)] public int    Id   { get; set; }
//     [Key(1)] public string Name { get; set; } = "";
// }
// byte[] bytes   = MessagePackSerializer.Serialize(new Person { Id = 1, Name = "Alice" });
// Person person  = MessagePackSerializer.Deserialize<Person>(bytes);
// Console.WriteLine($"{person.Id}: {person.Name}"); // 1: Alice
↥ back to top

Q. What is Serialization?

Serialization is the process of converting an object's state into a format (bytes, JSON, XML, binary) that can be stored or transmitted. Deserialization is the reverse — reconstructing the object from that format.

// ” JSON Serialization (recommended in .NET 10) ”——————————————————
using System.Text.Json;

record Person(int Id, string Name, DateTime BirthDate, List<string> Hobbies);

var person = new Person(1, "Alice", new DateTime(1995, 6, 15), ["Hiking", "Reading"]);

// Serialise to JSON string
string json = JsonSerializer.Serialize(person,
    new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);
// {
//   "Id": 1,
//   "Name": "Alice",
//   "BirthDate": "1995-06-15T00:00:00",
//   "Hobbies": ["Hiking","Reading"]
// }

// Deserialise from JSON string
Person restored = JsonSerializer.Deserialize<Person>(json)!;
Console.WriteLine($"{restored.Id}: {restored.Name}");

// Serialise to bytes (more efficient — no intermediate string)
byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(person);
Person fromBytes = JsonSerializer.Deserialize<Person>(bytes)!;

// Async serialisation to file
await using var file = File.Create("person.json");
await JsonSerializer.SerializeAsync(file, person);

// Async deserialisation from file
await using var readFile = File.OpenRead("person.json");
Person fromFile = (await JsonSerializer.DeserializeAsync<Person>(readFile))!;

// ” XML Serialization ”—————————————————————————————————————————————
[Serializable]
public class ProductXml { public int Id; public string Name = ""; }

var xmlSer = new System.Xml.Serialization.XmlSerializer(typeof(ProductXml));
await using var sw = new StringWriter();
xmlSer.Serialize(sw, new ProductXml { Id = 1, Name = "Laptop" });
Console.WriteLine(sw.ToString());

// ” Custom binary (no third-party, no security risk) ”——————————————
await using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms);
bw.Write(person.Id);
bw.Write(person.Name);
bw.Write(person.BirthDate.ToBinary());
bw.Write(person.Hobbies.Count);
foreach (var h in person.Hobbies) bw.Write(h);

ms.Position = 0;
using var br = new BinaryReader(ms);
int      id      = br.ReadInt32();
string   name    = br.ReadString();
DateTime birth   = DateTime.FromBinary(br.ReadInt64());
var hobbies      = Enumerable.Range(0, br.ReadInt32()).Select(_ => br.ReadString()).ToList();
Console.WriteLine($"Restored: {id} {name} [{string.Join(", ", hobbies)}]");

Serialization types:

Type Format Use case
JSON (System.Text.Json) Text — human readable REST APIs, config, storage
XML (XmlSerializer) Text — human readable Interop, legacy SOAP
Binary (BinaryWriter) Binary — compact Custom protocols, file formats
MessagePack Binary — compact High-performance IPC, game data
Protobuf (gRPC) Binary — compact Service-to-service communication
↥ back to top

Q. How do you use the Path class in C# to work with file paths?

The static System.IO.Path class provides platform-independent methods for manipulating file and directory path strings without performing any I/O.

using System.IO;

// ” 1. Combine path segments (handles separators automatically) ”—
string full = Path.Combine("C:\\Users", "Alice", "Documents", "report.pdf");
Console.WriteLine(full);
// Windows: C:\Users\Alice\Documents\report.pdf

// ” 2. Get parts of a path ”——————————————————————————————————————
string path = @"C:\Projects\MyApp\src\Program.cs";

Console.WriteLine(Path.GetFileName(path));           // Program.cs
Console.WriteLine(Path.GetFileNameWithoutExtension(path)); // Program
Console.WriteLine(Path.GetExtension(path));          // .cs
Console.WriteLine(Path.GetDirectoryName(path));      // C:\Projects\MyApp\src
Console.WriteLine(Path.GetPathRoot(path));           // C:\

// ” 3. Change or check extension ”————————————————————————————————
string newPath = Path.ChangeExtension(path, ".bak");
Console.WriteLine(newPath);   // C:\Projects\MyApp\src\Program.bak

Console.WriteLine(Path.HasExtension("readme.txt"));  // True
Console.WriteLine(Path.HasExtension("Makefile"));    // False

// ” 4. Rooted / absolute paths ”——————————————————————————————————
Console.WriteLine(Path.IsPathRooted(@"C:\temp"));    // True
Console.WriteLine(Path.IsPathRooted(@"relative\path")); // False

string absolute = Path.GetFullPath(@".\logs\app.log");
Console.WriteLine(absolute);  // expands relative to current directory

// ” 5. Temporary files and random names ”—————————————————————————
string tempFile = Path.GetTempFileName();   // creates empty file in %TEMP%
string tempDir  = Path.GetTempPath();       // e.g. C:\Users\Alice\AppData\Local\Temp\
string random   = Path.GetRandomFileName(); // e.g. 3j4knw32.tmp (no file created)

// ” 6. Path separator constants ”—————————————————————————————————
Console.WriteLine(Path.DirectorySeparatorChar); // \ on Windows, / on Linux
Console.WriteLine(Path.PathSeparator);          // ; on Windows, : on Linux
Console.WriteLine(Path.AltDirectorySeparatorChar); // /

// ” 7. Relative paths (net5.0+) ”—————————————————————————————————
string relative = Path.GetRelativePath(@"C:\Projects\MyApp", @"C:\Projects\MyApp\src\Program.cs");
Console.WriteLine(relative);  // src\Program.cs

// ” 8. Safe file naming ”—————————————————————————————————————————
char[] invalid = Path.GetInvalidFileNameChars();
string safeName = string.Concat("my:file*name".Select(c => invalid.Contains(c) ? '_' : c));
Console.WriteLine(safeName);  // my_file_name

// Always clean up temp files
File.Delete(tempFile);

Key Path methods at a glance:

Method Returns
Combine(€) Joined path string
GetFileName(path) "Program.cs"
GetFileNameWithoutExtension(path) "Program"
GetExtension(path) ".cs"
GetDirectoryName(path) Parent folder string
GetFullPath(path) Absolute path
GetRelativePath(base, path) Relative string
GetTempPath() System temp directory
GetTempFileName() Creates and returns a temp file path
↥ back to top

Q. What is the difference between the File and FileInfo classes in C#?

Both classes operate on files, but File provides static methods for one-off operations while FileInfo is an instance class that caches file metadata and is more efficient for multiple operations on the same file.

using System.IO;

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// File (static) — convenient for single operations
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
string path = @"C:\Temp\example.txt";

// Create / write
File.WriteAllText(path, "Hello, World!");
File.AppendAllText(path, "\nAppended line.");
File.WriteAllLines(path, ["Line 1", "Line 2", "Line 3"]);

// Read
string content  = File.ReadAllText(path);
string[] lines  = File.ReadAllLines(path);
byte[] bytes    = File.ReadAllBytes(path);

// Check and manage
Console.WriteLine(File.Exists(path));            // True
File.Copy(path, @"C:\Temp\backup.txt", overwrite: true);
File.Move(path, @"C:\Temp\moved.txt");
File.Delete(@"C:\Temp\moved.txt");

// Get basic attributes
DateTime created  = File.GetCreationTime(path);
DateTime modified = File.GetLastWriteTime(path);
FileAttributes attrs = File.GetAttributes(path);

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// FileInfo (instance) — better for multiple operations on one file
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
var fi = new FileInfo(@"C:\Temp\backup.txt");

// Cached metadata — no extra syscall for each property
Console.WriteLine(fi.Name);              // backup.txt
Console.WriteLine(fi.Extension);        // .txt
Console.WriteLine(fi.Length);           // file size in bytes
Console.WriteLine(fi.DirectoryName);    // C:\Temp
Console.WriteLine(fi.FullName);         // C:\Temp\backup.txt
Console.WriteLine(fi.CreationTime);
Console.WriteLine(fi.IsReadOnly);

// Operations via instance
fi.CopyTo(@"C:\Temp\copy.txt", overwrite: true);
fi.MoveTo(@"C:\Temp\renamed.txt");

using StreamReader sr = fi.OpenText();
Console.WriteLine(sr.ReadToEnd());

// Refresh after external changes
fi.Refresh();
Console.WriteLine(fi.Length);           // up-to-date size

File vs FileInfo:

Aspect File (static) FileInfo (instance)
Type Static utility class Instance class
Security checks Per call Once at construction
Multiple ops on same file Repeated security checks More efficient
Metadata caching No Yes (call Refresh() to update)
Best for One-off operations Multiple ops on the same file
↥ back to top

Q. How do you perform asynchronous file I/O in C#?

Async file I/O prevents blocking the calling thread during disk operations, which is critical in web servers and UI applications.

using System.IO;
using System.Text;

// ” 1. Read all text asynchronously ”————————————————————————————
string path = @"C:\Temp\data.txt";
string content = await File.ReadAllTextAsync(path);
Console.WriteLine(content);

// ” 2. Write all text asynchronously ”———————————————————————————
await File.WriteAllTextAsync(path, "Async content written.");

// ” 3. Read / write lines asynchronously ”————————————————————————
await File.WriteAllLinesAsync(path, ["Line A", "Line B", "Line C"]);
string[] lines = await File.ReadAllLinesAsync(path);

// ” 4. StreamReader / StreamWriter (streaming large files) ”——————
// Read large file line by line without loading all into memory
await using var reader = new StreamReader(path, Encoding.UTF8);
string? line;
while ((line = await reader.ReadLineAsync()) is not null)
    Console.WriteLine(line);

// Write using StreamWriter
await using var writer = new StreamWriter(path, append: false, Encoding.UTF8);
await writer.WriteLineAsync("Header");
for (int i = 1; i <= 5; i++)
    await writer.WriteLineAsync($"Row {i}");
// FlushAsync called automatically on DisposeAsync

// ” 5. FileStream with explicit async I/O ”———————————————————————
await using var fs = new FileStream(
    path,
    FileMode.Create,
    FileAccess.Write,
    FileShare.None,
    bufferSize: 4096,
    useAsync: true);          //  enables true OS-level async

byte[] data = Encoding.UTF8.GetBytes("Binary async write");
await fs.WriteAsync(data);

// ” 6. CancellationToken support ”————————————————————————————————
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
    string text = await File.ReadAllTextAsync(path, cts.Token);
    Console.WriteLine(text);
}
catch (OperationCanceledException)
{
    Console.WriteLine("File read cancelled.");
}

// ” 7. Process multiple files concurrently ”——————————————————————
string[] files = Directory.GetFiles(@"C:\Temp\Logs", "*.log");

IEnumerable<Task<string>> readTasks = files.Select(f => File.ReadAllTextAsync(f));
string[] contents = await Task.WhenAll(readTasks);
Console.WriteLine($"Read {contents.Length} log files.");

Sync vs async file I/O:

Aspect Synchronous Asynchronous
Thread blocking Blocks caller Frees caller thread
Throughput Lower (1 thread per op) Higher (thread pool)
Code complexity Simple Requires async/await
Use in ASP.NET Core Avoid … Always prefer
Use in Console/scripts … Acceptable Optional
↥ back to top

# 8. REGULAR EXPRESSION


Q. What is a regular expression in C# and what is it used for?

A regular expression (regex) is a sequence of characters that defines a search pattern. In C#, regexes are provided by the System.Text.RegularExpressions namespace.

Common use cases:

Use case Example pattern
Validate email ^[\w\.-]+@[\w\.-]+\.\w{2,}$
Validate phone ^\+?[\d\s\-()]{7,15}$
Extract dates \d{4}-\d{2}-\d{2}
Parse log files Named capture groups
Find/replace text Regex.Replace
Split on patterns Regex.Split
Scrape HTML/JSON (use with caution)
using System.Text.RegularExpressions;

string text = "Order #1234 placed on 2026-04-19 for $99.99";

// Check if a date exists
bool hasDate = Regex.IsMatch(text, @"\d{4}-\d{2}-\d{2}");
Console.WriteLine(hasDate); // True

// Extract the date
Match m = Regex.Match(text, @"\d{4}-\d{2}-\d{2}");
Console.WriteLine(m.Value);  // 2026-04-19

// Extract the order number
Match order = Regex.Match(text, @"#(\d+)");
Console.WriteLine(order.Groups[1].Value); // 1234

// Replace price format
string cleaned = Regex.Replace(text, @"\$[\d.]+", "[PRICE]");
Console.WriteLine(cleaned); // Order #1234 placed on 2026-04-19 for [PRICE]
↥ back to top

Q. What is the purpose of the Regex class in C#? How do you create and use a regular expression?

The Regex class in System.Text.RegularExpressions is the primary API for working with regular expressions in .NET. It supports matching, replacing, splitting, and extracting.

using System.Text.RegularExpressions;

// 1. Static methods — convenient for one-off patterns
bool isMatch   = Regex.IsMatch("hello123", @"\d+");          // true — contains digits
Match match    = Regex.Match("hello123", @"\d+");             // first match
MatchCollection all = Regex.Matches("a1 b2 c3", @"\d");      // all matches
string replaced = Regex.Replace("foo bar", @"\s+", "_");      // foo_bar
string[] parts  = Regex.Split("one,two,,three", @",+");       // ["one","two","three"]

// 2. Instance Regex — reuse compiled pattern (faster for repeated use)
var re = new Regex(@"\d{4}-\d{2}-\d{2}", RegexOptions.Compiled);
Console.WriteLine(re.IsMatch("Today is 2026-04-19")); // true

// 3. Source-generated Regex — best performance (.NET 7+, AOT-safe)
// Place in a partial class:
Console.WriteLine(DatePattern().IsMatch("2026-04-19")); // true

// Key methods summary:
// IsMatch(input)       ’ bool — does pattern occur?
// Match(input)         ’ Match — first occurrence
// Matches(input)       ’ MatchCollection — all occurrences
// Replace(input, repl) ’ string — replace matches
// Split(input)         ’ string[] — split on pattern

[GeneratedRegex(@"\d{4}-\d{2}-\d{2}", RegexOptions.None)]
static partial Regex DatePattern();
↥ back to top

Q. How do you match a pattern in a string using regular expressions in C#?

using System.Text.RegularExpressions;

string input = "Phone: +44-20-7946-0958, Alt: 01632-960-500";

// 1. IsMatch — check if pattern exists anywhere
bool found = Regex.IsMatch(input, @"\d{4}");
Console.WriteLine(found); // true

// 2. Match — get the FIRST match
Match first = Regex.Match(input, @"\d[\d\-]+\d");
if (first.Success)
    Console.WriteLine($"First: {first.Value}"); // 44-20-7946-0958

// 3. Matches — get ALL matches (lazy enumeration)
foreach (Match m in Regex.Matches(input, @"\d[\d\-]+\d"))
    Console.WriteLine(m.Value);
// 44-20-7946-0958
// 01632-960-500

// 4. NextMatch — iterate manually
Match m2 = Regex.Match(input, @"\+?\d[\d\-]+\d");
while (m2.Success)
{
    Console.WriteLine(m2.Value);
    m2 = m2.NextMatch();
}

// 5. Match position and length
Match pos = Regex.Match(input, @"\d{4,}");
Console.WriteLine($"Value='{pos.Value}' at index {pos.Index}, length {pos.Length}");
// Value='7946' at index ...

// 6. Anchors — ^ (start), $ (end), \b (word boundary)
Console.WriteLine(Regex.IsMatch("hello", @"^\w+$")); // true — entire string is word chars
Console.WriteLine(Regex.IsMatch("hello world", @"^\w+$")); // false — contains space

// 7. Multiline matching
string multiline = "line1\nline2\nline3";
var lines = Regex.Matches(multiline, @"^\w+$", RegexOptions.Multiline);
foreach (Match l in lines) Console.WriteLine(l.Value); // line1, line2, line3
↥ back to top

Q. How do you replace text in a string using regular expressions in C#?

using System.Text.RegularExpressions;

// 1. Simple replacement
string text = "The price is $12.50 and $7.99";
string result = Regex.Replace(text, @"\$[\d.]+", "[PRICE]");
Console.WriteLine(result); // The price is [PRICE] and [PRICE]

// 2. Back-references — reuse matched groups in replacement
string csv = "Smith, John; Doe, Jane";
// Reorder "Last, First" ’ "First Last"
string reordered = Regex.Replace(csv, @"(\w+),\s*(\w+)", "$2 $1");
Console.WriteLine(reordered); // John Smith; Jane Doe

// 3. Named groups in replacement
string log = "2026-04-19 ERROR something failed";
string reformatted = Regex.Replace(log,
    @"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})",
    "${day}/${month}/${year}");
Console.WriteLine(reformatted); // 19/04/2026 ERROR something failed

// 4. MatchEvaluator — dynamic replacement via delegate
string sentence = "hello world foo bar";
string titleCase = Regex.Replace(sentence, @"\b\w+\b", m =>
    char.ToUpper(m.Value[0]) + m.Value[1..]);
Console.WriteLine(titleCase); // Hello World Foo Bar

// 5. Replace with count limit
string repeated = "aaa bbb ccc";
string limited = Regex.Replace(repeated, @"\b\w+\b", "X", count: 2);
Console.WriteLine(limited); // X X ccc

// 6. Source-generated replace (best performance)
string masked = MaskDigits().Replace("Card: 4111-1111-1111-1111", "*");
Console.WriteLine(masked); // Card: ****-****-****-****

[GeneratedRegex(@"\d")]
static partial Regex MaskDigits();
↥ back to top

Q. What are some common use cases for regular expressions in C#?

using System.Text.RegularExpressions;

// 1. Email validation
bool IsValidEmail(string email) =>
    Regex.IsMatch(email, @"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$");

Console.WriteLine(IsValidEmail("user@example.com")); // true
Console.WriteLine(IsValidEmail("bad@.com"));          // false

// 2. Phone number extraction
string text = "Call us: +1-800-555-0100 or 020 7946 0958";
foreach (Match m in Regex.Matches(text, @"\+?[\d][\d\s\-]{6,}\d"))
    Console.WriteLine(m.Value); // +1-800-555-0100, 020 7946 0958

// 3. URL extraction
string html = "<a href='https://example.com'>link</a> <a href='http://foo.org/path'>other</a>";
foreach (Match m in Regex.Matches(html, @"https?://[^\s'""]+"))
    Console.WriteLine(m.Value);

// 4. Password strength validation
bool IsStrongPassword(string pwd) =>
    Regex.IsMatch(pwd, @"^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[\W_]).{8,}$");

Console.WriteLine(IsStrongPassword("Passw0rd!"));  // true
Console.WriteLine(IsStrongPassword("password"));   // false

// 5. Log file parsing with named groups
string log = "2026-04-19 14:30:55 ERROR [App] NullReferenceException";
Match logMatch = Regex.Match(log,
    @"(?<date>\d{4}-\d{2}-\d{2}) (?<time>[\d:]+) (?<level>\w+) \[(?<source>[^\]]+)\] (?<message>.+)");

Console.WriteLine($"Date:    {logMatch.Groups["date"].Value}");
Console.WriteLine($"Level:   {logMatch.Groups["level"].Value}");
Console.WriteLine($"Message: {logMatch.Groups["message"].Value}");

// 6. Sanitise / strip HTML tags
string stripped = Regex.Replace("<p>Hello <b>World</b></p>", @"<[^>]+>", "");
Console.WriteLine(stripped); // Hello World

// 7. Split on multiple delimiters
string[] tokens = Regex.Split("one,two;three|four", @"[,;|]");
Console.WriteLine(string.Join(" | ", tokens)); // one | two | three | four

// 8. Find duplicate words
string dupes = "the the cat sat on on the mat";
foreach (Match m in Regex.Matches(dupes, @"\b(\w+)\s+\1\b", RegexOptions.IgnoreCase))
    Console.WriteLine($"Duplicate: '{m.Value}'");
↥ back to top

Q. How do you validate an email address using regular expressions in C#?

using System.Text.RegularExpressions;

// 1. Source-generated (best — .NET 7+, AOT-safe, zero overhead)
public static partial class EmailValidator
{
    // RFC 5322 simplified — covers >99% of real-world addresses
    [GeneratedRegex(
        @"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$",
        RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture)]
    private static partial Regex EmailRegex();

    public static bool IsValid(string email) =>
        !string.IsNullOrWhiteSpace(email) && EmailRegex().IsMatch(email);
}

// Test
string[] emails =
[
    "user@example.com",        // …
    "user.name+tag@domain.co", // …
    "user@sub.domain.org",     // …
    "bad@.com",                // 
    "@nodomain",               // 
    "noDomainExtension@abc",   // 
    "spaces in@email.com",     // 
];

foreach (string email in emails)
    Console.WriteLine($"{email,-35}{(EmailValidator.IsValid(email) ? "…" : "")}");

// 2. Static Regex (cached instance — fine for most code)
var emailRegex = new Regex(
    @"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$",
    RegexOptions.Compiled | RegexOptions.IgnoreCase);

bool valid = emailRegex.IsMatch("user@example.com");

// 3. MailAddress parse — .NET built-in alternative (handles more edge cases)
static bool IsValidEmail2(string email)
{
    try
    {
        var addr = new System.Net.Mail.MailAddress(email);
        return addr.Address == email.Trim();
    }
    catch { return false; }
}
Console.WriteLine(IsValidEmail2("user@example.com")); // true
Console.WriteLine(IsValidEmail2("notanemail"));        // false
↥ back to top

Q. What is the difference between Regex.Match and Regex.IsMatch in C#?

  Regex.IsMatch Regex.Match
Returns bool — does pattern exist? Match object — first occurrence
Performance Slightly faster (stops at first match, no object) Allocates a Match object
Use when You only need yes/no You need position, value, or groups
No match Returns false Returns Match with Success = false
using System.Text.RegularExpressions;

string input = "Order #4271 shipped on 2026-04-19";

// IsMatch — fastest, just need to know if pattern exists
bool hasOrder = Regex.IsMatch(input, @"#\d+");
Console.WriteLine(hasOrder); // true

// Match — need the actual value or position
Match m = Regex.Match(input, @"#(\d+)");
if (m.Success)
{
    Console.WriteLine($"Full match:  {m.Value}");          // #4271
    Console.WriteLine($"Group 1:     {m.Groups[1].Value}"); // 4271
    Console.WriteLine($"Index:       {m.Index}");           // position in string
    Console.WriteLine($"Length:      {m.Length}");
}

// Match with no match — always check Success before accessing Value
Match noMatch = Regex.Match(input, @"\d{8}");
Console.WriteLine(noMatch.Success); // false
// Console.WriteLine(noMatch.Value); // "" — safe to call but meaningless

// Matches — all occurrences
MatchCollection all = Regex.Matches(input, @"\d+");
foreach (Match each in all)
    Console.Write($"{each.Value} "); // 4271 2026 04 19
Console.WriteLine();

// Rule of thumb:
// Just validating?        ’ IsMatch
// Need value/groups?      ’ Match
// Need all occurrences?   ’ Matches
// Need replace/transform? ’ Replace + MatchEvaluator
↥ back to top

Q. How do you extract groups from a match using regular expressions in C#?

using System.Text.RegularExpressions;

// 1. Numbered capture groups — ()
string log = "2026-04-19 14:32:05 ERROR NullReferenceException";
Match m = Regex.Match(log, @"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (\w+) (.+)");

if (m.Success)
{
    Console.WriteLine($"Date:    {m.Groups[1].Value}"); // 2026-04-19
    Console.WriteLine($"Time:    {m.Groups[2].Value}"); // 14:32:05
    Console.WriteLine($"Level:   {m.Groups[3].Value}"); // ERROR
    Console.WriteLine($"Message: {m.Groups[4].Value}"); // NullReferenceException
}

// 2. Named capture groups — (?<name>...)   recommended
Match named = Regex.Match(log,
    @"(?<date>\d{4}-\d{2}-\d{2}) (?<time>[\d:]+) (?<level>\w+) (?<msg>.+)");

Console.WriteLine($"Date:  {named.Groups["date"].Value}");
Console.WriteLine($"Level: {named.Groups["level"].Value}");
Console.WriteLine($"Msg:   {named.Groups["msg"].Value}");

// 3. Multiple matches with groups
string data = "Alice:30, Bob:25, Carol:35";
foreach (Match person in Regex.Matches(data, @"(?<name>[A-Z]\w+):(?<age>\d+)"))
{
    Console.WriteLine($"{person.Groups["name"].Value} is {person.Groups["age"].Value}");
}
// Alice is 30
// Bob is 25
// Carol is 35

// 4. Optional groups — check Success on the group
Match optional = Regex.Match("+44 20 7946 0958",
    @"(?<country>\+\d{1,3})?\s?(?<number>[\d\s]{7,})");
Console.WriteLine(optional.Groups["country"].Success
    ? optional.Groups["country"].Value
    : "(no country code)");

// 5. Non-capturing group — (?:...) — group without capturing
// Used for alternation or quantifiers without a group slot
Match nc = Regex.Match("colour or color",
    @"colo(?:u)?r"); // (?:u)? — optional 'u', not captured
Console.WriteLine(nc.Value); // colour
↥ back to top

Q. How do you handle case sensitivity in regular expressions in C#?

using System.Text.RegularExpressions;

string input = "Hello WORLD hello world";

// 1. Case-sensitive by default
MatchCollection sensitive = Regex.Matches(input, @"hello");
Console.WriteLine(sensitive.Count); // 1 — only lowercase "hello"

// 2. Case-insensitive via RegexOptions.IgnoreCase
MatchCollection insensitive = Regex.Matches(input, @"hello", RegexOptions.IgnoreCase);
Console.WriteLine(insensitive.Count); // 2 — "Hello" and "hello" (lowercase only, HELLO matches too)

// Wait — actually "Hello" and "hello" and "HELLO" would all match
MatchCollection all = Regex.Matches(input, @"hello", RegexOptions.IgnoreCase);
foreach (Match m in all)
    Console.WriteLine($"'{m.Value}' at {m.Index}"); // Hello, hello

// 3. Inline flag (?i) — embed in pattern (useful for partial case-insensitivity)
Match m1 = Regex.Match("HTTP/1.1 200 OK", @"(?i)http");
Console.WriteLine(m1.Success); // true

// Disable case-insensitive for part of pattern with (?-i)
Match m2 = Regex.Match("fooBAR", @"(?i)foo(?-i)BAR"); // foo case-insensitive, BAR must be exact
Console.WriteLine(m2.Success); // true
Match m3 = Regex.Match("foobar", @"(?i)foo(?-i)BAR");
Console.WriteLine(m3.Success); // false — "bar" doesn\'t match "BAR"

// 4. Source-generated with IgnoreCase
string email = "User@Example.COM";
Console.WriteLine(ValidEmail().IsMatch(email)); // true

[GeneratedRegex(@"^[\w.%+\-]+@[\w.\-]+\.[a-z]{2,}$", RegexOptions.IgnoreCase)]
static partial Regex ValidEmail();

// 5. Combined options
var re = new Regex(@"hello\s+world",
    RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline);
Console.WriteLine(re.IsMatch("HELLO\nWORLD")); // true (Singleline: . matches \n)
↥ back to top

Q. How do you create a precompiled Regex object in .NET?

There are three levels of “precompiled” regex in modern .NET:

Approach Compilation Performance AOT-safe
new Regex(pattern) Interpreted (default) Baseline
new Regex(pattern, RegexOptions.Compiled) JIT-compiled to IL ~2–3— faster
[GeneratedRegex] source generator (.NET 7+) Compiled at build time Fastest, no startup cost
using System.Text.RegularExpressions;

// 1. RegexOptions.Compiled — JIT-compiles to IL on first use
// Best for patterns used many times in a hot path
private static readonly Regex _emailRegex = new Regex(
    @"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$",
    RegexOptions.Compiled | RegexOptions.IgnoreCase);

// Store as static readonly so compilation happens once per AppDomain
Console.WriteLine(_emailRegex.IsMatch("user@example.com")); // true

// 2. Source-generated Regex — [GeneratedRegex] (.NET 7+)
// Generates optimised C# code at BUILD TIME — no runtime compilation
// AOT-compatible, trim-safe, zero startup cost
public static partial class Patterns
{
    [GeneratedRegex(
        @"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$",
        RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture,
        matchTimeoutMilliseconds: 1000)]
    public static partial Regex Email();

    [GeneratedRegex(@"\d{4}-\d{2}-\d{2}")]
    public static partial Regex IsoDate();

    [GeneratedRegex(@"(?<scheme>https?)://(?<host>[^/\s]+)(?<path>/[^\s]*)?")]
    public static partial Regex Url();
}

// Usage
Console.WriteLine(Patterns.Email().IsMatch("user@example.com")); // true
Console.WriteLine(Patterns.IsoDate().IsMatch("2026-04-19"));     // true

Match url = Patterns.Url().Match("https://example.com/path?q=1");
Console.WriteLine(url.Groups["scheme"].Value); // https
Console.WriteLine(url.Groups["host"].Value);   // example.com
Console.WriteLine(url.Groups["path"].Value);   // /path?q=1

// 3. Performance comparison
// Interpreted (~1—) ’ Compiled (~3—) ’ GeneratedRegex (~5—+)
// Use GeneratedRegex for new code targeting .NET 7+
↥ back to top

Q. What are regex quantifiers and character classes in C#?

Quantifiers control how many times a pattern element must match. Character classes define sets of characters to match at a position.

using System.Text.RegularExpressions;

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Character Classes
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// \d  — digit [0-9]
// \D  — non-digit
// \w  — word char [a-zA-Z0-9_]
// \W  — non-word char
// \s  — whitespace (\t, \n, \r, space)
// \S  — non-whitespace
// .   — any char except \n (use RegexOptions.Singleline to include \n)
// [abc]     — any of a, b, c
// [^abc]    — any EXCEPT a, b, c
// [a-z]     — range a to z
// [A-Za-z0-9] — letters and digits

var digits  = Regex.Matches("abc123def456", @"\d+");
foreach (Match m in digits) Console.WriteLine(m.Value);  // 123   456

var words   = Regex.Matches("Hello, World! 42", @"\w+");
foreach (Match m in words) Console.WriteLine(m.Value);   // Hello  World  42

// Hex colour
bool isHex = Regex.IsMatch("#1aF9b3", @"^#[0-9A-Fa-f]{6}$");  // True

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Quantifiers
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// *    — 0 or more (greedy)
// +    — 1 or more (greedy)
// ?    — 0 or 1
// {n}  — exactly n
// {n,} — at least n
// {n,m}— between n and m
// *?   — 0 or more (lazy)
// +?   — 1 or more (lazy)

string text = "<b>bold</b> and <i>italic</i>";

// Greedy — matches as much as possible
var greedy = Regex.Match(text, @"<.+>");
Console.WriteLine(greedy.Value);   // <b>bold</b> and <i>italic</i>

// Lazy — matches as little as possible
var lazy = Regex.Match(text, @"<.+?>");
Console.WriteLine(lazy.Value);     // <b>

// All tags (lazy)
var tags = Regex.Matches(text, @"<.+?>");
foreach (Match m in tags) Console.WriteLine(m.Value);
// <b>  </b>  <i>  </i>

// Phone number: exactly 10 digits
bool validPhone = Regex.IsMatch("5551234567", @"^\d{10}$");  // True

// Postal code: 5 or 9 digits (US ZIP)
bool zip = Regex.IsMatch("12345-6789", @"^\d{5}(-\d{4})?$");  // True

// Password: at least 8 chars, one upper, one lower, one digit
bool strongPwd = Regex.IsMatch("MyPass1!", @"^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$"); // True

// ” Split on multiple delimiters ”———————————————————————————————
string csv = "one, two;three|four";
string[] parts = Regex.Split(csv, @"[,;|]\s*");
Console.WriteLine(string.Join(" | ", parts));  // one | two | three | four

Quantifier quick reference:

Quantifier Meaning Example Matches
* 0 or more a* "", "a", "aaa"
+ 1 or more a+ "a", "aaa"
? 0 or 1 colou?r "color", "colour"
{3} Exactly 3 \d{3} "123"
{2,4} 2 to 4 \w{2,4} "ab", "abcd"
*? Lazy 0+ <.*?> Shortest match
↥ back to top

Q. How do you use named capturing groups and Regex.Split in C#?

Named groups ((?<name>pattern)) allow you to refer to captured substrings by name instead of index, making patterns more readable and maintainable. Regex.Split divides a string at each match of a pattern.

using System.Text.RegularExpressions;

// ” 1. Named groups ”—————————————————————————————————————————————
// Parse a date in the format  YYYY-MM-DD
var datePattern = new Regex(@"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})");

Match m = datePattern.Match("Event on 2026-04-19 at noon");
if (m.Success)
{
    Console.WriteLine(m.Groups["year"].Value);   // 2026
    Console.WriteLine(m.Groups["month"].Value);  // 04
    Console.WriteLine(m.Groups["day"].Value);    // 19
    Console.WriteLine($"{m.Groups["day"].Value}/{m.Groups["month"].Value}/{m.Groups["year"].Value}");
    // 19/04/2026
}

// ” 2. Multiple matches with named groups ”———————————————————————
string log = "ERROR 2026-01-10 | INFO 2026-01-11 | WARN 2026-01-12";
var logPattern = new Regex(@"(?<level>\w+) (?<date>\d{4}-\d{2}-\d{2})");

foreach (Match entry in logPattern.Matches(log))
    Console.WriteLine($"{entry.Groups["level"].Value} on {entry.Groups["date"].Value}");
// ERROR on 2026-01-10
// INFO  on 2026-01-11
// WARN  on 2026-01-12

// ” 3. Backreference with named group ”———————————————————————————
// Match doubled words: "the the", "is is"
var doubled = new Regex(@"\b(?<word>\w+)\s+\k<word>\b", RegexOptions.IgnoreCase);
Console.WriteLine(doubled.IsMatch("The the quick fox"));  // True
string cleaned = doubled.Replace("This is is a test test.", "${word}");
Console.WriteLine(cleaned);  // This is a test.

// ” 4. Replace using named group references ”—————————————————————
string dates = "Born: 1990-06-15, Hired: 2015-03-22";
// Reformat YYYY-MM-DD ’ DD/MM/YYYY
string reformatted = Regex.Replace(dates,
    @"(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})",
    "${d}/${m}/${y}");
Console.WriteLine(reformatted);  // Born: 15/06/1990, Hired: 22/03/2015

// ” 5. Regex.Split ”——————————————————————————————————————————————
// Basic split on whitespace
string sentence = "Split   this\tsentence\nnow";
string[] words = Regex.Split(sentence, @"\s+");
Console.WriteLine(string.Join("|", words));  // Split|this|sentence|now

// Split on multiple delimiters
string data = "one,two;three|four::five";
string[] items = Regex.Split(data, @"[,;|:]+");
Console.WriteLine(string.Join(" ", items));  // one two three four five

// Split and keep the delimiter (place pattern in capture group)
string csv = "a,b,c";
string[] withSeps = Regex.Split(csv, @"(,)");
Console.WriteLine(string.Join(" ", withSeps));  // a , b , c

// Split into fixed-width chunks
string hex = "DEADBEEFCAFE";
string[] bytes2 = Regex.Split(hex, @"(?<=\G.{2})(?=.)");  // every 2 chars
Console.WriteLine(string.Join("-", bytes2));  // DE-AD-BE-EF-CA-FE
↥ back to top

Q. What are lookahead and lookbehind assertions in regular expressions?

Lookahead ((?=...), (?!...)) and lookbehind ((?<=...), (?<!...)) are zero-width assertions — they check what's ahead/behind the current position without consuming characters.

using System.Text.RegularExpressions;

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Positive lookahead  (?=pattern)  — position is followed by pattern
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Find all numbers followed by "px"
var pixelValues = Regex.Matches("width:100px height:200px margin:5em", @"\d+(?=px)");
foreach (Match m in pixelValues) Console.Write($"{m.Value} "); // 100 200

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Negative lookahead  (?!pattern)  — NOT followed by pattern
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Match numbers NOT followed by "px"
var nonPx = Regex.Matches("width:100px margin:5em font:16px zoom:2", @"\d+(?!px)");
foreach (Match m in nonPx) Console.Write($"{m.Value} "); // 5 2

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Positive lookbehind  (?<=pattern)  — preceded by pattern
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Extract prices (number preceded by "$")
var prices = Regex.Matches("Items: $9.99, $24.50, £5.00", @"(?<=\$)\d+\.\d{2}");
foreach (Match m in prices) Console.Write($"{m.Value} "); // 9.99 24.50

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Negative lookbehind  (?<!pattern)  — NOT preceded by pattern
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Find digits NOT preceded by "$"
var nonDollar = Regex.Matches("tax:$10 qty:5 price:$99", @"(?<!\$)\b\d+\b");
foreach (Match m in nonDollar) Console.Write($"{m.Value} "); // 5

// ” Password strength — lookaheads for multiple requirements ”———
// At least 8 chars, one uppercase, one lowercase, one digit, one special char
string passwordPattern = @"^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$";
Console.WriteLine(Regex.IsMatch("MyP@ss1!", passwordPattern));    // True
Console.WriteLine(Regex.IsMatch("weakpass", passwordPattern));    // False

// ” Insert separator before each uppercase in camelCase ”————————
// Lookbehind to find position after lowercase, lookahead for uppercase
string camel = "getFirstNameById";
string snake = Regex.Replace(camel, @"(?<=[a-z])(?=[A-Z])", "_").ToLower();
Console.WriteLine(snake);  // get_first_name_by_id

// ” Remove trailing whitespace per line (multiline mode) ”———————
string multiline = "Hello   \nWorld  \nDone";
string trimmed = Regex.Replace(multiline, @"[ \t]+(?=\r?\n|$)", "",
    RegexOptions.Multiline);
Console.WriteLine(trimmed);
// Hello
// World
// Done

Lookaround summary:

Syntax Name Description
(?=abc) Positive lookahead Followed by abc
(?!abc) Negative lookahead NOT followed by abc
(?<=abc) Positive lookbehind Preceded by abc
(?<!abc) Negative lookbehind NOT preceded by abc

Key property: Lookarounds are zero-width — they assert a condition but do not consume any characters, so the matched text does not include the lookaround content.

↥ back to top

# 9. EXCEPTION HANDLING


Q. What is exception handling in C# and why is it important?

Exception handling is the mechanism for responding to runtime errors in a controlled way, preventing application crashes and allowing graceful recovery or meaningful error reporting.

Why it matters:

flowchart TD
    A["Execute code in\ntry block"] --> B{Exception\nthrown?}
    B -->|No| C["Continue normal\nexecution"]
    B -->|Yes| D{Matching\ncatch block?}
    D -->|Yes| E["Execute matching\ncatch block"]
    D -->|No| F["Propagate up\ncall stack"]
    E --> G["Execute\nfinally block"]
    C --> G
    F --> G
    G --> H{Was exception\nhandled?}
    H -->|Yes| I["Continue after\ntry-catch"]
    H -->|No| J["Unhandled Exception\nApp terminates / crash"]

    style E fill:#27ae60,color:#fff
    style J fill:#e74c3c,color:#fff
    style G fill:#f39c12,color:#fff
// Without exception handling — crash on bad input
int.Parse("abc"); // FormatException — app crashes

// With exception handling — graceful degradation
try
{
    int value = int.Parse("abc");
    Console.WriteLine(value);
}
catch (FormatException ex)
{
    Console.WriteLine($"Invalid number format: {ex.Message}");
}
finally
{
    Console.WriteLine("Always runs — clean up resources here");
}
↥ back to top

Q. How do you handle exceptions in C#? What is the difference between try, catch, and finally blocks?

// try   — code that might throw
// catch — handles a specific exception type
// finally — always runs (cleanup), whether or not an exception occurred

try
{
    string[] lines = await File.ReadAllLinesAsync("data.csv"); // may throw
    int count = lines.Length;
    Console.WriteLine($"Lines: {count}");
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"File not found: {ex.FileName}");
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine($"No permission: {ex.Message}");
}
catch (IOException ex)
{
    Console.WriteLine($"I/O error: {ex.Message}");
}
catch (Exception ex) // catch-all — least specific last
{
    Console.WriteLine($"Unexpected: {ex.GetType().Name}{ex.Message}");
}
finally
{
    // always executed — even if return or exception in catch
    Console.WriteLine("Cleanup complete");
}

// Exception filters — when clause (.NET 6+ idiomatic)
try
{
    using var client = new HttpClient();
    string data = await client.GetStringAsync("https://api.example.com/data");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
    Console.WriteLine("Resource not found (404)");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
    Console.WriteLine("Unauthorized (401)");
}
catch (TaskCanceledException)
{
    Console.WriteLine("Request timed out");
}
↥ back to top

Q. What is the Exception class, its key properties, and which class is the base for all exceptions?

System.Exception is the base class for all exceptions in .NET. Every exception ultimately derives from it.

System.Exception
”” System.SystemException          (CLR/runtime exceptions)
”   ”” NullReferenceException
”   ”” IndexOutOfRangeException
”   ”” InvalidOperationException
”   ”” ArgumentException
”   ”   ”” ArgumentNullException
”   ”   ””” ArgumentOutOfRangeException
”   ”” IOException
”   ”   ””” FileNotFoundException
”   ”” OverflowException
”   ”” FormatException
”   ”” StackOverflowException
”   ””” OutOfMemoryException
””” System.ApplicationException     (user/app exceptions — rarely used directly)

Key properties:

try
{
    throw new InvalidOperationException("Cannot process empty order",
        innerException: new ArgumentNullException("orderId"));
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);           // Cannot process empty order
    Console.WriteLine(ex.GetType().Name);    // InvalidOperationException
    Console.WriteLine(ex.StackTrace);        // full call stack
    Console.WriteLine(ex.Source);            // assembly where exception originated
    Console.WriteLine(ex.HResult);           // HRESULT error code (interop)
    Console.WriteLine(ex.HelpLink);          // optional URL for more info
    Console.WriteLine(ex.Data["key"]);       // custom key-value pairs
    Console.WriteLine(ex.InnerException?.Message); // ArgumentNullException message
    Console.WriteLine(ex.ToString());        // full exception string (type + message + stack)
}

// Adding custom data to an exception
var ex2 = new InvalidOperationException("Order failed");
ex2.Data["OrderId"]  = 1234;
ex2.Data["UserId"]   = "alice";
ex2.Data["Timestamp"] = DateTime.UtcNow;
throw ex2;
↥ back to top

Q. What is the InnerException property in C#?

InnerException preserves the original cause of an exception when it is caught and re-thrown inside a higher-level exception. This allows you to inspect the full exception chain.

// Wrapping a low-level exception with a high-level one
public async Task<Order> LoadOrderAsync(int id)
{
    try
    {
        return await _repository.GetByIdAsync(id);
    }
    catch (SqlException ex) // low-level DB exception
    {
        // Wrap with domain-level exception, preserving original as InnerException
        throw new OrderNotFoundException($"Order {id} could not be loaded.", innerException: ex);
    }
}

// Inspecting the chain
try
{
    await LoadOrderAsync(999);
}
catch (Exception ex)
{
    Console.WriteLine($"Top-level: {ex.Message}");

    Exception? inner = ex.InnerException;
    while (inner is not null)
    {
        Console.WriteLine($"  Caused by: [{inner.GetType().Name}] {inner.Message}");
        inner = inner.InnerException;
    }
}

// Flatten AggregateException inner exceptions
try
{
    await Task.WhenAll(
        Task.Run(() => throw new Exception("Task 1 failed")),
        Task.Run(() => throw new Exception("Task 2 failed")));
}
catch (AggregateException ae)
{
    foreach (var inner in ae.Flatten().InnerExceptions)
        Console.WriteLine($"  Inner: {inner.Message}");
}
↥ back to top

Q. What is the purpose of the throw keyword? What is the difference between throw, throw ex, and throw new?

  throw throw ex throw new ExType(...)
Stack trace Preserved … Reset to current line New exception, new trace
Use when Re-throwing the same exception Avoid — loses origin Wrapping with context
InnerException N/A N/A Preserve original as inner
// throw — re-throw preserving original stack trace (ALWAYS prefer this)
try
{
    await File.ReadAllTextAsync("missing.txt");
}
catch (FileNotFoundException)
{
    // log, then re-throw — stack trace points to the original throw site
    Console.WriteLine("Logged the error");
    throw; // … preserves full stack trace
}

// throw ex — resets stack trace (AVOID)
// catch (FileNotFoundException ex)
// {
//     throw ex; //  stack trace now starts HERE, original location lost
// }

// throw new — wrap with context (preserve original as InnerException)
try
{
    await LoadConfigAsync("appsettings.json");
}
catch (IOException ex)
{
    // Add domain context while preserving original exception
    throw new ApplicationException("Failed to start: config unavailable.", innerException: ex); // …
}

// throw new without inner — only use when starting a fresh exception
static void ValidateAge(int age)
{
    if (age < 0) throw new ArgumentOutOfRangeException(nameof(age), "Age cannot be negative.");
    if (age > 150) throw new ArgumentOutOfRangeException(nameof(age), "Age is unrealistically large.");
}

// ExceptionDispatchInfo — re-throw from a different context preserving stack trace
using System.Runtime.ExceptionServices;

Exception? captured = null;
var t = new Thread(() =>
{
    try { throw new InvalidOperationException("From thread"); }
    catch (Exception ex) { captured = ex; }
});
t.Start(); t.Join();

if (captured is not null)
    ExceptionDispatchInfo.Capture(captured).Throw(); // re-throws with original stack trace
↥ back to top

Q. How do you create a custom exception in C#?

// Best practice custom exception — sealed, with standard constructors
[Serializable]
public sealed class OrderNotFoundException : Exception
{
    public int OrderId { get; }

    // Standard constructors (required for full compatibility)
    public OrderNotFoundException()
        : base("Order was not found.") { }

    public OrderNotFoundException(string message)
        : base(message) { }

    public OrderNotFoundException(string message, Exception innerException)
        : base(message, innerException) { }

    // Domain-specific constructor
    public OrderNotFoundException(int orderId)
        : base($"Order {orderId} was not found.")
    {
        OrderId = orderId;
    }

    public OrderNotFoundException(int orderId, Exception innerException)
        : base($"Order {orderId} was not found.", innerException)
    {
        OrderId = orderId;
    }
}

// Exception hierarchy for a domain
public abstract class DomainException(string message, Exception? inner = null)
    : Exception(message, inner);

public sealed class InsufficientInventoryException(string sku, int requested, int available)
    : DomainException($"Not enough stock for '{sku}': requested {requested}, available {available}")
{
    public string Sku       { get; } = sku;
    public int Requested    { get; } = requested;
    public int Available    { get; } = available;
}

// Usage
try
{
    throw new InsufficientInventoryException("LAPTOP-001", requested: 5, available: 2);
}
catch (InsufficientInventoryException ex)
{
    Console.WriteLine(ex.Message);   // Not enough stock for 'LAPTOP-001': requested 5, available 2
    Console.WriteLine(ex.Sku);       // LAPTOP-001
    Console.WriteLine(ex.Requested); // 5
    Console.WriteLine(ex.Available); // 2
}
catch (DomainException ex)
{
    Console.WriteLine($"Domain error: {ex.Message}");
}
↥ back to top

Q. How do you handle multiple exceptions in a single catch block in C#?

// 1. Multiple catch blocks — most specific first
try
{
    ProcessData();
}
catch (ArgumentNullException ex)
{
    Console.WriteLine($"Null argument: {ex.ParamName}");
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine($"Out of range: {ex.ParamName} = {ex.ActualValue}");
}
catch (ArgumentException ex) // catches both above if not already caught
{
    Console.WriteLine($"Bad argument: {ex.Message}");
}
catch (IOException ex)
{
    Console.WriteLine($"I/O error: {ex.Message}");
}

// 2. Multi-catch (C# 6+) — handle multiple types with the same logic
try
{
    ProcessData();
}
catch (FormatException ex) when (true) // pattern: always matches
{
    Console.WriteLine(ex.Message);
}
// Actually the idiomatic multi-type catch:
catch (Exception ex) when (ex is FormatException or OverflowException or InvalidCastException)
{
    Console.WriteLine($"Conversion error: {ex.Message}");
}

// 3. Exception filter with when — handle subset of one type
try
{
    using var client = new HttpClient();
    string result = await client.GetStringAsync("https://api.example.com");
}
catch (HttpRequestException ex) when ((int?)ex.StatusCode >= 500)
{
    Console.WriteLine("Server error — retry later");
}
catch (HttpRequestException ex) when ((int?)ex.StatusCode is 400 or 404)
{
    Console.WriteLine("Client error — check the request");
}

// 4. Catch-all (use sparingly)
try { ProcessData(); }
catch (Exception ex)
{
    Console.WriteLine($"Unhandled: {ex.GetType().Name}{ex.Message}");
    // log and possibly rethrow
    throw;
}

void ProcessData() => throw new FormatException("Bad data format");
↥ back to top

Q. How do you log exceptions in C#?

// 1. Microsoft.Extensions.Logging (recommended — built into ASP.NET Core)
public class OrderService(ILogger<OrderService> logger)
{
    public async Task<Order?> GetOrderAsync(int id, CancellationToken ct = default)
    {
        try
        {
            return await _repository.GetByIdAsync(id, ct);
        }
        catch (OperationCanceledException)
        {
            logger.LogWarning("GetOrder {OrderId} was cancelled", id);
            throw;
        }
        catch (Exception ex)
        {
            // LogError overload with exception automatically captures type + stack trace
            logger.LogError(ex, "Failed to retrieve order {OrderId}", id);
            return null;
        }
    }
}

// 2. Serilog (popular structured logging library)
// dotnet add package Serilog.Sinks.Console Serilog.Sinks.File
using Serilog;

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
    .CreateLogger();

try
{
    throw new InvalidOperationException("Something went wrong");
}
catch (Exception ex)
{
    Log.Error(ex, "Processing failed for {Context}", "OrderService");
}
finally
{
    await Log.CloseAndFlushAsync();
}

// 3. Global exception handlers (ASP.NET Core)
// In Program.cs:
// app.UseExceptionHandler(errorApp => errorApp.Run(async context => { ... }));

// 4. AppDomain / TaskScheduler unhandled handlers (console/worker apps)
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
{
    var ex = e.ExceptionObject as Exception;
    Console.Error.WriteLine($"Fatal: {ex?.Message}");
    // log to file, flush sinks, etc.
};

TaskScheduler.UnobservedTaskException += (_, e) =>
{
    Console.Error.WriteLine($"Unobserved task exception: {e.Exception.Message}");
    e.SetObserved(); // prevent crash
};
↥ back to top

Q. How do you use the using statement to handle exceptions in C#?

The using statement ensures IDisposable.Dispose() is called even if an exception is thrown — equivalent to a try/finally block.

// 1. Classic using block
using (var stream = new FileStream("data.bin", FileMode.Open))
using (var reader = new BinaryReader(stream))
{
    int value = reader.ReadInt32();
    Console.WriteLine(value);
} // Dispose called here, even if ReadInt32 throws

// 2. Using declaration (C# 8+) — disposed at end of enclosing scope
await using var writer = new StreamWriter("output.txt");
await writer.WriteLineAsync("Hello");
// writer.Dispose() called when scope exits (exception or normal)

// 3. Using + try/catch — handle exception AND ensure disposal
StreamReader? reader2 = null;
try
{
    reader2 = new StreamReader("data.txt");
    string content = await reader2.ReadToEndAsync();
    ProcessContent(content);
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"File not found: {ex.FileName}");
}
finally
{
    reader2?.Dispose(); // manual disposal if not using 'using'
}

// Preferred: using + catch via nesting
try
{
    await using var fs = File.OpenRead("data.txt");
    // process...
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"Missing: {ex.Message}");
}

// 4. IAsyncDisposable — await using
public class AsyncResource : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        await Task.Delay(10); // flush async
        Console.WriteLine("AsyncResource disposed");
    }
}

await using var res = new AsyncResource();
// ... use res
// DisposeAsync called at scope end

void ProcessContent(string s) { }
↥ back to top

Q. What is the difference between checked and unchecked exceptions in C#?

Note: C# does not have checked/unchecked exceptions in the Java sense (forced throws declarations). Instead, checked/unchecked in C# refer to arithmetic overflow behaviour.

int max = int.MaxValue; // 2,147,483,647

// checked — throws OverflowException on arithmetic overflow
try
{
    checked
    {
        int result = max + 1; // throws OverflowException
        Console.WriteLine(result);
    }
}
catch (OverflowException ex)
{
    Console.WriteLine($"Overflow caught: {ex.Message}");
}

// checked expression
int safe = checked(max + 1); // also throws OverflowException

// unchecked — wraps around silently (default behaviour)
unchecked
{
    int wrapped = max + 1;
    Console.WriteLine(wrapped); // -2,147,483,648 (wraps around)
}

int wrapped2 = unchecked(max + 1); // expression form

// Default is unchecked for performance
int x = int.MaxValue + 1; // silently wraps to int.MinValue — no exception

// Enable checked globally via project setting:
// <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> in .csproj

// For floating point — overflow produces Infinity, not an exception
double bigDouble = double.MaxValue * 2;
Console.WriteLine(bigDouble);          // Infinity (no exception)
Console.WriteLine(double.IsInfinity(bigDouble)); // true
↥ back to top

Q. How do you handle exceptions in asynchronous methods and in methods that return a Task?

// 1. async/await — exceptions propagate naturally via await
async Task<string> LoadDataAsync(string url, CancellationToken ct = default)
{
    try
    {
        using var client = new HttpClient();
        return await client.GetStringAsync(url, ct);
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"HTTP error {ex.StatusCode}: {ex.Message}");
        return string.Empty;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("Request cancelled");
        return string.Empty;
    }
}

// Caller catches like synchronous code
try
{
    string data = await LoadDataAsync("https://api.example.com");
}
catch (Exception ex)
{
    Console.WriteLine($"Outer catch: {ex.Message}");
}

// 2. Task.WhenAll — AggregateException wraps all failures
Task[] tasks =
[
    Task.Run(() => throw new Exception("T1 failed")),
    Task.Run(() => throw new Exception("T2 failed")),
    Task.Run(() => Console.WriteLine("T3 OK")),
];

try
{
    await Task.WhenAll(tasks); // await re-throws first exception
}
catch (Exception ex)
{
    Console.WriteLine($"First error: {ex.Message}");
    // Inspect all faulted tasks
    foreach (var t in tasks.Where(t => t.IsFaulted))
        Console.WriteLine($"  {t.Exception!.InnerException!.Message}");
}

// 3. Task returning method — exception stored in Task, thrown on await
Task<int> ComputeAsync()
{
    return Task.Run(() =>
    {
        if (true) throw new InvalidOperationException("Compute failed");
        return 42;
    });
}

var task = ComputeAsync(); // exception not thrown yet — stored in task
try { int r = await task; } // exception thrown here
catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); }

// 4. Fire-and-forget — must handle internally (no await = no propagation)
_ = Task.Run(async () =>
{
    try { await SomeBackgroundWorkAsync(); }
    catch (Exception ex) { Console.WriteLine($"Background error: {ex.Message}"); }
});

async Task SomeBackgroundWorkAsync() => await Task.Delay(100);
↥ back to top

Q. What is the AggregateException class and when is it used?

AggregateException wraps one or more exceptions that occur during parallel or multi-task operations. It is thrown by Task.WaitAll, Task.WhenAll (when not awaited), and Parallel.For / Parallel.ForEach.

// 1. Task.WhenAll — use await to get individual exceptions via faulted tasks
Task[] tasks = [
    Task.Run(() => throw new ArgumentException("Bad arg")),
    Task.Run(() => throw new IOException("Disk error")),
    Task.Run(() => Console.WriteLine("OK")),
];

try
{
    await Task.WhenAll(tasks);
}
catch // await re-throws first exception — check all tasks for the rest
{
    var allErrors = tasks
        .Where(t => t.IsFaulted)
        .SelectMany(t => t.Exception!.InnerExceptions)
        .ToList();

    foreach (var ex in allErrors)
        Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
}

// 2. Task.WaitAll (synchronous) — throws AggregateException directly
try
{
    Task.WaitAll(tasks);
}
catch (AggregateException ae)
{
    ae.Handle(ex =>
    {
        if (ex is IOException ioEx)
        {
            Console.WriteLine($"I/O handled: {ioEx.Message}");
            return true; // handled
        }
        return false; // rethrow unhandled
    });
}

// 3. Flatten — collapse nested AggregateExceptions
try { Task.WaitAll(tasks); }
catch (AggregateException ae)
{
    foreach (var inner in ae.Flatten().InnerExceptions)
        Console.WriteLine($"  {inner.GetType().Name}: {inner.Message}");
}

// 4. Parallel.For — wraps iteration exceptions in AggregateException
try
{
    Parallel.For(0, 5, i =>
    {
        if (i == 2) throw new Exception($"Error at {i}");
    });
}
catch (AggregateException ae)
{
    foreach (var ex in ae.Flatten().InnerExceptions)
        Console.WriteLine(ex.Message);
}
↥ back to top

Q. Will the finally block execute if an exception has not occurred? What is the C# syntax to catch any possible exception?

// finally ALWAYS runs — whether or not an exception occurred
// Exceptions: StackOverflowException, process kill, Environment.FailFast

// Case 1: No exception — finally still runs
try
{
    Console.WriteLine("try: no exception");
}
catch (Exception ex)
{
    Console.WriteLine($"catch: {ex.Message}"); // NOT reached
}
finally
{
    Console.WriteLine("finally: always runs"); // … runs
}

// Case 2: Exception handled — finally runs after catch
try
{
    throw new InvalidOperationException("oops");
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"catch: {ex.Message}");
}
finally
{
    Console.WriteLine("finally: runs after catch"); // … runs
}

// Case 3: Exception NOT caught — finally still runs, then exception propagates
try
{
    try { throw new Exception("unhandled"); }
    finally { Console.WriteLine("finally: runs before propagation"); } // … runs
}
catch (Exception ex) { Console.WriteLine($"outer catch: {ex.Message}"); }

// Case 4: return in try — finally still runs before the method returns
int Calculate()
{
    try    { return 42; }
    finally { Console.WriteLine("finally: runs even with return"); } // …
}
Console.WriteLine(Calculate()); // finally runs first, then returns 42

// Syntax to catch ANY exception:
try { /* ... */ }
catch (Exception ex) // catches all managed exceptions
{
    Console.WriteLine($"Caught: {ex.Message}");
}

// Or bare catch (legacy — don\'t use, doesn\'t capture exception reference):
// try { } catch { }
↥ back to top

Q. What is the difference between System.ApplicationException and System.SystemException?

  SystemException ApplicationException
Thrown by The CLR / .NET runtime User / application code
Examples NullReferenceException, IOException, OverflowException Custom exceptions (historically)
Modern guidance Do not catch directly — catch specific types Obsolete pattern — avoid
Recommended now Catch specific SystemException subtypes Derive directly from Exception
//  ApplicationException was originally meant as the base for app exceptions
// — this guidance was ABANDONED in .NET 2.0 — the pattern is now discouraged

//  Old pattern (avoid)
// public class MyException : ApplicationException { }

// … Modern pattern — derive directly from Exception
public sealed class OrderNotFoundException(int orderId)
    : Exception($"Order {orderId} not found.")
{
    public int OrderId { get; } = orderId;
}

// SystemException examples (thrown by CLR):
try
{
    string? s = null;
    _ = s!.Length;                   // NullReferenceException : SystemException
}
catch (NullReferenceException ex)    // specific subtype — preferred
{
    Console.WriteLine(ex.Message);
}

// Catching SystemException directly is an anti-pattern — too broad
// catch (SystemException ex) { }  catches way too much

// Bottom line:
// - Catching 'Exception' is the correct catch-all
// - Catching 'systemException' or 'ApplicationException' directly — avoid
// - Derive custom exceptions from 'Exception' directly
↥ back to top

Q. What is the difference between StackOverflowException and OutOfMemoryException?

  StackOverflowException OutOfMemoryException
Cause Call stack exhausted (infinite/deep recursion) Heap exhausted — no memory for allocation
Recoverable Not catchable (terminates process in .NET) Sometimes catchable but rarely recoverable
Common trigger Infinite recursion, very deep call chains Large allocations, memory leaks, huge arrays
Prevention Add base cases, use iteration, Span<T> Pool objects, use ArrayPool, reduce allocations
// StackOverflowException — infinite recursion
// int Factorial(int n) => n == 0 ? 1 : n * Factorial(n); // BUG: missing base case
// ’ StackOverflowException — process terminates, cannot be caught!

// Correct: proper base case
int Factorial(int n) => n <= 1 ? 1 : n * Factorial(n - 1); // …

// Better for deep recursion: iterative or explicit stack
int FactorialIterative(int n)
{
    int result = 1;
    for (int i = 2; i <= n; i++) result *= i;
    return result;
}

// OutOfMemoryException — allocation failure
try
{
    // Allocating 10 GB array — likely to fail
    var huge = new byte[10L * 1024 * 1024 * 1024];
}
catch (OutOfMemoryException ex)
{
    Console.WriteLine($"OOM: {ex.Message}");
    // Rarely recoverable — GC.Collect() + trim memory pools then retry
}

// Preventing OOM:
// Use ArrayPool<T> for large temporary buffers
byte[] buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(1024 * 1024);
try { /* use buffer */ }
finally { System.Buffers.ArrayPool<byte>.Shared.Return(buffer); }

// Stream large files instead of loading all into memory
await foreach (string line in File.ReadLinesAsync("huge.csv"))
    Console.WriteLine(line); // never loads full file
↥ back to top

Q. Name different types of errors that can occur during program execution.

Error type When Examples Detectable at
Syntax error Build time Missing ;, mismatched {} Compile time
Semantic error Build time Wrong types, undefined variable Compile time
Runtime exception Execution NullReferenceException, DivideByZeroException Runtime
Logic error Execution Wrong algorithm, off-by-one Testing/runtime
Stack overflow Execution Infinite recursion Runtime (fatal)
OutOfMemory Execution Huge allocation, memory leak Runtime
I/O error Execution File not found, network down Runtime
Concurrency error Execution Race conditions, deadlocks Runtime (intermittent)
Configuration error Startup Missing appsettings, bad connstr Runtime
// Runtime exception — NullReferenceException
string? s = null;
try { _ = s!.Length; }
catch (NullReferenceException ex) { Console.WriteLine($"Runtime: {ex.Message}"); }

// Logic error — no exception, wrong result
int Average(int[] nums) => nums.Sum(); // BUG: forgot to divide by nums.Length
Console.WriteLine(Average([1, 2, 3])); // 6 instead of 2 — logic error

// Arithmetic error
try { int x = 10 / 0; }
catch (DivideByZeroException ex) { Console.WriteLine($"DivideByZero: {ex.Message}"); }

// Type conversion error
try { int x = int.Parse("abc"); }
catch (FormatException ex) { Console.WriteLine($"Format: {ex.Message}"); }

// Index out of range
try { var arr = new int[3]; _ = arr[5]; }
catch (IndexOutOfRangeException ex) { Console.WriteLine($"Index: {ex.Message}"); }

// Null argument
try { string.IsNullOrEmpty(null!); } // safe here, but:
try { ArgumentNullException.ThrowIfNull(null, "param"); }
catch (ArgumentNullException ex) { Console.WriteLine($"NullArg: {ex.Message}"); }

// I/O error
try { File.ReadAllText("missing.txt"); }
catch (FileNotFoundException ex) { Console.WriteLine($"File: {ex.Message}"); }
↥ back to top

Q. Explain the difference between the finally block and the Finalize method.

  finally block Finalize / destructor
Type Exception-handling construct Object lifecycle method (GC)
When runs End of try/catch block (deterministic) When GC collects object (non-deterministic)
Called by Developer code flow Garbage Collector
Purpose Cleanup after a try block Release unmanaged resources as a safety net
Control Full developer control Non-deterministic timing
Preferred alternative Inherent to try structure IDisposable.Dispose + using
// finally — deterministic cleanup, runs at end of try/catch
void ReadFile(string path)
{
    StreamReader? reader = null;
    try
    {
        reader = new StreamReader(path);
        Console.WriteLine(reader.ReadToEnd());
    }
    catch (IOException ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
    finally
    {
        reader?.Dispose(); // always runs
        Console.WriteLine("finally: reader disposed");
    }
}

// Finalize / destructor — GC safety net for unmanaged resources
public class UnmanagedWrapper
{
    private IntPtr _handle;

    public UnmanagedWrapper() => _handle = AllocateResource();

    // Finalizer — called by GC if Dispose was not called
    ~UnmanagedWrapper()
    {
        // Safety net only — do NOT rely on timing
        FreeResource(_handle);
        Console.WriteLine("Finalizer ran");
    }

    // … Implement IDisposable for deterministic cleanup
    public void Dispose()
    {
        FreeResource(_handle);
        GC.SuppressFinalize(this); // tell GC: no need to finalize
        Console.WriteLine("Dispose called");
    }

    private static IntPtr AllocateResource() => new IntPtr(1);
    private static void FreeResource(IntPtr h) { }
}

// … Always prefer using/Dispose over relying on finalizer
using var wrapper = new UnmanagedWrapper(); // Dispose called deterministically
↥ back to top

Q. Can multiple catch blocks execute for a single exception in C#? What are the most commonly used exception types in .NET?

No. Only one catch block executes per exception — the first matching block. Subsequent catch blocks are skipped.

// Only the FIRST matching catch block runs
try
{
    throw new ArgumentNullException("param");
}
catch (ArgumentNullException ex)
{
    Console.WriteLine($"1st: {ex.GetType().Name}"); // … This runs
}
catch (ArgumentException ex)
{
    Console.WriteLine($"2nd: {ex.GetType().Name}"); //  NEVER reached
}
catch (Exception ex)
{
    Console.WriteLine($"3rd: {ex.GetType().Name}"); //  NEVER reached
}

// Correct ordering: most specific ’ most general
try { /* ... */ }
catch (FileNotFoundException ex) { /* most specific */ }
catch (IOException ex)           { /* less specific */ }
catch (Exception ex)             { /* catch-all last */ }

Most commonly used exception types in .NET:

Exception Cause
NullReferenceException Accessing member of null reference
ArgumentNullException null passed where not allowed
ArgumentException Invalid method argument
ArgumentOutOfRangeException Argument outside valid range
InvalidOperationException Object in wrong state for operation
NotSupportedException Operation not supported
NotImplementedException Method not yet implemented
IndexOutOfRangeException Array index out of bounds
KeyNotFoundException Dictionary key not found
FormatException String format is invalid (e.g., int.Parse)
OverflowException Arithmetic overflow in checked context
DivideByZeroException Division by zero
StackOverflowException Call stack exhausted
OutOfMemoryException Heap exhausted
IOException I/O operation failure
FileNotFoundException File not found
UnauthorizedAccessException No permission for operation
TimeoutException Operation timed out
TaskCanceledException Task was cancelled
OperationCanceledException Async operation was cancelled
HttpRequestException HTTP request failure
SqlException SQL Server error
AggregateException Multiple Task/Parallel exceptions
↥ back to top

Q. What are the different ways to handle errors in C#?

// 1. try/catch/finally — standard structured handling
try { ProcessOrder(order); }
catch (OrderNotFoundException ex) { logger.LogWarning(ex, "Order not found"); }
catch (Exception ex) { logger.LogError(ex, "Unexpected error"); throw; }
finally { /* cleanup */ }

// 2. Exception filters — when clause
try { await CallExternalApiAsync(); }
catch (HttpRequestException ex) when ((int?)ex.StatusCode >= 500)
    { Console.WriteLine("Server error — retry"); }

// 3. Result pattern — no exceptions for expected failures (functional style)
public readonly record struct Result<T>(T? Value, string? Error, bool IsSuccess)
{
    public static Result<T> Ok(T value)    => new(value, null, true);
    public static Result<T> Fail(string e) => new(default, e, false);
}

Result<Order> result = TryGetOrder(id);
if (result.IsSuccess) Process(result.Value!);
else Console.WriteLine(result.Error);

// 4. Nullable return / null-coalescing — for optional data
Order? order = await _repo.FindAsync(id);
Order resolved = order ?? Order.Default;

// 5. TryXxx pattern — like int.TryParse
if (int.TryParse(input, out int value))
    Console.WriteLine(value);
else
    Console.WriteLine("Invalid number");

// 6. ArgumentException helpers (.NET 6+)
void Process(string name, IEnumerable<int> items, int count)
{
    ArgumentNullException.ThrowIfNull(name);
    ArgumentException.ThrowIfNullOrWhiteSpace(name);
    ArgumentNullException.ThrowIfNull(items);
    ArgumentOutOfRangeException.ThrowIfNegative(count);
    ArgumentOutOfRangeException.ThrowIfGreaterThan(count, 1000);
}

// 7. Global handlers — last-resort logging
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
    Console.Error.WriteLine($"Fatal: {(e.ExceptionObject as Exception)?.Message}");

TaskScheduler.UnobservedTaskException += (_, e) =>
{
    Console.Error.WriteLine($"Unobserved: {e.Exception.Message}");
    e.SetObserved();
};

// 8. Polly — resilience policies (retry, circuit breaker, timeout)
// dotnet add package Polly
// var pipeline = new ResiliencePipelineBuilder()
//     .AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 3 })
//     .AddTimeout(TimeSpan.FromSeconds(10))
//     .Build();
// await pipeline.ExecuteAsync(async ct => await CallApiAsync(ct));

Result<Order> TryGetOrder(int id) =>
    id > 0 ? Result<Order>.Ok(new Order()) : Result<Order>.Fail("Invalid id");
void Process(Order o) { }
record Order { public static Order Default { get; } = new(); }
↥ back to top

Q. What are exception filters (when clause) in C# and how are they used?

Exception filters (C# 6+) allow you to conditionally catch an exception only when a boolean expression evaluates to true. The when keyword attaches a filter to a catch block. If the filter is false, the exception is not caught and continues to propagate — without unwinding the stack, which preserves the original call stack for debugging.

// ” 1. Basic exception filter ”———————————————————————————————————
try
{
    int result = int.Parse(Console.ReadLine() ?? "");
    Console.WriteLine(100 / result);
}
catch (FormatException ex) when (ex.Message.Contains("Input string"))
{
    Console.WriteLine("Please enter a valid integer.");
}
catch (DivideByZeroException) when (DateTime.Now.DayOfWeek != DayOfWeek.Sunday)
{
    Console.WriteLine("Division by zero on a weekday.");
}

// ” 2. Filter on HttpStatusCode ”—————————————————————————————————
static async Task FetchDataAsync(string url)
{
    try
    {
        using var client = new System.Net.Http.HttpClient();
        var response = await client.GetAsync(url);
        response.EnsureSuccessStatusCode();
    }
    catch (System.Net.Http.HttpRequestException ex)
        when ((int?)ex.StatusCode == 404)
    {
        Console.WriteLine($"Resource not found: {url}");
    }
    catch (System.Net.Http.HttpRequestException ex)
        when ((int?)ex.StatusCode >= 500)
    {
        Console.WriteLine($"Server error ({ex.StatusCode}): {url}");
    }
}

// ” 3. Logging filter (side-effect without catching) ”————————————
static bool Log(Exception ex)
{
    Console.Error.WriteLine($"[LOG] {ex.GetType().Name}: {ex.Message}");
    return false;   //  never catch — just log and re-throw
}

try
{
    throw new InvalidOperationException("Something went wrong");
}
catch (Exception ex) when (Log(ex))   // Log is called, returns false ’ not caught
{
    // Never reached
}
// The exception propagates with the original stack intact

// ” 4. Multiple filters on the same exception type ”———————————————
static void ProcessOrder(int orderId)
{
    try
    {
        if (orderId <= 0)
            throw new ArgumentOutOfRangeException(nameof(orderId), orderId, "Must be > 0");
        if (orderId > 1_000_000)
            throw new ArgumentOutOfRangeException(nameof(orderId), orderId, "Must be <= 1 000 000");
    }
    catch (ArgumentOutOfRangeException ex) when (ex.ActualValue is int v && v <= 0)
    {
        Console.WriteLine("Order ID must be positive.");
    }
    catch (ArgumentOutOfRangeException ex) when (ex.ActualValue is int v && v > 1_000_000)
    {
        Console.WriteLine("Order ID too large.");
    }
}

when vs regular catch:

Aspect catch (T ex) catch (T ex) when (condition)
Stack unwound Yes Only if condition is true
Condition None Boolean expression
Re-throw fidelity Needs throw; Stack preserved if not caught
Multiple blocks for same type Compile error … Allowed
↥ back to top

Q. What are the common built-in exception types in C# and when are they thrown?

.NET defines a rich hierarchy of exception types under System.Exception. Understanding when each is thrown helps write targeted catch blocks.

// ” Exception hierarchy (simplified) ”———————————————————————————
// System.Exception
// ”” System.SystemException          (runtime errors)
// ”   ”” ArgumentException
// ”   ”   ”” ArgumentNullException
// ”   ”   ””” ArgumentOutOfRangeException
// ”   ”” InvalidOperationException
// ”   ”” NullReferenceException
// ”   ”” IndexOutOfRangeException
// ”   ”” InvalidCastException
// ”   ”” OverflowException
// ”   ”” DivideByZeroException
// ”   ”” StackOverflowException      (non-catchable)
// ”   ”” OutOfMemoryException
// ”   ”” NotImplementedException
// ”   ”” NotSupportedException
// ”   ”” TimeoutException
// ”   ”” OperationCanceledException
// ”   ”   ””” TaskCanceledException
// ”   ””” IOException
// ”       ”” FileNotFoundException
// ”       ”” DirectoryNotFoundException
// ”       ””” EndOfStreamException
// ””” System.ApplicationException    (app-level — rarely used directly)

// ” Demonstration of common exceptions ”——————————————————————————
// 1. ArgumentNullException
static void Greet(string name)
{
    ArgumentNullException.ThrowIfNull(name);         // .NET 6+ helper
    Console.WriteLine($"Hello, {name}!");
}

// 2. ArgumentOutOfRangeException
static string GetElement(string[] arr, int i)
{
    ArgumentOutOfRangeException.ThrowIfNegative(i);
    ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(i, arr.Length);
    return arr[i];
}

// 3. InvalidOperationException — wrong state
class EmailSender
{
    private bool _connected;
    public void Connect() => _connected = true;
    public void Send(string msg)
    {
        if (!_connected) throw new InvalidOperationException("Call Connect() first.");
        Console.WriteLine($"Sent: {msg}");
    }
}

// 4. NullReferenceException — accessing null member
string? s = null;
try { _ = s!.Length; }
catch (NullReferenceException) { Console.WriteLine("Null ref"); }

// 5. IndexOutOfRangeException
int[] nums = [1, 2, 3];
try { _ = nums[5]; }
catch (IndexOutOfRangeException ex) { Console.WriteLine(ex.Message); }

// 6. InvalidCastException
object obj = "hello";
try { int n = (int)obj; }
catch (InvalidCastException) { Console.WriteLine("Bad cast"); }

// 7. OverflowException (inside checked block)
try { int x = checked(int.MaxValue + 1); }
catch (OverflowException) { Console.WriteLine("Arithmetic overflow"); }

// 8. FileNotFoundException
try { string _ = File.ReadAllText(@"C:\missing.txt"); }
catch (FileNotFoundException ex) { Console.WriteLine($"File not found: {ex.FileName}"); }

// 9. OperationCanceledException / TaskCanceledException
using var cts = new CancellationTokenSource(millisecondsDelay: 10);
try
{
    await Task.Delay(1000, cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Task cancelled.");
}

Common exception types summary:

Exception Thrown when
ArgumentNullException null passed for non-nullable parameter
ArgumentOutOfRangeException Value outside allowed range
InvalidOperationException Object in wrong state for operation
NullReferenceException Dereferencing a null object
IndexOutOfRangeException Array/string index out of bounds
InvalidCastException Invalid type cast
OverflowException Arithmetic overflow in checked context
DivideByZeroException Integer division by zero
FileNotFoundException File path does not exist
NotImplementedException Method body not yet written
NotSupportedException Operation not supported by this type
TaskCanceledException CancellationToken cancelled a Task
AggregateException One or more async/parallel task failures
↥ back to top

Q. What are the best practices for exception handling in C#?

using System.IO;

// ” 1. Catch only what you can handle ”———————————————————————————
//  Swallowing all exceptions hides bugs
try { /* € */ }
catch (Exception) { }    // silent swallow — avoid

// … Catch the most specific type you can actually handle
try
{
    string data = File.ReadAllText("config.json");
}
catch (FileNotFoundException)
{
    // Handle missing config specifically
    Console.WriteLine("Config not found — using defaults.");
}

// ” 2. Use `finally` or `using` for resource cleanup ”————————————
// … using — preferred for IDisposable resources
await using var conn = new System.Data.SqlClient.SqlConnection("€");
await conn.OpenAsync();

// ” 3. Re-throw with `throw;` not `throw ex;` ”———————————————————
static void LoadData()
{
    try { File.ReadAllText("data.csv"); }
    catch (IOException ex)
    {
        //  throw ex;  — resets the stack trace
        // … throw;     — preserves original stack trace
        Console.Error.WriteLine($"Failed to load: {ex.Message}");
        throw;
    }
}

// ” 4. Wrap low-level exceptions in meaningful ones ”—————————————
public class UserRepository
{
    public User FindById(int id)
    {
        try
        {
            // DB call €
            throw new System.Data.SqlClient.SqlException(); // simulated
        }
        catch (System.Data.SqlClient.SqlException ex)
        {
            throw new RepositoryException($"Failed to load user {id}.", ex); // preserves inner
        }
    }
}

// Custom exception
public class RepositoryException : Exception
{
    public RepositoryException(string message, Exception inner) : base(message, inner) { }
}
public record User(int Id, string Name);

// ” 5. Use exception filters for conditional handling ”———————————
try { /* network call */ }
catch (TimeoutException ex) when (ex.Message.Contains("read"))
{
    Console.WriteLine("Read timeout — retry.");
}

// ” 6. Validate early — avoid exceptions as control flow ”————————
//  Using exceptions as flow control
static int ParseAgeException(string s)
{
    try { return int.Parse(s); }
    catch (FormatException) { return -1; }
}

// … Use TryParse / guard clauses
static int ParseAgeSafe(string s)
    => int.TryParse(s, out int age) ? age : -1;

// ” 7. Log exceptions with context ”——————————————————————————————
static void ProcessOrder(int orderId)
{
    try { /* € */ }
    catch (Exception ex)
    {
        // Log full exception (type, message, stack trace, inner)
        Console.Error.WriteLine($"[ERROR] ProcessOrder({orderId}): {ex}");
        throw;  // re-throw — don\'t hide the exception from callers
    }
}

// ” 8. Never catch StackOverflowException / ExecutionEngineException
// These are non-recoverable — the process must terminate.

// ” 9. Handle async exceptions properly ”—————————————————————————
static async Task<string> DownloadAsync(string url)
{
    using var client = new System.Net.Http.HttpClient();
    try
    {
        return await client.GetStringAsync(url);
    }
    catch (System.Net.Http.HttpRequestException ex)
    {
        Console.Error.WriteLine($"HTTP error: {ex.StatusCode}{ex.Message}");
        return string.Empty;
    }
}

// ” 10. Global unhandled exception handlers ”—————————————————————
// In Program.cs / top-level setup:
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
    Console.Error.WriteLine($"Unhandled: {e.ExceptionObject}");

TaskScheduler.UnobservedTaskException += (_, e) =>
{
    Console.Error.WriteLine($"Unobserved task: {e.Exception.Message}");
    e.SetObserved();
};

Best practice checklist:

Practice Reason
Catch specific exception types Avoids hiding unexpected errors
Never swallow exceptions silently Bugs become invisible
Use throw; not throw ex; Preserves original stack trace
Wrap with meaningful exception Adds domain context
Use finally/using for cleanup Resources released even on failure
Validate inputs early Prevents exceptions as flow control
Log full exception object Stack trace + inner exception captured
Don't catch non-recoverable exceptions StackOverflowException, OutOfMemoryException
↥ back to top

# 10. EVENTS AND DELEGATES


Q. What are delegates in C# and why are they used?

A delegate is a type-safe function pointer — a reference to a method with a specific signature. It allows methods to be passed as parameters, stored as variables, and invoked dynamically.

// Declare a delegate type
delegate int MathOp(int a, int b);

// Methods matching the signature
int Add(int a, int b) => a + b;
int Multiply(int a, int b) => a * b;

// Assign and invoke
MathOp op = Add;
Console.WriteLine(op(3, 4));  // 7

op = Multiply;
Console.WriteLine(op(3, 4));  // 12

// Pass delegate as parameter
void ApplyOp(int x, int y, MathOp operation)
    => Console.WriteLine($"Result: {operation(x, y)}");

ApplyOp(5, 6, Add);       // Result: 11
ApplyOp(5, 6, Multiply);  // Result: 30

// Lambda as delegate
MathOp subtract = (a, b) => a - b;
Console.WriteLine(subtract(10, 3)); // 7

// Built-in generic delegates (prefer over custom)
Func<int, int, int> divide = (a, b) => a / b;
Action<string> print = msg => Console.WriteLine(msg);
Predicate<int> isEven = n => n % 2 == 0;

Console.WriteLine(divide(10, 2));   // 5
print("Hello delegates");
Console.WriteLine(isEven(4));       // True
↥ back to top

Q. What are Events? What is the difference between a delegate and an event?

An event is a mechanism for a class to notify subscribers when something happens. It is built on top of a delegate but restricted so only the declaring class can invoke it.

  Delegate Event
Invocation Anyone can invoke Only the declaring class
Assignment = allowed externally Only += / -= externally
Purpose General function reference Publisher-subscriber notifications
Null check Caller's responsibility Raised via ?.Invoke pattern
// Delegate — anyone can invoke (no encapsulation)
public delegate void AlertHandler(string message);
public AlertHandler OnAlert;  // public field delegate — not recommended

// Event — only the declaring class can invoke
public class Button
{
    // event keyword wraps the delegate with access restrictions
    public event EventHandler<ButtonClickedEventArgs>? Clicked;

    public void Click()
    {
        // Only Button can raise the event
        Clicked?.Invoke(this, new ButtonClickedEventArgs("Left"));
    }
}

public class ButtonClickedEventArgs(string button) : EventArgs
{
    public string Button { get; } = button;
}

// Subscribe
var btn = new Button();
btn.Clicked += (sender, e) => Console.WriteLine($"Button clicked: {e.Button}");
btn.Click(); // Button clicked: Left

// btn.Clicked?.Invoke(...)  //  compile error — external code cannot invoke event
// btn.Clicked = null;       //  compile error — cannot assign externally
↥ back to top

Q. How do you declare and raise an event? What is the purpose of the EventHandler delegate? How do you subscribe and unsubscribe?

// EventHandler<TEventArgs> — standard delegate for events:
//   void Handler(object? sender, TEventArgs e)

public class OrderProcessor
{
    // 1. Declare event using EventHandler<T> (standard pattern)
    public event EventHandler<OrderEventArgs>? OrderPlaced;
    public event EventHandler<OrderEventArgs>? OrderFailed;
    public event EventHandler? ProcessingCompleted; // no custom args

    // 2. Raise event — protected virtual method (allows derived class override)
    protected virtual void OnOrderPlaced(OrderEventArgs e)
        => OrderPlaced?.Invoke(this, e);

    protected virtual void OnOrderFailed(OrderEventArgs e)
        => OrderFailed?.Invoke(this, e);

    public async Task ProcessAsync(Order order)
    {
        try
        {
            await Task.Delay(100); // simulate work
            OnOrderPlaced(new OrderEventArgs(order.Id, "Placed successfully"));
        }
        catch (Exception ex)
        {
            OnOrderFailed(new OrderEventArgs(order.Id, ex.Message));
        }
        finally
        {
            ProcessingCompleted?.Invoke(this, EventArgs.Empty);
        }
    }
}

public class OrderEventArgs(int orderId, string message) : EventArgs
{
    public int OrderId  { get; } = orderId;
    public string Message { get; } = message;
}

// 3. Subscribe (+= ) and unsubscribe (-=)
var processor = new OrderProcessor();

void OnPlaced(object? sender, OrderEventArgs e)
    => Console.WriteLine($"Order {e.OrderId}: {e.Message}");

void OnCompleted(object? sender, EventArgs e)
    => Console.WriteLine("Processing completed");

processor.OrderPlaced          += OnPlaced;      // subscribe
processor.ProcessingCompleted  += OnCompleted;

await processor.ProcessAsync(new Order(1));

processor.OrderPlaced          -= OnPlaced;      // unsubscribe
processor.ProcessingCompleted  -= OnCompleted;

// Lambda subscription (keep reference to unsubscribe later)
EventHandler<OrderEventArgs>? failedHandler = null;
failedHandler = (_, e) => Console.WriteLine($"Failed: {e.Message}");
processor.OrderFailed += failedHandler;
processor.OrderFailed -= failedHandler; // unsubscribe using reference

record Order(int Id);
↥ back to top

Q. What is a multicast delegate (combinable delegate) in C#?

A multicast delegate holds references to multiple methods in an invocation list. Each += adds a method; -= removes it. All methods are invoked in order when the delegate is called.

delegate void Notify(string message);

void Logger(string msg)  => Console.WriteLine($"[Log] {msg}");
void Emailer(string msg) => Console.WriteLine($"[Email] {msg}");
void Sms(string msg)     => Console.WriteLine($"[SMS] {msg}");

// Combine delegates
Notify notify = Logger;
notify += Emailer;
notify += Sms;

notify("Order shipped"); // all three called in order
// [Log] Order shipped
// [Email] Order shipped
// [SMS] Order shipped

// Inspect invocation list
foreach (var d in notify.GetInvocationList())
    Console.WriteLine(d.Method.Name); // Logger, Emailer, Sms

// Remove a handler
notify -= Emailer;
notify("Order delivered");
// [Log] Order delivered
// [SMS] Order delivered

// Return value — only LAST invoked delegate\'s return value is returned
delegate int Transform(int x);
Transform chain = x => x + 1;
chain += x => x * 2;
chain += x => x - 3;
int result = chain(5); // (5+1)=6, (5*2)=10, (5-3)=2 ’ only last: 2
Console.WriteLine(result); // 2

// Combine with Delegate.Combine
Notify a = Logger;
Notify b = Sms;
Notify combined = (Notify)Delegate.Combine(a, b)!;
combined("Test"); // Logger + Sms
↥ back to top

Q. How do you use anonymous methods with delegates in C#?

// Anonymous method (C# 2.0 syntax — mostly replaced by lambdas)
Func<int, int, int> add = delegate(int a, int b) { return a + b; };
Console.WriteLine(add(3, 4)); // 7

// Parameterless anonymous method
Action greet = delegate { Console.WriteLine("Hello!"); };
greet();

// With event subscription
button.Clicked += delegate(object? sender, EventArgs e)
{
    Console.WriteLine("Button clicked via anonymous method");
};

// Captures outer variable (closure)
int multiplier = 3;
Func<int, int> multiply = delegate(int x) { return x * multiplier; };
Console.WriteLine(multiply(5)); // 15
multiplier = 10;
Console.WriteLine(multiply(5)); // 50 — captures reference, not value!

// Modern equivalent — lambda (preferred)
Func<int, int, int> addLambda = (a, b) => a + b;
Action greetLambda = () => Console.WriteLine("Hello via lambda!");

// Anonymous method vs lambda comparison
Func<int, bool> isPositiveAnon   = delegate(int x) { return x > 0; };
Func<int, bool> isPositiveLambda = x => x > 0; //  preferred

Button button = new();
record Button { public event EventHandler? Clicked; }
↥ back to top

Q. What are the advantages of using events and delegates? What is the difference between Event and Method?

Advantages:

Feature Benefit
Loose coupling Publisher doesn't know about subscribers
Multiple subscribers Multicast — many handlers for one event
Type safety Delegate signature enforced at compile time
Extensibility Add/remove handlers without changing publisher
Callback pattern Pass methods as arguments (strategy pattern)
// Without delegates — tight coupling
class OrderService
{
    private readonly EmailService _email = new();
    private readonly SmsService _sms = new();

    public void PlaceOrder(int id)
    {
        _email.Send($"Order {id} placed"); // tightly coupled
        _sms.Send($"Order {id} placed");
    }
}

// With delegates/events — loose coupling
class OrderServiceDecoupled
{
    public event EventHandler<int>? OrderPlaced;

    public void PlaceOrder(int id)
        => OrderPlaced?.Invoke(this, id); // doesn\'t know about Email/SMS
}

// Subscribers register independently
var svc = new OrderServiceDecoupled();
svc.OrderPlaced += (_, id) => new EmailService().Send($"Order {id}");
svc.OrderPlaced += (_, id) => new SmsService().Send($"Order {id}");

// Event vs Method
// Method — direct call, caller knows callee
void SendEmail(string msg) => Console.WriteLine($"Email: {msg}");
SendEmail("Direct call"); // caller must know method exists

// Event — indirect notification, publisher doesn\'t know subscribers
svc.OrderPlaced += (_, id) => Console.WriteLine($"Notification: order {id}");
svc.PlaceOrder(42); // publisher just fires — doesn\'t know who listens

class EmailService { public void Send(string m) => Console.WriteLine($"Email: {m}"); }
class SmsService   { public void Send(string m) => Console.WriteLine($"SMS: {m}"); }
↥ back to top

Q. How do you use lambda expressions with delegates in C#? What is the difference between lambdas and delegates?

// Lambda IS a delegate — it satisfies any compatible delegate type
Func<int, int, int> sum      = (a, b) => a + b;
Action<string>      log      = msg  => Console.WriteLine(msg);
Predicate<string>   notEmpty = s   => !string.IsNullOrEmpty(s);

// Expression lambda (single expression, no return keyword)
Func<int, int> square = x => x * x;

// Statement lambda (block body)
Func<int, int> absoluteValue = x =>
{
    if (x < 0) return -x;
    return x;
};

// Custom delegate type
delegate bool Validator<T>(T value);
Validator<string> lengthOk = s => s.Length <= 100;
Console.WriteLine(lengthOk("hello")); // True

// Differences — lambda vs delegate
// Delegate: named type declaration
delegate int BinaryOp(int a, int b);
BinaryOp multiply = (a, b) => a * b;

// Lambda: anonymous inline expression — syntactic sugar over delegates
Func<int, int, int> multiply2 = (a, b) => a * b; // same as above

// Key differences:
// 1. Delegates can be non-generic; lambdas always need a target delegate type
// 2. Lambdas can be expression trees (Expression<Func<...>>); anonymous methods cannot
// 3. Lambdas are shorter and more readable

// Expression tree — lambda captured as data (used by EF Core, etc.)
using System.Linq.Expressions;
Expression<Func<int, bool>> expr = x => x > 5;
Console.WriteLine(expr);                  // x => (x > 5)
Console.WriteLine(expr.Compile()(10));    // True — compile and invoke

// Lambdas passed to LINQ
int[] numbers = [1, 2, 3, 4, 5, 6];
var evens = numbers.Where(n => n % 2 == 0).Select(n => n * n).ToList();
Console.WriteLine(string.Join(", ", evens)); // 4, 16, 36
↥ back to top

Q. What is the difference between Action, Func, and Predicate delegates in C#?

Delegate Signature Returns Use when
Action Action<T1, T2, €> void Side-effect operations (print, log, update)
Func Func<T1, T2, €, TResult> TResult Transformations, computations
Predicate<T> Predicate<T> bool Tests a condition — equivalent to Func<T, bool>
// Action — no return value
Action<string, int> repeat = (msg, n) =>
{
    for (int i = 0; i < n; i++) Console.WriteLine(msg);
};
repeat("Hello", 3);

Action<Exception> logError = ex => Console.Error.WriteLine(ex.Message);
logError(new Exception("Oops"));

// Func — returns a value (last type param is return type)
Func<string, int>          length   = s => s.Length;
Func<int, int, int>        add      = (a, b) => a + b;
Func<string, string, bool> contains = (s, sub) => s.Contains(sub);

Console.WriteLine(length("dotnet")); // 6
Console.WriteLine(add(3, 7));        // 10
Console.WriteLine(contains("hello world", "world")); // True

// Predicate<T> — equivalent to Func<T, bool>
Predicate<string> isLong      = s => s.Length > 10;
Predicate<int>    isPositive  = n => n > 0;

Console.WriteLine(isLong("Hello World!")); // True
Console.WriteLine(isPositive(-1));         // False

// List.FindAll uses Predicate<T>
var numbers = new List<int> { -3, -1, 0, 2, 5, 8 };
List<int> positives = numbers.FindAll(isPositive);
Console.WriteLine(string.Join(", ", positives)); // 2, 5, 8

// Func<string,string> vs custom delegate
// Func<string,string> — generic, no custom type needed
Func<string, string> toUpper = s => s.ToUpper();

// Custom delegate — allows named type with documentation
delegate string StringTransform(string input);
StringTransform transform = s => s.Trim().ToLower();

// Both work the same way — Func is preferred for brevity
Console.WriteLine(toUpper("hello")); // HELLO
Console.WriteLine(transform("  World  ")); // world
↥ back to top

Q. How do you implement custom event accessors in C#?

Custom event accessors (add / remove) give explicit control over subscription — useful for thread-safety, filtering, or backing stores.

using System.Collections.Concurrent;

public class EventBus
{
    // Custom backing store — thread-safe dictionary of handlers
    private readonly ConcurrentDictionary<string, EventHandler> _handlers = new();

    // Custom event accessor
    public event EventHandler MessageReceived
    {
        add    => _handlers.AddOrUpdate("msg", value, (_, existing) => existing + value);
        remove => _handlers.AddOrUpdate("msg", null!, (_, existing) => existing - value);
    }

    public void Publish(string message)
    {
        if (_handlers.TryGetValue("msg", out var handler))
            handler?.Invoke(this, EventArgs.Empty);
    }
}

// Thread-safe event with lock (classic pattern)
public class SafeButton
{
    private readonly object _lock = new();
    private EventHandler? _clicked;

    public event EventHandler Clicked
    {
        add    { lock (_lock) { _clicked += value; } }
        remove { lock (_lock) { _clicked -= value; } }
    }

    // Raise safely
    protected virtual void OnClicked()
    {
        EventHandler? handler;
        lock (_lock) { handler = _clicked; } // copy under lock
        handler?.Invoke(this, EventArgs.Empty); // invoke outside lock
    }

    public void Click() => OnClicked();
}

// Usage
var btn = new SafeButton();
btn.Clicked += (_, _) => Console.WriteLine("Safe click handler 1");
btn.Clicked += (_, _) => Console.WriteLine("Safe click handler 2");
btn.Click();
// Safe click handler 1
// Safe click handler 2
↥ back to top

Q. How do you handle events in a derived class in C#?

public class Shape
{
    // Declare event with protected virtual raise method
    public event EventHandler<ShapeEventArgs>? Drawn;

    protected virtual void OnDrawn(ShapeEventArgs e)
        => Drawn?.Invoke(this, e);

    public virtual void Draw()
    {
        Console.WriteLine("Drawing Shape");
        OnDrawn(new ShapeEventArgs("Shape"));
    }
}

public class Circle : Shape
{
    // Override the raise method to add derived-class behaviour
    protected override void OnDrawn(ShapeEventArgs e)
    {
        Console.WriteLine("Circle-specific drawing logic");
        base.OnDrawn(e); // fire the event from base
    }

    public override void Draw()
    {
        Console.WriteLine("Drawing Circle");
        base.Draw();
    }
}

public class ShapeEventArgs(string shapeName) : EventArgs
{
    public string ShapeName { get; } = shapeName;
}

// Subscribe at base-class level — works for derived types too
Shape shape = new Circle();
shape.Drawn += (_, e) => Console.WriteLine($"Event: {e.ShapeName} was drawn");
shape.Draw();
// Drawing Circle
// Circle-specific drawing logic
// Drawing Shape
// Event: Shape was drawn
↥ back to top

Q. What are the best practices for using events and delegates in C#?

// 1. Always use EventHandler<TEventArgs> — avoids custom delegate declarations
public event EventHandler<OrderEventArgs>? OrderPlaced; // …
// public delegate void OrderHandler(Order o);          //  unnecessary

// 2. Null-conditional invoke — thread-safe raise
OrderPlaced?.Invoke(this, new OrderEventArgs(1, "Placed")); // …
// if (OrderPlaced != null) OrderPlaced(this, ...);          //  race condition

// 3. Protected virtual raise method — enables derived class extension
protected virtual void OnOrderPlaced(OrderEventArgs e)
    => OrderPlaced?.Invoke(this, e);

// 4. Always unsubscribe to prevent memory leaks
class Subscriber : IDisposable
{
    private readonly OrderProcessor _processor;

    public Subscriber(OrderProcessor processor)
    {
        _processor = processor;
        _processor.OrderPlaced += HandleOrderPlaced;
    }

    private void HandleOrderPlaced(object? sender, OrderEventArgs e)
        => Console.WriteLine($"Order {e.OrderId}: {e.Message}");

    public void Dispose()
        => _processor.OrderPlaced -= HandleOrderPlaced; // … unsubscribe
}

// 5. Prefer Func/Action over custom delegates for simple cases
Func<int, bool>  isValid = x => x > 0;   // … concise
Action<string>   log     = Console.WriteLine; // …

// 6. Use weak event patterns for long-lived publishers / short-lived subscribers
// (WeakEventManager in WPF; or manual WeakReference<T> in other scenarios)

// 7. EventArgs should be immutable (read-only properties)
public sealed class OrderEventArgs(int orderId, string message) : EventArgs
{
    public int OrderId    { get; } = orderId;   // … read-only
    public string Message { get; } = message;
}

// 8. Do NOT raise events in constructors — subscribers may not be attached yet
// 9. Do NOT throw exceptions in event handlers — use try/catch inside handler
// 10. Name events with verb or verb-noun pairs: OrderPlaced, DataReceived, ErrorOccurred

class OrderProcessor
{
    public event EventHandler<OrderEventArgs>? OrderPlaced;
    protected virtual void OnOrderPlaced(OrderEventArgs e)
        => OrderPlaced?.Invoke(this, e);
}
class OrderEventArgs(int orderId, string message) : EventArgs
{
    public int OrderId { get; } = orderId;
    public string Message { get; } = message;
}
↥ back to top

Q. What is the EventHandler<TEventArgs> pattern and how do you implement it correctly?

The standard .NET event pattern uses EventHandler<TEventArgs> where TEventArgs derives from EventArgs. This convention ensures compatibility with the .NET event system, tooling, and frameworks.

// ” 1. Custom EventArgs ”—————————————————————————————————————————
public class StockPriceChangedEventArgs : EventArgs
{
    public string Symbol    { get; }
    public decimal OldPrice { get; }
    public decimal NewPrice { get; }
    public decimal Change   => NewPrice - OldPrice;

    public StockPriceChangedEventArgs(string symbol, decimal oldPrice, decimal newPrice)
    {
        Symbol   = symbol;
        OldPrice = oldPrice;
        NewPrice = newPrice;
    }
}

// ” 2. Publisher — declares and raises the event ”————————————————
public class StockTicker
{
    private readonly Dictionary<string, decimal> _prices = [];

    // Standard declaration: EventHandler<TEventArgs>?, nullable for no subscribers
    public event EventHandler<StockPriceChangedEventArgs>? PriceChanged;

    // Protected virtual method — allows derived classes to override raising logic
    protected virtual void OnPriceChanged(StockPriceChangedEventArgs e)
        => PriceChanged?.Invoke(this, e);  // ?.Invoke is thread-safer than null check + invoke

    public void UpdatePrice(string symbol, decimal newPrice)
    {
        decimal oldPrice = _prices.GetValueOrDefault(symbol);
        _prices[symbol] = newPrice;

        if (oldPrice != newPrice)
            OnPriceChanged(new StockPriceChangedEventArgs(symbol, oldPrice, newPrice));
    }
}

// ” 3. Subscriber — attaches and detaches handlers ”——————————————
public class AlertSystem
{
    private readonly StockTicker _ticker;

    public AlertSystem(StockTicker ticker)
    {
        _ticker = ticker;
        _ticker.PriceChanged += OnPriceChanged;   // subscribe
    }

    private void OnPriceChanged(object? sender, StockPriceChangedEventArgs e)
    {
        if (Math.Abs(e.Change) > 5m)
            Console.WriteLine($"ALERT: {e.Symbol} moved {e.Change:+0.00;-0.00}{e.NewPrice}");
    }

    public void Detach() => _ticker.PriceChanged -= OnPriceChanged;  // unsubscribe
}

// ” 4. Usage ”———————————————————————————————————————————————————
var ticker = new StockTicker();
var alerts = new AlertSystem(ticker);

ticker.UpdatePrice("AAPL", 182.50m);   // no alert — first price
ticker.UpdatePrice("AAPL", 191.00m);   // ALERT: AAPL moved +8.50 ’ 191.00

alerts.Detach();                        // unsubscribe — prevent memory leaks

ticker.UpdatePrice("AAPL", 200.00m);   // no alert — subscriber detached

// ” 5. Thread-safe event invocation (local copy pattern) ”————————
public class SafePublisher
{
    public event EventHandler<EventArgs>? DataReady;

    protected virtual void OnDataReady()
    {
        // Copy reference before null check — prevents race condition
        // where another thread unsubscribes between the null check and invoke
        var handler = DataReady;
        handler?.Invoke(this, EventArgs.Empty);  // ?.Invoke already does this internally
    }
}

// ” 6. EventHandler without custom args (simple notification) ”———
public class Timer
{
    public event EventHandler? Tick;            // uses plain EventHandler
    protected virtual void OnTick()
        => Tick?.Invoke(this, EventArgs.Empty);
}

Standard event pattern rules:

Rule Reason
Derive EventArgs subclass for custom data Type-safe event data
Use EventHandler<TEventArgs>? (nullable) No NullReferenceException when no subscribers
Raise via protected virtual void OnXxx() Allows override in derived classes
Use ?.Invoke(this, e) not if (E != null) E(€) Thread-safer single evaluation
Always unsubscribe when done Prevents memory leaks (publisher holds reference to subscriber)
↥ back to top

Q. How do you prevent memory leaks caused by event subscriptions in C#?

When a subscriber registers with an event, the publisher holds a strong reference to the subscriber. If the publisher outlives the subscriber and the handler is never unsubscribed, the subscriber cannot be garbage collected — a classic event-caused memory leak.

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// PROBLEM — publisher outlives subscriber, subscriber leaks
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
static event EventHandler? StaticEvent;    // static ’ lives forever

class Subscriber
{
    public Subscriber() => StaticEvent += OnEvent;        // subscribe
    private void OnEvent(object? s, EventArgs e) => Console.WriteLine("Event fired");
    // No Dispose / unsubscribe ’ instance can never be GC'd while StaticEvent exists
}

var sub = new Subscriber();
sub = null!;                //  we think it\'s gone, but StaticEvent still holds a reference
GC.Collect();               // sub is NOT collected — memory leak

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// FIX 1 — Implement IDisposable and unsubscribe in Dispose
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
class ProperSubscriber : IDisposable
{
    private readonly StockTicker _ticker;
    private bool _disposed;

    public ProperSubscriber(StockTicker ticker)
    {
        _ticker = ticker;
        _ticker.PriceChanged += HandlePriceChanged;
    }

    private void HandlePriceChanged(object? sender, StockPriceChangedEventArgs e)
        => Console.WriteLine($"{e.Symbol}: {e.NewPrice}");

    public void Dispose()
    {
        if (_disposed) return;
        _ticker.PriceChanged -= HandlePriceChanged;   //  critical
        _disposed = true;
    }
}

// Usage with using ensures unsubscription
var ticker2 = new StockTicker();
using (var sub2 = new ProperSubscriber(ticker2))
{
    ticker2.UpdatePrice("GOOG", 175m);
}   // Dispose called ’ handler unregistered

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// FIX 2 — WeakEventManager / weak references (WPF helper)
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// In WPF: System.Windows.WeakEventManager<TEventSource, TEventArgs>
// Stores handler via WeakReference — subscriber can be GC'd even if subscribed

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// FIX 3 — Weak delegate wrapper (general-purpose)
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
class WeakEventHandler<TArgs> where TArgs : EventArgs
{
    private readonly WeakReference<EventHandler<TArgs>> _weakRef;

    public WeakEventHandler(EventHandler<TArgs> handler)
        => _weakRef = new WeakReference<EventHandler<TArgs>>(handler);

    public void Invoke(object? sender, TArgs args)
    {
        if (_weakRef.TryGetTarget(out var handler))
            handler(sender, args);
    }
}

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// FIX 4 — Lambda unsubscription (store reference)
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
EventHandler<StockPriceChangedEventArgs>? handler = null;
handler = (s, e) => Console.WriteLine(e.Symbol);
ticker2.PriceChanged += handler;
// ...
ticker2.PriceChanged -= handler;   // … works because handler variable is stored
// ticker2.PriceChanged -= (s, e) => Console.WriteLine(e.Symbol); //  new lambda — never matches

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// DIAGNOSTIC — check subscriber count via reflection (debug only)
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
static int GetSubscriberCount<T>(object publisher, string eventName) where T : Delegate
{
    var fi = publisher.GetType()
        .GetField(eventName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    var del = fi?.GetValue(publisher) as T;
    return del?.GetInvocationList().Length ?? 0;
}

Memory leak prevention checklist:

Technique When to use
Unsubscribe in Dispose Long-lived subscribers with IDisposable lifecycle
using statement Short-scoped subscribers
Store lambda reference When subscribing with a lambda expression
WeakReference wrapper When you cannot control subscriber lifetime
Avoid static events Static events hold references forever — use with care
↥ back to top

# 11. GARBAGE COLLECTION


Q. What is garbage collection in .NET and how does it work?

The Garbage Collector (GC) is an automatic memory manager in the .NET runtime that allocates and reclaims heap memory for managed objects, eliminating the need for manual free/delete calls.

How it works:

  1. Objects are allocated on the managed heap
  2. The GC periodically checks which objects are reachable (via roots: stack variables, static fields, GC handles)
  3. Unreachable objects are swept — their memory is reclaimed
  4. Surviving objects are compacted (defragmentation) and promoted to higher generations
flowchart TD
    A["Object Created\n(new keyword)"] --> B["Allocated in\nGeneration 0 (Gen 0)"]
    B --> C{"GC Collection\ntriggered?"}
    C -->|"Still reachable\n(has root)"| D["Survive ’ Promoted\nto Generation 1"]
    C -->|"Unreachable\n(no root)"| E["Memory Reclaimed\n(swept)"]
    D --> F{"Next GC\ncollection?"}
    F -->|"Still reachable"| G["Promote to\nGeneration 2\n(long-lived)"]
    F -->|"Unreachable"| E
    G --> H{"Large Object?\n≥ 85KB"}
    H -->|Yes| I["Large Object Heap\n(LOH) — Gen 2"]
    H -->|No| G

    style E fill:#e74c3c,color:#fff
    style G fill:#27ae60,color:#fff
    style I fill:#8e44ad,color:#fff
// Objects on managed heap — GC manages lifetime automatically
var list = new List<string>();          // heap allocation
list.Add("item");                       // more heap
list = null;                           // now unreachable ’ eligible for GC

// You never need to free managed objects — GC handles it
string s = new string('x', 1000);
s = null; // GC will reclaim when it runs next collection

// GC roots — objects reachable from these are NOT collected:
// - Local variables on the stack
// - Static fields
// - CPU registers
// - GC handles (pinned, strong, weak)

// Check GC memory info
var gcInfo = GC.GetGCMemoryInfo();
Console.WriteLine($"Heap size: {gcInfo.HeapSizeBytes:N0} bytes");
Console.WriteLine($"Fragmented: {gcInfo.FragmentedBytes:N0} bytes");
Console.WriteLine($"Total available: {gcInfo.TotalAvailableMemoryBytes:N0} bytes");

// GC notifications (server scenarios)
GC.RegisterForFullGCNotification(10, 10);
Console.WriteLine($"GC latency mode: {System.Runtime.GCSettings.LatencyMode}");
↥ back to top

Q. What are Generation 0, Generation 1, and Generation 2 in garbage collection? How does the GC know when to clean up?

The GC uses a generational model based on the observation that recently allocated objects tend to die young. Objects are promoted through generations as they survive collections.

Generation Contains GC frequency Notes
Gen 0 Newly allocated objects Very frequent (ms) Cheapest collection
Gen 1 Survived one Gen 0 Less frequent Buffer between Gen 0 and Gen 2
Gen 2 Long-lived objects Infrequent (seconds) Static fields, caches, singletons
LOH Objects ≥ 85,000 bytes With Gen 2 Large Object Heap — not compacted by default
// Objects start in Gen 0
var obj = new object();
Console.WriteLine(GC.GetGeneration(obj)); // 0

// Force promotion for demonstration
GC.Collect(0); // collect Gen 0
GC.WaitForPendingFinalizers();
Console.WriteLine(GC.GetGeneration(obj)); // 1 (survived ’ promoted)

GC.Collect(1);
GC.WaitForPendingFinalizers();
Console.WriteLine(GC.GetGeneration(obj)); // 2 (survived again)

// Large objects go straight to LOH (Gen 2)
var large = new byte[100_000]; // ≥ 85KB → LOH
Console.WriteLine(GC.GetGeneration(large)); // 2

// How GC knows when to collect:
// 1. Gen 0 budget exhausted (allocations exceed threshold)
// 2. System memory pressure
// 3. Explicit GC.Collect() call
// 4. AppDomain unload

// GC phases: Mark ’ Sweep ’ Compact
// Mark   — traverse from roots, mark all reachable objects
// Sweep  — identify unreachable objects
// Compact — slide live objects together, update references

// Gen 0 metrics
Console.WriteLine($"Gen 0 collections: {GC.CollectionCount(0)}");
Console.WriteLine($"Gen 1 collections: {GC.CollectionCount(1)}");
Console.WriteLine($"Gen 2 collections: {GC.CollectionCount(2)}");
Console.WriteLine($"Total memory: {GC.GetTotalMemory(false):N0} bytes");
↥ back to top

Q. Does the garbage collector clean up primitive types?

Primitive types (value types like int, double, bool, struct) allocated on the stack are NOT managed by the GC — they are freed automatically when the stack frame is popped.

Only reference types on the managed heap are managed by the GC.

// Value types on the stack — NO GC involvement
int x = 42;          // stack — freed when method returns
double d = 3.14;     // stack
bool flag = true;    // stack

// Value types inside a class — ON the heap (as part of the object)
class DataHolder
{
    public int Count;     // on heap because DataHolder is a reference type
    public double Value;  // on heap
}
var holder = new DataHolder(); // holder reference on stack, object on heap ’ GC manages

// Struct on the stack
struct Point { public int X, Y; }
Point p = new Point { X = 1, Y = 2 }; // entirely on stack — NO GC

// Struct on the heap (boxed or inside a class/array)
object boxed = p;          // boxing — copied to heap ’ GC manages
Point[] points = new Point[10]; // array on heap, but Point values inline in array

// Summary:
// Primitive/value types on stack ’ freed by stack unwind (no GC)
// Reference types on heap ’ GC manages
// Boxed value types on heap ’ GC manages
// Value types as fields of heap objects ’ GC manages (as part of parent object)

Console.WriteLine($"Is value type: {typeof(int).IsValueType}");    // True
Console.WriteLine($"Is value type: {typeof(string).IsValueType}"); // False
↥ back to top

Q. How does the garbage collector behave when a class has a destructor (finalizer)?

Objects with finalizers are placed on the finalization queue when they become unreachable. The GC must run the finalizer before reclaiming memory, which requires at least two GC cycles.

// Object WITH finalizer — two-cycle collection
public class ResourceWithFinalizer
{
    public ResourceWithFinalizer() => Console.WriteLine("Created");

    // Finalizer (destructor syntax) — called by GC on a dedicated finalizer thread
    ~ResourceWithFinalizer()
    {
        Console.WriteLine("Finalized by GC");
        // GC thread — do NOT use Thread.CurrentThread, allocate large objects, etc.
    }
}

// Cycle 1: object found unreachable ’ moved to finalization queue (NOT reclaimed yet)
// Cycle 2: finalizer thread runs, GC reclaims memory

// … Dispose pattern — call GC.SuppressFinalize to skip the second cycle
public class ManagedResource : IDisposable
{
    private bool _disposed;

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing)
        {
            // free managed resources
        }
        // free unmanaged resources
        _disposed = true;
    }

    ~ManagedResource()
    {
        Dispose(disposing: false); // safety net — unmanaged only
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this); // remove from finalization queue ’ single cycle
    }
}

// Always use 'using' to call Dispose deterministically
using var res = new ManagedResource();
// Dispose called here — GC.SuppressFinalize prevents finalizer run

// Impact on GC:
// Without Dispose: 2 GC cycles, finalizer thread overhead, delayed reclamation
// With Dispose + SuppressFinalize: 1 GC cycle, no finalizer overhead
↥ back to top

Q. Can the garbage collector reclaim unmanaged resources? Can you force garbage collection? Is it a good practice?

Unmanaged resources (file handles, sockets, native memory via Marshal.AllocHGlobal, COM objects) are NOT managed by the GC. They must be released explicitly via IDisposable / finalizers.

//  GC cannot free unmanaged resources — you must do it
var handle = System.Runtime.InteropServices.Marshal.AllocHGlobal(1024);
// ... use handle
System.Runtime.InteropServices.Marshal.FreeHGlobal(handle); // manual cleanup required

// … Wrap in SafeHandle or IDisposable for automatic cleanup
public class NativeBuffer : IDisposable
{
    private IntPtr _ptr;
    private bool _disposed;

    public NativeBuffer(int size)
        => _ptr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size);

    public void Dispose()
    {
        if (!_disposed)
        {
            System.Runtime.InteropServices.Marshal.FreeHGlobal(_ptr);
            _ptr = IntPtr.Zero;
            _disposed = true;
            GC.SuppressFinalize(this);
        }
    }

    ~NativeBuffer() => Dispose(); // safety net
}

using var buf = new NativeBuffer(1024);

// Forcing GC — GC.Collect()
GC.Collect();                         // collect all generations
GC.Collect(0);                        // collect Gen 0 only
GC.Collect(2, GCCollectionMode.Forced); // forced full collection
GC.WaitForPendingFinalizers();        // wait for finalizer thread to complete
GC.Collect();                         // collect finalizable objects

//  Is it good practice to force GC?
//  Almost never — reasons:
// - Promotes objects to higher generations unnecessarily (Gen 0 ’ Gen 1 ’ Gen 2)
// - Disrupts GC\'s self-tuning heuristics
// - Causes latency spikes (stop-the-world pause)
// - Rarely improves performance; often makes it worse

// … Acceptable rare cases:
// 1. After a known large allocation is no longer needed
// 2. In unit tests verifying finalizer behaviour
// 3. Before performance-sensitive benchmarks (baseline memory)
// 4. Out-of-process tooling / diagnostics

void ProcessLargeBatch()
{
    LoadLargeDataSet();
    GC.Collect(2, GCCollectionMode.Aggressive, blocking: true, compacting: true); // rare justified case
}

void LoadLargeDataSet() { }
↥ back to top

Q. How do you detect memory leaks in .NET applications?

// Common causes of managed memory leaks:
// 1. Event handlers not unsubscribed (most common)
// 2. Static fields holding object references
// 3. Caches with no eviction policy
// 4. Closures capturing large objects
// 5. Long-lived collections growing unbounded

// 1. Event leak — subscriber held alive by publisher\'s event
public class Publisher
{
    public event EventHandler? Updated;
}

public class Subscriber
{
    public Subscriber(Publisher pub)
        => pub.Updated += OnUpdated; // pub holds reference to 'this'

    private void OnUpdated(object? sender, EventArgs e) { }

    // Fix: implement IDisposable and unsubscribe
}

// 2. Detect with GC.GetTotalMemory
long before = GC.GetTotalMemory(forceFullCollection: true);
var list = new List<byte[]>();
for (int i = 0; i < 100; i++) list.Add(new byte[1024 * 1024]); // 100 MB
long after = GC.GetTotalMemory(false);
Console.WriteLine($"Leaked: {(after - before) / 1024 / 1024} MB");
list.Clear();

// 3. WeakReference — holds reference without preventing GC
var weakRef = new WeakReference<byte[]>(new byte[1024]);
GC.Collect();
if (weakRef.TryGetTarget(out var target))
    Console.WriteLine("Still alive");
else
    Console.WriteLine("Collected");

// 4. Tools for detecting leaks:
// - dotnet-counters: dotnet counters monitor --process-id <pid>
// - dotnet-dump:     dotnet dump collect --process-id <pid>
// - Visual Studio Diagnostic Tools ’ Memory Usage ’ Snapshots
// - JetBrains dotMemory, Redgate ANTS, PerfView

// 5. MemoryDiagnoser in BenchmarkDotNet
// [MemoryDiagnoser]
// public class MyBenchmark { ... }

// 6. ObjectPooling to reduce pressure
var pool = System.Buffers.ArrayPool<byte>.Shared;
byte[] rented = pool.Rent(1024);
try { /* use buffer */ }
finally { pool.Return(rented); } // returned to pool — no GC pressure

byte[] target2 = [];
↥ back to top

Q. What is a finalizer in C#? What is GC.SuppressFinalize and when should you use it?

// Finalizer — called by GC before reclaiming object memory
// Syntax: ~ClassName() { }
// - Runs on the dedicated finalizer thread
// - Non-deterministic timing
// - Do NOT call managed code that may have been collected
// - Only for unmanaged resource cleanup as a SAFETY NET

public class FileWrapper : IDisposable
{
    private IntPtr _fileHandle;
    private bool _disposed;

    public FileWrapper(string path)
        => _fileHandle = OpenFile(path); // OS handle

    // Finalizer — safety net if Dispose was not called
    ~FileWrapper()
    {
        Console.WriteLine("Finalizer: cleaning up (Dispose was not called!)");
        Dispose(disposing: false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        if (disposing)
        {
            // safe to access managed objects here
        }
        CloseFile(_fileHandle); // unmanaged cleanup always
        _fileHandle = IntPtr.Zero;
        _disposed = true;
    }

    // GC.SuppressFinalize — tells GC: "finalizer not needed, skip finalization queue"
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this); // … skip finalizer — memory reclaimed in ONE cycle
    }

    private static IntPtr OpenFile(string path) => new(1);
    private static void CloseFile(IntPtr h) { }
}

// Always call Dispose with 'using'
using var fw = new FileWrapper("data.bin");
// Dispose called ’ GC.SuppressFinalize ’ no finalizer overhead

// GC.ReRegisterForFinalize — re-register for finalization (rare use: resurrection pattern)
public class ResurrectableResource : IDisposable
{
    ~ResurrectableResource() => Console.WriteLine("Finalized");

    public void Reset()
    {
        GC.ReRegisterForFinalize(this); // will finalize again when unreachable
    }

    public void Dispose()
    {
        GC.SuppressFinalize(this);
        Console.WriteLine("Disposed");
    }
}

// GC.KeepAlive — prevents GC from collecting object before a certain point
void UseHandle(IntPtr handle)
{
    var resource = new FileWrapper("file");
    _ = handle; // use handle
    GC.KeepAlive(resource); // ensure resource is NOT collected before this point
}
↥ back to top

Q. What is GC.Collect vs GC.WaitForPendingFinalizers?

// GC.Collect — triggers a garbage collection
GC.Collect();         // collect all generations (0, 1, 2)
GC.Collect(0);        // collect Gen 0 only
GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true);
// GCCollectionMode: Default, Forced, Optimized, Aggressive (.NET 6+)

// GC.WaitForPendingFinalizers — blocks until finalizer thread completes all queued finalizers
GC.WaitForPendingFinalizers();

// Why use both together?
// After Collect() — unreachable finalizable objects are queued for finalization
// WaitForPendingFinalizers() — waits for finalizer thread to process that queue
// Second Collect() — reclaims the now-finalized objects

// Standard pattern when you MUST force GC (tests, benchmarks):
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect(); // reclaim objects that were waiting for finalization

// Example — verifying finalizer runs in tests
bool finalized = false;

void CreateObject()
{
    var obj = new FinalizableObj(() => finalized = true);
}

CreateObject();           // obj goes out of scope
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine($"Finalized: {finalized}"); // True

// GC.GetTotalMemory(forceFullCollection: true) — combines Collect + WaitForPendingFinalizers
long memory = GC.GetTotalMemory(forceFullCollection: true);
Console.WriteLine($"Memory after full GC: {memory:N0} bytes");

class FinalizableObj(Action onFinalize)
{
    ~FinalizableObj() => onFinalize();
}
↥ back to top

Q. What is a WeakReference in C#?

A WeakReference<T> holds a reference to an object without preventing it from being garbage collected. Useful for caches and observer patterns where you don't want to force objects to stay alive.

// WeakReference<T> — does NOT prevent GC collection
var data = new byte[1024 * 1024]; // 1 MB
var weak = new WeakReference<byte[]>(data);

data = null!; // remove strong reference
GC.Collect();

if (weak.TryGetTarget(out byte[]? target))
    Console.WriteLine($"Still alive: {target.Length} bytes");
else
    Console.WriteLine("Collected by GC");

// WeakReference cache pattern — auto-evicts entries under memory pressure
public class WeakCache<TKey, TValue> where TKey : notnull where TValue : class
{
    private readonly Dictionary<TKey, WeakReference<TValue>> _cache = new();

    public void Set(TKey key, TValue value)
        => _cache[key] = new WeakReference<TValue>(value);

    public TValue? Get(TKey key)
    {
        if (_cache.TryGetValue(key, out var wr) && wr.TryGetTarget(out var val))
            return val;
        _cache.Remove(key); // clean up dead entry
        return null;
    }
}

var cache = new WeakCache<int, string>();
cache.Set(1, "hello");

string? val = cache.Get(1);
Console.WriteLine(val ?? "not found"); // hello

// WeakReference (non-generic, legacy) — avoid, use WeakReference<T> instead
object obj = new { Name = "test" };
var legacyWeak = new WeakReference(obj);
Console.WriteLine(legacyWeak.IsAlive); // True
obj = null!;
GC.Collect();
Console.WriteLine(legacyWeak.IsAlive); // False
↥ back to top

Q. What is the Lazy<T> class? What is the difference between Lazy and Lazy<T>?

Lazy<T> defers creation of an expensive object until it is first accessed. It is thread-safe by default.

// Lazy<T> — deferred, thread-safe initialization
var lazyConfig = new Lazy<AppConfig>(() =>
{
    Console.WriteLine("Loading config..."); // only runs on first access
    return new AppConfig { Timeout = 30 };
});

// Value not yet created
Console.WriteLine(lazyConfig.IsValueCreated); // False

// First access — triggers initialization
AppConfig config = lazyConfig.Value;           // "Loading config..."
Console.WriteLine(lazyConfig.IsValueCreated); // True
Console.WriteLine(lazyConfig.Value.Timeout);  // 30 — second access, no re-init

// Thread safety modes
var lazy1 = new Lazy<ExpensiveObject>(LazyThreadSafetyMode.ExecutionAndPublication); // default — lock on init
var lazy2 = new Lazy<ExpensiveObject>(LazyThreadSafetyMode.PublicationOnly);         // race: one winner
var lazy3 = new Lazy<ExpensiveObject>(LazyThreadSafetyMode.None);                    // no thread safety

// Common pattern: lazy singleton in a class
public class DataService
{
    private static readonly Lazy<DataService> _instance
        = new(() => new DataService());

    public static DataService Instance => _instance.Value;

    private DataService() { }
    public void Query() => Console.WriteLine("Querying data");
}

DataService.Instance.Query();

// Lazy (non-generic) — does NOT exist as a public API
// 'Lazy' by itself is not a type — always use Lazy<T>
// The question likely refers to:
// € Lazy<T>  — built-in BCL class
// € Custom lazy patterns (lazy fields, lazy properties)

// Lazy property pattern (no Lazy<T> class)
private ExpensiveObject? _resource;
ExpensiveObject Resource => _resource ??= new ExpensiveObject();

record AppConfig { public int Timeout { get; init; } }
class ExpensiveObject { }
↥ back to top

Q. What is a MemoryCache in C#?

MemoryCache is an in-process, thread-safe cache provided by Microsoft.Extensions.Caching.Memory. It stores key-value pairs in memory with optional expiration, size limits, and eviction callbacks.

using Microsoft.Extensions.Caching.Memory;

// Create cache
var cache = new MemoryCache(new MemoryCacheOptions
{
    SizeLimit = 1024 // max entries (in size units you define)
});

// Set with absolute expiration
cache.Set("user:1", new User(1, "Alice"), new MemoryCacheEntryOptions
{
    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),
    Size = 1 // count this entry as 1 unit toward SizeLimit
});

// Set with sliding expiration (refreshed on each access)
cache.Set("session:abc", new SessionData(), new MemoryCacheEntryOptions
{
    SlidingExpiration = TimeSpan.FromMinutes(20),
    Size = 1
});

// Get
if (cache.TryGetValue("user:1", out User? user))
    Console.WriteLine($"From cache: {user?.Name}");

// GetOrCreate — atomic check-and-create
User cachedUser = cache.GetOrCreate("user:2", entry =>
{
    entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
    entry.Size = 1;
    return new User(2, "Bob"); // factory — called only on cache miss
})!;

// Async GetOrCreateAsync
User cachedUser2 = await cache.GetOrCreateAsync("user:3", async entry =>
{
    entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
    entry.Size = 1;
    return await LoadUserFromDbAsync(3);
}) ?? throw new Exception("Not found");

// Eviction callback
cache.Set("temp:key", "value", new MemoryCacheEntryOptions
{
    AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30),
    Size = 1
}.RegisterPostEvictionCallback((key, value, reason, state) =>
    Console.WriteLine($"Evicted '{key}': {reason}")));

// Remove manually
cache.Remove("user:1");

// In ASP.NET Core — register via DI
// services.AddMemoryCache();
// Then inject IMemoryCache into your service

static Task<User> LoadUserFromDbAsync(int id) => Task.FromResult(new User(id, $"User{id}"));
record User(int Id, string Name);
record SessionData;
↥ back to top

Q. What is a Mutex in C#?

A Mutex (mutual exclusion) is a synchronization primitive that restricts access to a resource to one thread at a time, and uniquely supports cross-process synchronization via a named mutex.

// 1. Local mutex — single-process synchronization
using var mutex = new Mutex();

Thread t1 = new(() =>
{
    mutex.WaitOne(); // acquire
    try   { Console.WriteLine("T1 in critical section"); Thread.Sleep(100); }
    finally { mutex.ReleaseMutex(); }
});

Thread t2 = new(() =>
{
    mutex.WaitOne();
    try   { Console.WriteLine("T2 in critical section"); }
    finally { mutex.ReleaseMutex(); }
});

t1.Start(); t2.Start(); t1.Join(); t2.Join();

// 2. Named mutex — cross-process (e.g., single-instance application)
const string MutexName = "Global\\MyApp_SingleInstance";

bool createdNew;
using var globalMutex = new Mutex(initiallyOwned: true, MutexName, out createdNew);

if (!createdNew)
{
    Console.WriteLine("Another instance is already running.");
    return;
}

try
{
    Console.WriteLine("Application running...");
    Thread.Sleep(5000); // simulate work
}
finally
{
    globalMutex.ReleaseMutex();
}

// 3. Mutex with timeout
using var timedMutex = new Mutex();
bool acquired = timedMutex.WaitOne(TimeSpan.FromSeconds(5));
if (acquired)
{
    try { Console.WriteLine("Acquired with timeout"); }
    finally { timedMutex.ReleaseMutex(); }
}
else
{
    Console.WriteLine("Timed out waiting for mutex");
}

// Note: for single-process scenarios prefer lock/Monitor or SemaphoreSlim
// Mutex is heavier — use only when cross-process sync is needed
↥ back to top

Q. What is Semaphore vs SemaphoreSlim in C#?

  Semaphore SemaphoreSlim
Cross-process … Named semaphores In-process only
Async support   WaitAsync()
Performance Heavier (OS kernel) Lighter (user-mode)
Use when Cross-process throttling In-process async throttling
// SemaphoreSlim — preferred for async in-process scenarios
var semaphore = new SemaphoreSlim(initialCount: 3, maxCount: 3); // allow 3 concurrent

var tasks = Enumerable.Range(1, 10).Select(async i =>
{
    await semaphore.WaitAsync(); // acquire slot (async — no thread blocking)
    try
    {
        Console.WriteLine($"Task {i} running (slots left: {semaphore.CurrentCount})");
        await Task.Delay(500); // simulate work
    }
    finally
    {
        semaphore.Release(); // release slot
        Console.WriteLine($"Task {i} done");
    }
});

await Task.WhenAll(tasks); // max 3 tasks run concurrently

// Named Semaphore — cross-process throttling
using var namedSemaphore = new Semaphore(initialCount: 2, maximumCount: 2, name: "Global\\MySemaphore");
bool entered = namedSemaphore.WaitOne(TimeSpan.FromSeconds(5));
if (entered)
{
    try { Console.WriteLine("Entered semaphore"); }
    finally { namedSemaphore.Release(); }
}

// Rate-limiting with SemaphoreSlim (throttle API calls)
var throttle = new SemaphoreSlim(5); // max 5 concurrent HTTP calls
async Task<string> FetchAsync(HttpClient client, string url)
{
    await throttle.WaitAsync();
    try   { return await client.GetStringAsync(url); }
    finally { throttle.Release(); }
}
↥ back to top

Q. What is a deadlock in C#?

A deadlock occurs when two or more threads each hold a resource that the other needs, causing all threads to wait indefinitely.

// Classic deadlock — two threads, two locks acquired in opposite order
object lock1 = new();
object lock2 = new();

Thread t1 = new(() =>
{
    lock (lock1)
    {
        Thread.Sleep(50); // give t2 time to acquire lock2
        lock (lock2) { Console.WriteLine("T1: acquired both locks"); }
    }
});

Thread t2 = new(() =>
{
    lock (lock2)
    {
        Thread.Sleep(50);
        lock (lock1) { Console.WriteLine("T2: acquired both locks"); }
    }
});

// t1.Start(); t2.Start();  DEADLOCK! Both threads wait forever

// Prevention 1: consistent lock ordering
Thread safe1 = new(() => { lock (lock1) { lock (lock2) { Console.WriteLine("safe1"); } } });
Thread safe2 = new(() => { lock (lock1) { lock (lock2) { Console.WriteLine("safe2"); } } });
safe1.Start(); safe2.Start(); safe1.Join(); safe2.Join();

// Prevention 2: use Monitor.TryEnter with timeout
Thread tryLock = new(() =>
{
    if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(1)))
    {
        try
        {
            if (Monitor.TryEnter(lock2, TimeSpan.FromSeconds(1)))
            {
                try { Console.WriteLine("Acquired both"); }
                finally { Monitor.Exit(lock2); }
            }
            else { Console.WriteLine("Could not acquire lock2 — backoff"); }
        }
        finally { Monitor.Exit(lock1); }
    }
});
tryLock.Start(); tryLock.Join();

// Prevention 3: prefer async/await + SemaphoreSlim over blocking locks
// Prevention 4: CancellationToken in async operations prevents indefinite waits
// Prevention 5: use higher-level concurrency primitives (Channel<T>, Dataflow)
↥ back to top

Q. What is the Interlocked class in C#?

Interlocked provides atomic operations on shared variables — thread-safe without locks, using CPU atomic instructions.

// Interlocked.Increment / Decrement — atomic ++ and --
int counter = 0;
var threads = Enumerable.Range(0, 10).Select(_ => new Thread(() =>
{
    for (int i = 0; i < 1000; i++)
        Interlocked.Increment(ref counter); // atomic — no race condition
})).ToList();

threads.ForEach(t => t.Start());
threads.ForEach(t => t.Join());
Console.WriteLine(counter); // always 10,000

// Without Interlocked: counter++ is NOT atomic (read-modify-write race)
// counter++ ’ IL: ldloc, ldc.i4.1, add, stloc — three non-atomic operations

// Interlocked.Add — atomic addition
long total = 0;
Interlocked.Add(ref total, 100);
Console.WriteLine(total); // 100

// Interlocked.Exchange — atomically set and return old value
int state = 0;
int oldState = Interlocked.Exchange(ref state, 1);
Console.WriteLine($"Old: {oldState}, New: {state}"); // Old: 0, New: 1

// Interlocked.CompareExchange — set if current value equals expected (CAS)
int value = 5;
int original = Interlocked.CompareExchange(ref value, newValue: 10, comparand: 5);
Console.WriteLine($"Original: {original}, Value: {value}"); // Original: 5, Value: 10

// CAS loop — lock-free update pattern
long sharedLong = 0;
void AddLockFree(long amount)
{
    long current, updated;
    do
    {
        current = Interlocked.Read(ref sharedLong);
        updated = current + amount;
    } while (Interlocked.CompareExchange(ref sharedLong, updated, current) != current);
}

// Interlocked.Read — atomic 64-bit read on 32-bit systems
long safeRead = Interlocked.Read(ref sharedLong);

// Interlocked.MemoryBarrier / MemoryBarrierProcessWide — memory fences
Interlocked.MemoryBarrier(); // full fence — prevents CPU/compiler reordering
↥ back to top

Q. What is the difference between Task and ValueTask?

  Task / Task<T> ValueTask / ValueTask<T>
Allocation Always heap allocated No allocation if synchronous
Caching Can be cached/reused Single await only
Overhead Higher for hot paths Lower for sync-fast paths
Use when General async work High-throughput, often-sync methods
// Task — standard async, always allocates
async Task<int> GetCountAsync()
{
    await Task.Delay(100); // genuinely async
    return 42;
}

// ValueTask — avoids allocation when result is immediately available
async ValueTask<int> GetCachedCountAsync()
{
    if (_cache.TryGetValue("count", out int cached))
        return cached; // synchronous fast path — NO Task allocation

    int value = await LoadFromDbAsync(); // async slow path
    _cache["count"] = value;
    return value;
}

// Using ValueTask
int count = await GetCachedCountAsync();

// Rules for ValueTask:
// … Await it exactly once
// … Don\'t store and await later (use AsTask() first)
// … Don\'t await from multiple consumers
// … Use when method frequently returns synchronously

// Converting ValueTask to Task when you need to share/store
ValueTask<int> vt = GetCachedCountAsync();
Task<int> task = vt.AsTask(); // convert — now safely multi-awaitable
int r1 = await task;
int r2 = await task; // … safe after AsTask()

// IValueTaskSource — advanced: reuse ValueTask with pool (avoid this unless profiling shows need)

Dictionary<string, int> _cache = new();
Task<int> LoadFromDbAsync() => Task.FromResult(100);
↥ back to top

Q. What is CancellationToken and CancellationTokenSource in C#?

// CancellationTokenSource — creates and controls cancellation
using var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

// Cancel after timeout
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

// Cancel after delay
cts.CancelAfter(TimeSpan.FromSeconds(5));

// Manual cancel
// cts.Cancel(); // triggers cancellation

// CancellationToken — passed to async methods to observe cancellation
async Task<string> FetchDataAsync(string url, CancellationToken ct = default)
{
    using var client = new HttpClient();
    // Pass token to async I/O — cancels automatically
    return await client.GetStringAsync(url, ct);
}

// Full usage example
using var source = new CancellationTokenSource();
CancellationToken ct = source.Token;

// Register a callback on cancellation
ct.Register(() => Console.WriteLine("Operation was cancelled"));

Task workTask = Task.Run(async () =>
{
    for (int i = 0; i < 100; i++)
    {
        ct.ThrowIfCancellationRequested(); // poll and throw OperationCanceledException
        await Task.Delay(100, ct);         // also respects cancellation
        Console.WriteLine($"Step {i}");
    }
}, ct);

await Task.Delay(350);
source.Cancel(); // cancel after ~350ms

try
{
    await workTask;
}
catch (OperationCanceledException)
{
    Console.WriteLine("Task was cancelled gracefully");
}

// Linked tokens — cancel when ANY source fires
using var userCts    = new CancellationTokenSource();
using var timeoutCts2 = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var linked     = CancellationTokenSource.CreateLinkedTokenSource(
    userCts.Token, timeoutCts2.Token);

await FetchDataAsync("https://example.com", linked.Token);
↥ back to top

Q. What is the difference between Task.WhenAll and Task.WhenAny?

  Task.WhenAll Task.WhenAny
Completes when ALL tasks complete FIRST task completes
Exception Waits for all; aggregates Returns immediately; others keep running
Use for Fan-out parallel work Timeout, race, first-success patterns
// Task.WhenAll — wait for all, collect all results
async Task WhenAllExample()
{
    Task<string> task1 = FetchAsync("https://api.example.com/users");
    Task<string> task2 = FetchAsync("https://api.example.com/orders");
    Task<string> task3 = FetchAsync("https://api.example.com/products");

    string[] results = await Task.WhenAll(task1, task2, task3); // parallel fetch
    Console.WriteLine($"Users: {results[0].Length} chars");
    Console.WriteLine($"Orders: {results[1].Length} chars");
}

// Task.WhenAny — first to complete wins
async Task WhenAnyExample()
{
    // Pattern 1: timeout
    using var cts = new CancellationTokenSource();
    Task<string> fetch   = FetchAsync("https://slow-api.example.com");
    Task<string> timeout = Task.Delay(TimeSpan.FromSeconds(5)).ContinueWith(_ => "timeout");

    Task<string> first = await Task.WhenAny(fetch, timeout);
    string result = await first;
    Console.WriteLine(result == "timeout" ? "Request timed out" : $"Got: {result.Length} chars");

    // Pattern 2: first successful result from multiple endpoints
    var endpoints = new[]
    {
        FetchAsync("https://api1.example.com/data"),
        FetchAsync("https://api2.example.com/data"),
        FetchAsync("https://api3.example.com/data"),
    };
    Task<string> winner = await Task.WhenAny(endpoints);
    Console.WriteLine($"Fastest result: {(await winner).Length} chars");
}

// WhenAll exception handling — see all failures
Task[] failingTasks =
[
    Task.Run(() => throw new Exception("Task 1")),
    Task.Run(() => throw new Exception("Task 2")),
];

try { await Task.WhenAll(failingTasks); }
catch
{
    foreach (var t in failingTasks.Where(t => t.IsFaulted))
        Console.WriteLine(t.Exception!.InnerException!.Message);
}

static Task<string> FetchAsync(string url) =>
    Task.FromResult($"data-from-{url}");
↥ back to top

Q. What is ConcurrentDictionary in C#?

ConcurrentDictionary<TKey, TValue> is a thread-safe dictionary in System.Collections.Concurrent that allows multiple threads to read and write concurrently without external locking.

using System.Collections.Concurrent;

var dict = new ConcurrentDictionary<string, int>(StringComparer.OrdinalIgnoreCase);

// Thread-safe add or update
dict["count"] = 0;

// TryAdd — adds only if key doesn\'t exist
bool added = dict.TryAdd("item1", 10);

// AddOrUpdate — atomic add-or-update
dict.AddOrUpdate(
    key: "count",
    addValue: 1,
    updateValueFactory: (_, current) => current + 1);

// GetOrAdd — atomic get-or-create
int value = dict.GetOrAdd("hits", key =>
{
    Console.WriteLine($"Creating default for {key}");
    return 0;
});

// GetOrAdd with factory object (avoid closure allocation)
int value2 = dict.GetOrAdd("hits", static (key, seed) => seed, addValueFactoryArgument: 42);

// Parallel increment
var counter = new ConcurrentDictionary<string, long>();
var tasks = Enumerable.Range(0, 100).Select(_ => Task.Run(() =>
{
    for (int i = 0; i < 1000; i++)
        counter.AddOrUpdate("total", 1L, (_, v) => v + 1L);
}));
await Task.WhenAll(tasks);
Console.WriteLine(counter["total"]); // always 100,000

// TryGetValue / TryRemove / TryUpdate
if (dict.TryGetValue("count", out int count))
    Console.WriteLine($"count = {count}");

dict.TryRemove("item1", out _);

// Snapshot iteration (safe but may not be perfectly consistent)
foreach (var (key, val) in dict)
    Console.WriteLine($"{key} = {val}");

// Keys / Values — snapshot copies
ICollection<string> keys = dict.Keys;
↥ back to top

Q. What is BlockingCollection in C#? What is the difference between ConcurrentBag and ConcurrentQueue?

BlockingCollection<T> provides bounded, blocking producer-consumer patterns. It wraps any IProducerConsumerCollection<T> (defaults to ConcurrentQueue<T>).

  ConcurrentQueue<T> ConcurrentBag<T>
Order FIFO Unordered
Best for Producer-consumer pipelines Work-stealing (same thread adds+removes)
Thread affinity None Optimized for thread-local access
// BlockingCollection — bounded producer-consumer queue
var collection = new BlockingCollection<int>(boundedCapacity: 100);

// Producer — blocks when collection is full
Task producer = Task.Run(() =>
{
    for (int i = 0; i < 20; i++)
    {
        collection.Add(i);                 // blocks if at capacity
        Console.WriteLine($"Produced: {i}");
    }
    collection.CompleteAdding();           // signal no more items
});

// Consumer — blocks when collection is empty
Task consumer = Task.Run(() =>
{
    foreach (int item in collection.GetConsumingEnumerable()) // blocks until item or completed
        Console.WriteLine($"Consumed: {item}");
});

await Task.WhenAll(producer, consumer);

// ConcurrentQueue — FIFO, producer-consumer pipeline
var queue = new ConcurrentQueue<string>();
queue.Enqueue("first");
queue.Enqueue("second");

if (queue.TryDequeue(out string? item)) Console.WriteLine(item); // first
if (queue.TryPeek(out string? next))    Console.WriteLine(next); // second

// ConcurrentBag — unordered, thread-local optimization
var bag = new ConcurrentBag<int>();
Parallel.For(0, 10, i => bag.Add(i)); // each thread adds to local bag

while (bag.TryTake(out int bagItem))
    Console.Write($"{bagItem} "); // unordered output
Console.WriteLine();

// ConcurrentStack — LIFO
var stack = new ConcurrentStack<int>();
stack.Push(1); stack.Push(2); stack.Push(3);
if (stack.TryPop(out int top)) Console.WriteLine(top); // 3
↥ back to top

Q. What is the difference between server GC and workstation GC? How do you configure GC with GCSettings?

  Workstation GC Server GC
Default for Desktop, single-process apps ASP.NET Core, server workloads
Threads 1 GC thread 1 GC thread per CPU core
Heap 1 heap 1 heap per CPU core
Throughput Lower Higher
Latency Lower pauses Higher pauses (more work per GC)
Memory Lower Higher
using System.Runtime;

// Check current mode
Console.WriteLine($"Server GC: {GCSettings.IsServerGC}");
Console.WriteLine($"Latency mode: {GCSettings.LatencyMode}");

// GCLatencyMode — balance throughput vs pause time
// Configure for interactive/low-latency scenario
GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
// Minimizes Gen 2 collections — good for UI, real-time

// Briefly suppress GC during critical section
GC.TryStartNoGCRegion(1024 * 1024 * 10); // request 10 MB no-GC region
try
{
    // Critical path — GC will not run here if memory is available
    PerformLatencySensitiveWork();
}
finally
{
    GC.EndNoGCRegion();
    GCSettings.LatencyMode = GCLatencyMode.Interactive; // restore
}

// Configure in runtimeconfig.json (preferred over code):
// {
//   "runtimeOptions": {
//     "configProperties": {
//       "System.GC.Server": true,
//       "System.GC.Concurrent": true,
//       "System.GC.HeapHardLimit": 1073741824
//     }
//   }
// }

// Or in .csproj:
// <ServerGarbageCollection>true</ServerGarbageCollection>
// <GarbageCollectionAdaptationMode>0</GarbageCollectionAdaptationMode>

// Optimize GC in .NET:
// 1. Reduce allocations — use stackalloc, Span<T>, ArrayPool<T>
// 2. Avoid boxing — use generics instead of object
// 3. Dispose IDisposable objects promptly (using statement)
// 4. Use object pooling for large, frequently-allocated objects
// 5. Prefer value types (struct) for small, short-lived data
// 6. Avoid large object heap (LOH) fragmentation — pool large arrays

void PerformLatencySensitiveWork() { }

↥ back to top

Q. How do you implement the IDisposable pattern correctly in C#?

IDisposable is used to release unmanaged resources (file handles, database connections, sockets, native memory) deterministically — without waiting for the garbage collector. The complete “dispose pattern” combines a public Dispose() method with a ~finalizer as a safety net.

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// 1. Simple IDisposable — no finalizer needed (wraps another IDisposable)
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
public class FileProcessor : IDisposable
{
    private StreamReader? _reader;
    private bool _disposed;

    public FileProcessor(string path)
        => _reader = new StreamReader(path);

    public string? ReadLine() => _reader?.ReadLine();

    public void Dispose()
    {
        if (_disposed) return;
        _reader?.Dispose();   // dispose managed resource
        _reader = null;
        _disposed = true;
    }
}

// Usage — always use `using` for IDisposable
using var processor = new FileProcessor("data.txt");
string? line = processor.ReadLine();

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// 2. Full Dispose Pattern — when you hold UNMANAGED resources directly
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
public class NativeResourceHolder : IDisposable
{
    // Managed resource (another IDisposable)
    private Stream? _stream;

    // Unmanaged resource (IntPtr, SafeHandle, etc.)
    private IntPtr _nativeHandle;

    private bool _disposed;

    public NativeResourceHolder(string path)
    {
        _stream       = File.OpenRead(path);
        _nativeHandle = AllocateNativeResource();   // hypothetical P/Invoke
    }

    // ” Public entry point ”———————————————————————————————————
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);   // no need for finalizer — Dispose already ran
    }

    // ” Core logic — called by both Dispose() and finalizer ”——
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            // Safe to access managed objects here (Dispose called)
            _stream?.Dispose();
            _stream = null;
        }

        // Always release unmanaged resources
        if (_nativeHandle != IntPtr.Zero)
        {
            FreeNativeResource(_nativeHandle);   // hypothetical P/Invoke
            _nativeHandle = IntPtr.Zero;
        }

        _disposed = true;
    }

    // ” Finalizer — safety net if caller forgot Dispose() ”————
    ~NativeResourceHolder() => Dispose(disposing: false);

    private static IntPtr AllocateNativeResource() => new IntPtr(1);   // placeholder
    private static void FreeNativeResource(IntPtr handle) { }           // placeholder
}

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// 3. Preferred modern approach — wrap unmanaged handle in SafeHandle
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
using Microsoft.Win32.SafeHandles;

public class SafeResourceHolder : IDisposable
{
    private SafeFileHandle? _handle;
    private Stream?         _stream;
    private bool            _disposed;

    public SafeResourceHolder(string path)
    {
        _handle = File.OpenHandle(path);
        _stream = new FileStream(_handle, FileAccess.Read);
    }

    public void Dispose()
    {
        if (_disposed) return;
        _stream?.Dispose();   // disposes both stream and handle
        _disposed = true;
        // No finalizer needed — SafeHandle has its own
    }
}

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// 4. IAsyncDisposable — for async cleanup (C# 8+)
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
public class AsyncDbConnection : IAsyncDisposable
{
    private bool _disposed;

    public async ValueTask DisposeAsync()
    {
        if (_disposed) return;
        await CloseConnectionAsync();   // async teardown
        _disposed = true;
    }

    private static Task CloseConnectionAsync() => Task.Delay(10);
}

await using var conn = new AsyncDbConnection();
// ... use conn ...
// DisposeAsync called automatically at end of scope

Dispose pattern summary:

Scenario Use
Wraps only other IDisposable Simple Dispose() — no finalizer
Holds unmanaged resource directly Full pattern with Dispose(bool) + finalizer
Unmanaged handle SafeHandle subclass — preferred over raw IntPtr
Async teardown required IAsyncDisposable + await using
Always call GC.SuppressFinalize After successful Dispose() to skip finalizer queue
↥ back to top

Q. What is the Large Object Heap (LOH) and how does it affect memory and GC performance?

The .NET GC splits the managed heap into the Small Object Heap (SOH) for objects < 85,000 bytes and the Large Object Heap (LOH) for objects ≥ 85,000 bytes. The LOH is treated differently and can cause memory pressure and fragmentation.

// ” 1. What goes to the LOH ”—————————————————————————————————————
// Any single managed object >= 85,000 bytes (default threshold)
// Most common: large arrays (byte[], int[], string with >40K chars)

byte[] small = new byte[84_999];  // SOH — Gen 0
byte[] large = new byte[85_000];  // LOH — collected only during Gen 2 GC

// ” 2. LOH is collected only with Gen 2 (Full GC) ”———————————————
// SOH: Gen 0 ’ Gen 1 ’ Gen 2 (short-lived objects collected quickly)
// LOH: always collected together with Gen 2 ’ more expensive, less frequent

// ” 3. LOH fragmentation ”————————————————————————————————————————
// LOH is NOT compacted by default (unlike SOH)
// Allocate and free many large arrays ’ holes appear ’ OutOfMemoryException
// even when total free memory is enough (fragmentation)
void DemonstrateFragmentation()
{
    var arrays = new List<byte[]>();
    for (int i = 0; i < 100; i++)
        arrays.Add(new byte[100_000]);   // 100 — 100KB = 10 MB on LOH

    // Release every other one
    for (int i = 0; i < arrays.Count; i += 2)
        arrays[i] = null!;

    GC.Collect();   // compacts SOH but NOT LOH by default — fragmented holes remain
}

// ” 4. Force LOH compaction (one-time, expensive) ”———————————————
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();   // compacts LOH this one time, then resets to NoCompaction

// ” 5. Best practice: ArrayPool<T> to avoid LOH allocations ”—————
using System.Buffers;

void ProcessData(int size)
{
    //  Allocates a new large array — goes to LOH, increases GC pressure
    // byte[] buffer = new byte[size];

    // … Rent from pool — reuses existing arrays, no LOH pressure
    byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
    try
    {
        // Use buffer (may be slightly larger than requested)
        Array.Clear(buffer, 0, size);
        Console.WriteLine($"Processing {buffer.Length} bytes");
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(buffer);   // return to pool — NOT freed
    }
}

ProcessData(200_000);   // large but no LOH allocation

// ” 6. Span<T> and Memory<T> — zero-copy, stack-friendly slices ”—
byte[] fullBuffer = new byte[1_000_000];

// Span<T> — stack-allocated slice reference (cannot be stored in heap fields)
Span<byte> slice = fullBuffer.AsSpan(0, 100);
slice.Fill(0xFF);

// Memory<T> — heap-compatible async-friendly slice
Memory<byte> memSlice = fullBuffer.AsMemory(100, 200);
await ProcessMemoryAsync(memSlice);

static async Task ProcessMemoryAsync(Memory<byte> mem)
{
    await Task.Yield();
    Console.WriteLine($"Processing {mem.Length} bytes asynchronously");
}

// ” 7. Monitor LOH size ”—————————————————————————————————————————
long lohSize = GC.GetGCMemoryInfo().GenerationInfo[3].SizeAfterBytes;
Console.WriteLine($"LOH size after GC: {lohSize / 1024:N0} KB");

LOH rules of thumb:

Rule Reason
Objects ≥ 85 KB go to LOH Default GC threshold
LOH collected only with Gen 2 GC More expensive, less frequent
LOH is NOT compacted by default Fragmentation risk
Use ArrayPool<T>.Shared.Rent() Reuse large arrays, avoid LOH pressure
Use Span<T> / Memory<T> Zero-copy slices, no allocation
Compact LOH only when needed GCLargeObjectHeapCompactionMode.CompactOnce
↥ back to top

Q. What are Span<T> and Memory<T> in C# and how do they reduce allocations?

Span<T> and Memory<T> are allocation-free slice types that let you work with contiguous regions of memory — whether from arrays, stack, or native memory — without copying.

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Span<T> — stack-only, synchronous, ultra-fast
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••

// ” 1. Slice an array without copying ”———————————————————————————
int[] numbers = [10, 20, 30, 40, 50, 60, 70];
Span<int> middle = numbers.AsSpan(2, 3);   // [30, 40, 50] — no copy
middle[1] = 99;                             // mutates the original array
Console.WriteLine(numbers[3]);              // 99

// ” 2. Parse substrings without allocating a new string ”—————————
ReadOnlySpan<char> date = "2026-06-01".AsSpan();
int year  = int.Parse(date[..4]);     // "2026"
int month = int.Parse(date[5..7]);    // "06"
int day   = int.Parse(date[8..]);     // "01"
Console.WriteLine(new DateTime(year, month, day)); // 01/06/2026

// ” 3. Stack-allocated Span (stackalloc) ”————————————————————————
// No heap allocation at all
Span<byte> stackBuffer = stackalloc byte[256];
stackBuffer.Fill(0);
Console.WriteLine(stackBuffer.Length);   // 256

// ” 4. String split without allocating substrings ”———————————————
static int CountCommas(ReadOnlySpan<char> text)
{
    int count = 0;
    foreach (var c in text)
        if (c == ',') count++;
    return count;
}
Console.WriteLine(CountCommas("a,b,c,d".AsSpan()));   // 3

// ” 5. Span across native memory (unsafe) ”———————————————————————
// unsafe {
//     byte* ptr = stackalloc byte[100];
//     Span<byte> native = new Span<byte>(ptr, 100);
// }

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Memory<T> — heap-compatible, works with async
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••

// ” 6. Memory<T> in async methods ”———————————————————————————————
byte[] buffer = new byte[4096];
Memory<byte> mem = buffer.AsMemory(0, 1024);

async Task ReadToMemoryAsync(Stream stream, Memory<byte> destination)
{
    int bytesRead = await stream.ReadAsync(destination);  // no copy — writes directly
    Console.WriteLine($"Read {bytesRead} bytes");
}

// ” 7. ReadOnlyMemory<T> for strings and read-only data ”—————————
ReadOnlyMemory<char> roMem = "Hello, World!".AsMemory(7, 5);  // "World"
Console.WriteLine(new string(roMem.Span));  // World

// ” 8. MemoryPool<T> for reusable large buffers ”—————————————————
using IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(minBufferSize: 4096);
Memory<byte> pooledMem = owner.Memory;
// use pooledMem...
// IMemoryOwner.Dispose() returns memory to pool automatically

// ” 9. Performance comparison ”———————————————————————————————————
// Traditional (allocates):       string sub = str.Substring(start, length);
// Span-based (zero alloc):       ReadOnlySpan<char> sub = str.AsSpan(start, length);

static bool StartsWithHttp(string url)
{
    ReadOnlySpan<char> span = url;
    return span.StartsWith("https://", StringComparison.OrdinalIgnoreCase)
        || span.StartsWith("http://", StringComparison.OrdinalIgnoreCase);
}

Span<T> vs Memory<T> vs string:

Feature Span<T> Memory<T> string / T[]
Stack-only … Yes No No
Works in async No … Yes … Yes
Slicing Zero-copy Zero-copy Allocates new object
Mutation … Yes … Yes string immutable
Works with stackalloc … Yes No No
GC pressure None (stack) Low (slice only) High (new object)
↥ back to top

# 12. LAMBDA EXPRESSIONS


Q. What is a lambda expression in C# and why is it used? How do you declare one?

A lambda expression is an anonymous inline function defined with the => (goes-to) operator. It creates a concise way to write delegate implementations, LINQ queries, callbacks, and event handlers without declaring a separate named method.

Syntax forms:

// Expression lambda — single expression, implicit return
(parameters) => expression

// Statement lambda — block body with explicit return
(parameters) => { statements; return value; }

// Zero parameters
() => Console.WriteLine("Hello")

// One parameter — parentheses optional
x => x * x

// Multiple parameters
(x, y) => x + y

// Explicitly typed parameters
(int x, string y) => $"{y} = {x}"

Why use lambdas:

// Without lambda — verbose
static bool IsEvenMethod(int n) => n % 2 == 0;
var evens1 = new List<int> { 1, 2, 3, 4, 5 }.FindAll(IsEvenMethod);

// With lambda — concise, inline
var evens2 = new List<int> { 1, 2, 3, 4, 5 }.FindAll(n => n % 2 == 0);

// Delegate types satisfied by lambdas
Func<int, int>      square  = x => x * x;
Action<string>      print   = msg => Console.WriteLine(msg);
Predicate<string>   isShort = s => s.Length < 5;
Comparison<int>     desc    = (a, b) => b.CompareTo(a);

Console.WriteLine(square(6));    // 36
print("Lambda!");
Console.WriteLine(isShort("Hi")); // True

// C# 14: natural type for lambdas (compiler infers delegate type)
var add = (int a, int b) => a + b; // inferred as Func<int,int,int>
Console.WriteLine(add(3, 4));  // 7

// Static lambda (.NET 5+) — prevents accidental capture
var multiplier = 10;
Func<int, int> staticLambda = static x => x * 2; // … cannot capture 'multiplier'
↥ back to top

Q. What is the difference between expression lambdas, statement lambdas, and anonymous methods?

// 1. Expression lambda — single expression, implicit return
Func<int, int> square = x => x * x;
Func<int, int, int> sum = (a, b) => a + b;
Console.WriteLine(square(5));  // 25
Console.WriteLine(sum(3, 4));  // 7

// 2. Statement lambda — block body, explicit return
Func<int, string> classify = n =>
{
    if (n < 0) return "negative";
    if (n == 0) return "zero";
    return "positive";
};
Console.WriteLine(classify(-5)); // negative

// 3. Anonymous method (C# 2.0 — pre-lambda) — verbose, limited
Func<int, int> squareAnon = delegate(int x) { return x * x; };
Console.WriteLine(squareAnon(5)); // 25

// Key differences:
// Expression lambda  — concise, can be used as Expression<Func<...>> (EF Core, etc.)
// Statement lambda   — more complex logic, cannot be Expression<T>
// Anonymous method   — legacy, no expression tree support, no implicit typing

// Expression tree — only expression lambdas work
using System.Linq.Expressions;
Expression<Func<int, bool>> expr  = x => x > 5;   // … expression lambda
// Expression<Func<int, bool>> fail = x => { return x > 5; }; //  compile error (statement)

Console.WriteLine(expr.Compile()(10)); // True — compiled and invoked
Console.WriteLine(expr);              // x => (x > 5) — inspectable as data

// IQueryable uses expression trees to build SQL
// var users = dbContext.Users.Where(u => u.Age > 18); // EF translates to SQL WHERE
↥ back to top

Q. How do you use lambda expressions with LINQ in C#?

int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Where — filter
var evens = numbers.Where(n => n % 2 == 0);
// [2, 4, 6, 8, 10]

// Select — transform (map)
var squares = numbers.Select(n => n * n);
// [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

// Where + Select chained
var largeSquares = numbers.Where(n => n > 5).Select(n => n * n);
// [36, 49, 64, 81, 100]

// OrderBy / OrderByDescending
var sorted = numbers.OrderByDescending(n => n);
// [10, 9, 8, ...]

// GroupBy
var words = new[] { "apple", "ant", "banana", "avocado", "blueberry" };
var byLetter = words.GroupBy(w => w[0]);
foreach (var g in byLetter)
    Console.WriteLine($"{g.Key}: {string.Join(", ", g)}");
// a: apple, ant, avocado
// b: banana, blueberry

// Aggregate (fold/reduce)
int product = numbers.Aggregate(1, (acc, n) => acc * n);
Console.WriteLine(product); // 3628800 (10!)

// SelectMany — flatten nested sequences
string[] sentences = ["hello world", "foo bar baz"];
var allWords = sentences.SelectMany(s => s.Split(' '));
// [hello, world, foo, bar, baz]

// Complex object example
var orders = new[]
{
    new { Id = 1, Customer = "Alice", Amount = 250m, Year = 2024 },
    new { Id = 2, Customer = "Bob",   Amount = 100m, Year = 2024 },
    new { Id = 3, Customer = "Alice", Amount = 400m, Year = 2025 },
};

var report = orders
    .Where(o => o.Amount > 150)
    .GroupBy(o => o.Customer)
    .Select(g => new { Customer = g.Key, Total = g.Sum(o => o.Amount), Count = g.Count() })
    .OrderByDescending(r => r.Total);

foreach (var r in report)
    Console.WriteLine($"{r.Customer}: {r.Total:C} ({r.Count} orders)");
// Alice: $650.00 (2 orders)
↥ back to top

Q. What are the advantages of using lambda expressions in C#?

Advantage Description
Concise Eliminates boilerplate method declarations
Inline Logic defined at the usage site — easier to read
Closure Captures surrounding variables naturally
LINQ Enables readable query composition
Expression trees Lambdas can be inspected/translated (EF Core ’ SQL)
First-class Passed as arguments, stored in variables, returned from methods
Composable Build pipelines with Func chains
// Composable pipeline
Func<int, int> doubleIt    = x => x * 2;
Func<int, int> addTen      = x => x + 10;
Func<int, bool> isPositive = x => x > 0;

Func<int, int> pipeline = x => addTen(doubleIt(x));
Console.WriteLine(pipeline(5)); // (5*2)+10 = 20

// Higher-order functions
static Func<int, int> MultiplyBy(int factor) => x => x * factor;
var triple = MultiplyBy(3);
var quadruple = MultiplyBy(4);
Console.WriteLine(triple(7));    // 21
Console.WriteLine(quadruple(7)); // 28

// Storing and reusing
var validators = new List<Func<string, bool>>
{
    s => s.Length >= 8,
    s => s.Any(char.IsUpper),
    s => s.Any(char.IsDigit),
};

string password = "MyPass1!";
bool valid = validators.All(v => v(password));
Console.WriteLine($"Password valid: {valid}"); // True

// Lazy evaluation — lambda delays execution
Func<string> expensive = () =>
{
    Thread.Sleep(1000); // only runs when called
    return "computed";
};

// Called only when needed
if (ShouldCompute())
    Console.WriteLine(expensive());

static bool ShouldCompute() => true;
↥ back to top

Q. How do you capture variables in a lambda expression (closures) in C#?

Lambdas capture variables by reference, not by value. The lambda and outer code share the same variable.

// Basic closure — captures outer variable
int multiplier = 3;
Func<int, int> multiply = x => x * multiplier;
Console.WriteLine(multiply(5)); // 15

multiplier = 10; // change outer variable
Console.WriteLine(multiply(5)); // 50 — lambda sees updated value!

//  Classic closure trap in loops
var funcs = new List<Func<int>>();
for (int i = 0; i < 5; i++)
    funcs.Add(() => i); // captures the VARIABLE i, not its current value

funcs.ForEach(f => Console.Write(f() + " ")); // 5 5 5 5 5 — all see final i

// … Fix: capture a copy with a local variable
funcs.Clear();
for (int i = 0; i < 5; i++)
{
    int copy = i;
    funcs.Add(() => copy); // captures 'copy' — each iteration\'s own variable
}
funcs.ForEach(f => Console.Write(f() + " ")); // 0 1 2 3 4 — … correct

// … C# foreach — loop variable is NOT shared (safe by design)
int[] items = [10, 20, 30];
var itemFuncs = items.Select(item => (Func<int>)(() => item)).ToList();
itemFuncs.ForEach(f => Console.Write(f() + " ")); // 10 20 30 …

// Closure lifetime — captured variable stays alive as long as lambda exists
Func<int> counter = MakeCounter();
Console.WriteLine(counter()); // 1
Console.WriteLine(counter()); // 2
Console.WriteLine(counter()); // 3

Func<int> MakeCounter()
{
    int count = 0;               // 'count' lives as long as the lambda
    return () => ++count;        // lambda captures 'count'
}

// Memory implication — large objects captured in lambdas stay alive
// Store lambdas minimally; don\'t capture large datasets unnecessarily
↥ back to top

Q. How do you use lambda expressions with delegates? What are the limitations of lambda expressions?

// Lambda with built-in delegates
Func<string, int>   parse   = int.Parse;             // method group
Func<int, int, int> add     = (a, b) => a + b;
Action<string>      log     = Console.WriteLine;
Predicate<int>      isEven  = n => n % 2 == 0;
Comparison<string>  byLen   = (a, b) => a.Length.CompareTo(b.Length);

// Lambda with custom delegate
delegate bool NumberTest(int n, int threshold);
NumberTest greaterThan = (n, t) => n > t;
Console.WriteLine(greaterThan(10, 5)); // True

// Lambda as method argument
int[] nums = [3, 1, 4, 1, 5, 9, 2, 6];
Array.Sort(nums, (a, b) => b - a); // sort descending
Console.WriteLine(string.Join(", ", nums)); // 9, 6, 5, 4, 3, 2, 1, 1

// -------- LIMITATIONS --------

// 1. Cannot use ref/out/in parameters in expression lambdas (statement lambdas only)
Func<int, int> refLambda = (int x) =>       // …
{
    // ref int y = ref x; //  lambdas cannot yield ref returns via Func<>
    return x + 1;
};

// 2. Cannot use 'yield return' inside lambdas
// Func<IEnumerable<int>> gen = () => { yield return 1; }; //  compile error

// 3. Cannot use 'goto', 'break', 'continue' to jump outside the lambda
// 4. Cannot use unsafe code (pointers) inside lambdas

// 5. Statement lambdas cannot be expression trees
using System.Linq.Expressions;
// Expression<Func<int,int>> e = x => { return x; }; //  compile error
Expression<Func<int, int>> e = x => x; // … expression only

// 6. Debugging is harder — stack traces show generated names like <MethodName>b__0_0

// 7. Performance — each lambda declaration creates a delegate allocation
//    Use 'static' lambda or cache delegate to avoid repeated allocations
static Func<int, int>? _cached;
_cached ??= static x => x * 2; // allocated once

// 8. Cannot be used as default argument values
// void Method(Func<int, int> f = x => x) { } //  (but null default is fine)
// void Method(Func<int, int>? f = null) { }  // …
↥ back to top

Q. How do you handle exceptions in lambda expressions? Can you use async/await with lambdas? How do you use lambdas in event handling?

// Exception handling inside lambda — use try/catch within body
Func<int, int, int> safeDivide = (a, b) =>
{
    try { return a / b; }
    catch (DivideByZeroException) { return 0; }
};
Console.WriteLine(safeDivide(10, 0)); // 0

// Exception in lambda passed to LINQ — propagates to calling code
int[] numbers = [1, 2, 0, 4];
try
{
    var results = numbers.Select(n => 100 / n).ToList(); // throws at n=0
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Lambda threw: {ex.Message}");
}

// ---- ASYNC LAMBDA ----
// async lambda with Action<>-like void
Func<Task> asyncAction = async () =>
{
    await Task.Delay(100);
    Console.WriteLine("Async lambda done");
};
await asyncAction();

// async lambda returning Task<T>
Func<string, Task<int>> asyncFunc = async url =>
{
    using var client = new HttpClient();
    string data = await client.GetStringAsync(url);
    return data.Length;
};
int length = await asyncFunc("https://example.com");

//  Avoid async void lambda (no way to await/observe exceptions)
// Action asyncVoid = async () => { await Task.Delay(100); }; //  fire-and-forget, exception lost
// Instead use Func<Task>:
Func<Task> safeAsyncAction = async () => { await Task.Delay(100); }; // …

// ---- EVENT HANDLING ----
var button = new Button();

// Lambda event handler — concise
button.Clicked += (sender, e) => Console.WriteLine($"Clicked: {e.Label}");

// Store reference to unsubscribe later
EventHandler<ClickEventArgs>? handler = null;
handler = (sender, e) =>
{
    Console.WriteLine($"Handler: {e.Label}");
    button.Clicked -= handler; // unsubscribe after one click
};
button.Clicked += handler;

button.DoClick("OK");
button.DoClick("OK"); // second click — handler already removed

class Button
{
    public event EventHandler<ClickEventArgs>? Clicked;
    public void DoClick(string label) => Clicked?.Invoke(this, new ClickEventArgs(label));
}
class ClickEventArgs(string label) : EventArgs { public string Label { get; } = label; }
↥ back to top

Q. What is the syntax for a lambda with multiple parameters? How do you use lambdas with generic types?

// Zero parameters
Func<string> greeting = () => "Hello, World!";
Console.WriteLine(greeting()); // Hello, World!

// One parameter (parentheses optional)
Func<int, int> square = x => x * x;
Func<int, int> cube   = (x) => x * x * x;

// Two parameters
Func<int, int, int>     add  = (a, b) => a + b;
Func<string, int, bool> fits = (str, max) => str.Length <= max;

// Three+ parameters
Func<int, int, int, int> clamp = (val, min, max) => Math.Clamp(val, min, max);
Console.WriteLine(clamp(15, 0, 10)); // 10

// Explicitly typed (required when compiler can\'t infer)
Func<IEnumerable<int>, int, bool> hasMore = (IEnumerable<int> items, int count) =>
    items.Count() > count;

// ---- GENERIC TYPES ----

// Lambda with generic method
static TResult Transform<T, TResult>(T value, Func<T, TResult> transform)
    => transform(value);

int length = Transform("hello", s => s.Length);      // 5
string upper = Transform(42, n => n.ToString("X"));   // 2A
Console.WriteLine(length);  // 5
Console.WriteLine(upper);   // 2A

// Generic delegate type
Func<T, T> Identity<T>() => x => x;
var intIdentity    = Identity<int>();
var stringIdentity = Identity<string>();
Console.WriteLine(intIdentity(42));        // 42
Console.WriteLine(stringIdentity("hi"));   // hi

// Generic pipeline / chain
static Func<T, TResult2> Compose<T, TResult1, TResult2>(
    Func<T, TResult1> first,
    Func<TResult1, TResult2> second) => x => second(first(x));

Func<string, int>    parseLength = s => s.Length;
Func<int, string>    describe    = n => $"length is {n}";
Func<string, string> combined    = Compose(parseLength, describe);

Console.WriteLine(combined("hello"));   // length is 5
Console.WriteLine(combined("dotnet"));  // length is 6

// LINQ with generic types
List<T> Filter<T>(IEnumerable<T> items, Func<T, bool> predicate)
    => items.Where(predicate).ToList();

var numbers = Filter(Enumerable.Range(1, 20), n => n % 3 == 0);
var names   = Filter(new[] { "Alice", "Bob", "Charlie" }, n => n.StartsWith('A'));
Console.WriteLine(string.Join(", ", numbers)); // 3, 6, 9, 12, 15, 18
Console.WriteLine(string.Join(", ", names));   // Alice
↥ back to top

Q. How do Func<T>, Action<T>, and Predicate<T> work as built-in delegate types in C#?

C# provides three families of built-in generic delegate types that eliminate the need to declare custom delegates for common patterns.

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Func<TResult> and Func<T1..T16, TResult>
// — delegates that RETURN a value
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
Func<int>                      getZero    = () => 0;
Func<int, int>                 square     = x => x * x;
Func<int, int, int>            add        = (a, b) => a + b;
Func<string, int, string>      repeat     = (s, n) => string.Concat(Enumerable.Repeat(s, n));
Func<int, int, int, int>       clamp      = (v, lo, hi) => Math.Clamp(v, lo, hi);

Console.WriteLine(square(5));              // 25
Console.WriteLine(add(3, 4));             // 7
Console.WriteLine(repeat("ab", 3));       // ababab
Console.WriteLine(clamp(15, 0, 10));      // 10

// Use Func as parameter
static TResult Apply<T, TResult>(T value, Func<T, TResult> transform)
    => transform(value);

Console.WriteLine(Apply("hello", s => s.ToUpper())); // HELLO

// Compose two Funcs
Func<int, int>   doubleIt  = x => x * 2;
Func<int, string> toStr    = x => $"Value: {x}";
Func<int, string> combined = x => toStr(doubleIt(x));
Console.WriteLine(combined(7));   // Value: 14

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Action<T1..T16>
// — delegates that RETURN void
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
Action                 printHello  = () => Console.WriteLine("Hello");
Action<string>         printName   = name => Console.WriteLine($"Hello, {name}!");
Action<string, int>    printRepeat = (s, n) => { for (int i = 0; i < n; i++) Console.Write(s); };
Action<int, int, int>  printRange  = (start, end, step) =>
    { for (int i = start; i < end; i += step) Console.Write($"{i} "); };

printHello();                  // Hello
printName("Alice");            // Hello, Alice!
printRepeat("* ", 3);         // * * *
printRange(0, 10, 2);         // 0 2 4 6 8

// Pipeline with Action
static void Process<T>(IEnumerable<T> items, Action<T> processor)
{
    foreach (var item in items)
        processor(item);
}
Process(new[] { "a", "b", "c" }, s => Console.WriteLine(s.ToUpper()));

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Predicate<T>
// — shorthand for Func<T, bool> — used in List<T> methods
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
Predicate<int>    isEven      = n => n % 2 == 0;
Predicate<string> isLong      = s => s.Length > 5;
Predicate<string> startsWithA = s => s.StartsWith('A');

Console.WriteLine(isEven(4));           // True
Console.WriteLine(isLong("Hi"));        // False

// Used directly with List<T> methods
var words = new List<string> { "Apple", "Ant", "Banana", "Cherry", "Avocado" };
List<string> aWords = words.FindAll(startsWithA);    // ["Apple", "Ant", "Avocado"]
int idx = words.FindIndex(isLong);                   // 2 (Banana = 6 chars)
words.RemoveAll(w => w.Length < 4);                  // removes "Ant"

Console.WriteLine(string.Join(", ", aWords));        // Apple, Ant, Avocado

// Predicate<T> == Func<T, bool> — interchangeable via conversion
Func<int, bool> funcVersion = isEven.Invoke;
Predicate<int>  predVersion = new Predicate<int>(funcVersion);

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Comparison<T>  — bonus built-in delegate for sorting
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
var people = new List<(string Name, int Age)>
{
    ("Charlie", 30), ("Alice", 25), ("Bob", 35)
};
people.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
Console.WriteLine(string.Join(", ", people.Select(p => p.Name)));  // Alice, Bob, Charlie

Built-in delegate families at a glance:

Type Signature Returns Example
Func<TResult> () => T Value () => 42
Func<T, TResult> T => TResult Value x => x * 2
Func<T1,T2,TResult> (T1,T2) => TResult Value (a,b) => a+b
Action () => void void () => Console.WriteLine()
Action<T> T => void void x => Console.WriteLine(x)
Predicate<T> T => bool bool x => x > 0
Comparison<T> (T,T) => int int (a,b) => a.CompareTo(b)
Converter<TIn,TOut> TIn => TOut TOut s => int.Parse(s)
↥ back to top

Q. What is the difference between Expression<Func<T,TResult>> and Func<T,TResult> in C#?

Func<T, TResult> is a compiled delegate — executable code stored as IL. Expression<Func<T, TResult>> is a data structure representing the lambda as an abstract syntax tree that can be inspected, translated, or compiled at runtime. This distinction is critical for ORMs like Entity Framework Core.

using System.Linq.Expressions;

// ” 1. Func — compiled IL, not inspectable ”——————————————————————
Func<int, bool> funcDelegate = x => x > 10;

// Executes directly
Console.WriteLine(funcDelegate(15));   // True
Console.WriteLine(funcDelegate(5));    // False

// Cannot inspect the body — it\'s already compiled binary code

// ” 2. Expression<Func<T,TResult>> — a data structure (AST) ”—————
Expression<Func<int, bool>> expr = x => x > 10;

// Inspect the expression tree
Console.WriteLine(expr.Body);                              // (x > 10)
Console.WriteLine(expr.Body.NodeType);                    // GreaterThan
Console.WriteLine(((BinaryExpression)expr.Body).Left);   // x
Console.WriteLine(((BinaryExpression)expr.Body).Right);  // 10

// Compile to a delegate when you need to execute it
Func<int, bool> compiled = expr.Compile();
Console.WriteLine(compiled(15));   // True

// ” 3. Why ORMs use Expression<Func<>> ”———————————————————————————
// IQueryable<T>.Where() accepts Expression<Func<T, bool>>
// The ORM inspects the tree and translates it to SQL

// IEnumerable (LINQ to Objects) — uses Func, executes in memory
IEnumerable<int> numbers = Enumerable.Range(1, 100);
var inMemory = numbers.Where(funcDelegate);   // Func — runs as .NET code

// IQueryable (EF Core, LINQ to SQL) — uses Expression, translates to SQL
// IQueryable<Product> products = dbContext.Products;
// var fromDb = products.Where(expr);  // Expression ’ "SELECT € WHERE Price > 10"

// ” 4. Build an Expression tree manually ”————————————————————————
// Equivalent to: x => x * x + 2 * x + 1
ParameterExpression param = Expression.Parameter(typeof(int), "x");
Expression xSquared   = Expression.Multiply(param, param);        // x * x
Expression twoX       = Expression.Multiply(Expression.Constant(2), param); // 2 * x
Expression sum        = Expression.Add(xSquared, twoX);           // x*x + 2*x
Expression full       = Expression.Add(sum, Expression.Constant(1)); // + 1

var quadratic = Expression.Lambda<Func<int, int>>(full, param).Compile();
Console.WriteLine(quadratic(3));   // 3*3 + 2*3 + 1 = 16
Console.WriteLine(quadratic(5));   // 5*5 + 2*5 + 1 = 36

// ” 5. Modify / rewrite an expression ”——————————————————————————
// Common use case: expression visitor to rewrite predicates
class ReplaceParameterVisitor : ExpressionVisitor
{
    private readonly ParameterExpression _old, _new;
    public ReplaceParameterVisitor(ParameterExpression o, ParameterExpression n)
        => (_old, _new) = (o, n);
    protected override Expression VisitParameter(ParameterExpression node)
        => node == _old ? _new : base.VisitParameter(node);
}

// Combine two predicates: x > 5 AND x < 20
Expression<Func<int, bool>> gt5  = x => x > 5;
Expression<Func<int, bool>> lt20 = x => x < 20;

var newParam = Expression.Parameter(typeof(int), "x");
var visitor  = new ReplaceParameterVisitor(lt20.Parameters[0], gt5.Parameters[0]);
var combined2 = Expression.Lambda<Func<int, bool>>(
    Expression.AndAlso(gt5.Body, visitor.Visit(lt20.Body)),
    gt5.Parameters[0]);

var between = combined2.Compile();
Console.WriteLine(between(10));  // True
Console.WriteLine(between(25));  // False

Func<T> vs Expression<Func<T>>:

Aspect Func<T, TResult> Expression<Func<T, TResult>>
Nature Compiled delegate AST data structure
Executable Directly Requires .Compile()
Inspectable No … Yes
Used with IEnumerable … Yes (LINQ to Objects) Converted to Func
Used with IQueryable Pulls all data to memory … Translates to SQL/query
Performance Fast execution Slower (compilation overhead)
Modifiable No … Via ExpressionVisitor
↥ back to top

# 13. Language Integrated Query


Q. What is Expression Tree In C#?

An Expression Tree in C# is a data structure that represents code in a tree-like format, where each node is an expression (such as a method call, operation, or value). Expression trees are part of the System.Linq.Expressions namespace and are mainly used to represent code in a way that can be inspected, modified, or executed at runtime.

Key Points:

Example:

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        // Create an expression tree for: x => x * 2
        Expression<Func<int, int>> expr = x => x * 2;

        // Compile and execute the expression tree
        var func = expr.Compile();
        Console.WriteLine(func(5)); // Output: 10

        // Inspect the expression tree
        Console.WriteLine(expr.Body); // Output: (x * 2)
    }
}

Use Cases:

↥ back to top

Q. What is LINQ in C# and why is it used?

LINQ (Language Integrated Query) is a feature introduced in C# 3.0 that provides a uniform syntax for querying data from different sources — in-memory collections, databases (EF Core), XML, and more — directly inside C# code.

// Without LINQ
var result = new List<int>();
foreach (var n in new[] { 1, 2, 3, 4, 5, 6 })
    if (n % 2 == 0)
        result.Add(n * n);

// With LINQ — expressive, readable, composable
var result2 = new[] { 1, 2, 3, 4, 5, 6 }
    .Where(n => n % 2 == 0)
    .Select(n => n * n)
    .ToList(); // [4, 16, 36]

Why use LINQ?

↥ back to top

Q. What are the main advantages of using LINQ in C#?

Advantage Description
Concise syntax Replaces verbose loops with declarative one-liners
Type safety Errors caught at compile time
IntelliSense Full IDE support for auto-complete
Deferred execution Query runs only when iterated — avoid wasted work
Composable Chain multiple operators without intermediate collections
Cross-source Same API for in-memory, SQL, XML, REST
Maintainable Less boilerplate, intent is clear
Parallel support PLINQ enables easy parallelism
var products = new List<Product>
{
    new("Laptop",  1200, "Electronics"),
    new("Phone",    800, "Electronics"),
    new("Notebook",  10, "Stationery"),
    new("Pen",        2, "Stationery"),
};

// Composable pipeline — each step is a lazy transformation
var summary = products
    .Where(p => p.Price > 5)                            // filter
    .GroupBy(p => p.Category)                           // group
    .Select(g => new {                                  // project
        Category = g.Key,
        Count    = g.Count(),
        Total    = g.Sum(p => p.Price),
        Average  = g.Average(p => p.Price),
    })
    .OrderByDescending(x => x.Total);                  // sort

foreach (var s in summary)
    Console.WriteLine($"{s.Category}: {s.Count} items, total ${s.Total}");
// Electronics: 2 items, total $2000
// Stationery:  1 items, total $10

record Product(string Name, decimal Price, string Category);
↥ back to top

Q. How do you write a basic LINQ query in C#?

LINQ queries can be written in two equivalent syntaxes: query syntax (SQL-like) and method syntax (lambda chains). Method syntax is more commonly used in modern C#.

int[] numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];

// ” Query syntax ”————————————————————————————————————
var queryResult =
    from n in numbers
    where n > 3
    orderby n descending
    select n * 2;

// ” Equivalent method syntax ”————————————————————————
var methodResult = numbers
    .Where(n => n > 3)
    .OrderByDescending(n => n)
    .Select(n => n * 2);

foreach (int n in methodResult)
    Console.Write($"{n} "); // 18 12 10 10 10 8

// Basic operators used in most queries:
var data = new List<string> { "Alice", "Bob", "Charlie", "David", "Eve" };

// Filter
var longNames = data.Where(s => s.Length > 4);              // Charlie, David

// Project
var upper = data.Select(s => s.ToUpper());                  // ALICE, BOB ...

// Sort
var sorted = data.OrderBy(s => s.Length).ThenBy(s => s);    // Bob, Eve, Alice ...

// First match
string first = data.First(s => s.StartsWith('C'));          // Charlie

// Aggregate
int totalChars = data.Sum(s => s.Length);                   // 25
↥ back to top

Q. What is the difference between LINQ to Objects, LINQ to SQL, and LINQ to XML?

  LINQ to Objects LINQ to SQL LINQ to XML
Data source In-memory collections (IEnumerable<T>) SQL Server tables XML documents
Interface IEnumerable<T> IQueryable<T> IEnumerable<XElement>
Execution Always in-process Translated to SQL, runs at DB In-process DOM
Namespace System.Linq System.Linq + EF Core System.Xml.Linq
Translation None — pure C# C# ’ SQL C# ’ XPath-style
// 1. LINQ to Objects — in-memory
int[] nums = [1, 2, 3, 4, 5];
var evens = nums.Where(n => n % 2 == 0).ToArray(); // [2, 4]

// 2. LINQ to SQL / EF Core — translated to SQL
// SELECT p.* FROM Products p WHERE p.Price > 100 ORDER BY p.Name
var products = await db.Products
    .Where(p => p.Price > 100)
    .OrderBy(p => p.Name)
    .ToListAsync(); // IQueryable<T> ’ SQL query at DB

// 3. LINQ to XML — query XML documents
var xml = XDocument.Parse("""
    <products>
      <product id="1"><name>Laptop</name><price>1200</price></product>
      <product id="2"><name>Phone</name><price>800</price></product>
    </products>
    """);

var names = xml.Root!
    .Elements("product")
    .Where(e => (decimal)e.Element("price")! > 900)
    .Select(e => (string)e.Element("name")!);

foreach (var name in names)
    Console.WriteLine(name); // Laptop
↥ back to top

Q. Can you provide an example of a LINQ query that filters and sorts data?

var employees = new List<Employee>
{
    new(1, "Alice",   "Engineering", 95_000),
    new(2, "Bob",     "Marketing",   60_000),
    new(3, "Charlie", "Engineering", 85_000),
    new(4, "Diana",   "Engineering", 110_000),
    new(5, "Eve",     "HR",          55_000),
    new(6, "Frank",   "Marketing",   70_000),
};

// Filter: Engineering dept with salary > 80k; sort by salary descending
var senior = employees
    .Where(e => e.Department == "Engineering" && e.Salary > 80_000)
    .OrderByDescending(e => e.Salary)
    .ThenBy(e => e.Name)
    .Select(e => new { e.Name, e.Salary });

foreach (var e in senior)
    Console.WriteLine($"{e.Name}: ${e.Salary:N0}");
// Diana: $110,000
// Alice: $95,000
// Charlie: $85,000

// Query syntax equivalent
var seniorQuery =
    from e in employees
    where e.Department == "Engineering" && e.Salary > 80_000
    orderby e.Salary descending, e.Name
    select new { e.Name, e.Salary };

// Pagination
var page2 = employees
    .OrderBy(e => e.Name)
    .Skip(2)          // skip first 2
    .Take(2)          // take next 2
    .ToList();

record Employee(int Id, string Name, string Department, decimal Salary);
↥ back to top

Q. What are the different types of LINQ operators in C#?

LINQ operators are categorised by their function:

Category Operators
Filtering Where, OfType
Projection Select, SelectMany
Sorting OrderBy, OrderByDescending, ThenBy, ThenByDescending, Reverse
Grouping GroupBy, ToLookup
Joining Join, GroupJoin, Zip
Set Distinct, DistinctBy, Union, Intersect, Except, ExceptBy
Aggregation Count, LongCount, Sum, Min, Max, Average, Aggregate
Element First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAt
Quantifiers Any, All, Contains
Partitioning Skip, Take, SkipWhile, TakeWhile, SkipLast, TakeLast
Conversion ToList, ToArray, ToDictionary, ToHashSet, AsEnumerable, Cast
Concatenation Concat, Append, Prepend
Generation Range, Repeat, Empty
int[] a = [1, 2, 3, 4, 5];
int[] b = [3, 4, 5, 6, 7];

// Set operators
var union     = a.Union(b);           // [1,2,3,4,5,6,7]
var intersect = a.Intersect(b);       // [3,4,5]
var except    = a.Except(b);          // [1,2]

// Generation
var range  = Enumerable.Range(1, 5);  // [1,2,3,4,5]
var repeat = Enumerable.Repeat("x", 3); // ["x","x","x"]

// Partitioning
var page = a.Skip(1).Take(3);         // [2,3,4]
var tail = a.TakeLast(2);             // [4,5]

// Quantifiers
bool anyEven = a.Any(n => n % 2 == 0);  // true
bool allPos  = a.All(n => n > 0);       // true

// DistinctBy / MinBy / MaxBy (.NET 6+)
var words = new[] { "apple", "ant", "banana", "avocado" };
var byFirstLetter = words.DistinctBy(w => w[0]); // apple, banana
var shortest = words.MinBy(w => w.Length);         // ant
↥ back to top

Q. How do you use the Select and Where operators in LINQ?

var people = new List<Person>
{
    new("Alice", 30, "alice@example.com"),
    new("Bob",   17, "bob@example.com"),
    new("Carol", 25, "carol@example.com"),
    new("Dave",  15, "dave@example.com"),
};

// Where — filter adults
var adults = people.Where(p => p.Age >= 18);
// Alice, Carol

// Select — project to a new type
var emails = people.Select(p => p.Email);
// ["alice@example.com", ...]

// Combine: filter then project
var adultEmails = people
    .Where(p => p.Age >= 18)
    .Select(p => new { p.Name, p.Email });

// Select with index
var indexed = people
    .Select((p, i) => $"{i + 1}. {p.Name}");
// ["1. Alice", "2. Bob", ...]

// SelectMany — flatten nested collections
var orders = new List<Order>
{
    new("Alice", ["Laptop", "Mouse"]),
    new("Bob",   ["Phone"]),
};

var allItems = orders.SelectMany(o => o.Items);
// ["Laptop", "Mouse", "Phone"]

// SelectMany with result selector
var orderItems = orders.SelectMany(
    o => o.Items,
    (order, item) => new { order.Customer, Item = item });
// { Customer: "Alice", Item: "Laptop" }, ...

record Person(string Name, int Age, string Email);
record Order(string Customer, List<string> Items);
↥ back to top

Q. What is the purpose of the GroupBy operator in LINQ?

GroupBy partitions a sequence into groups by a key. Each group is an IGrouping<TKey, TElement> which exposes the Key and the matching elements.

var orders = new List<Order>
{
    new(1, "Alice", "Electronics", 1200m),
    new(2, "Bob",   "Electronics",  800m),
    new(3, "Alice", "Stationery",    15m),
    new(4, "Carol", "Electronics",  500m),
    new(5, "Bob",   "Stationery",     8m),
};

// Group by category
var byCategory = orders.GroupBy(o => o.Category);

foreach (var group in byCategory)
{
    Console.WriteLine($"Category: {group.Key} ({group.Count()} orders)");
    foreach (var o in group)
        Console.WriteLine($"  {o.Customer}: ${o.Amount}");
}
// Category: Electronics (3 orders)
//   Alice: $1200  Bob: $800  Carol: $500
// Category: Stationery (2 orders)
//   Alice: $15  Bob: $8

// GroupBy with aggregation (most common use)
var summary = orders
    .GroupBy(o => o.Category)
    .Select(g => new
    {
        Category = g.Key,
        Count    = g.Count(),
        Total    = g.Sum(o => o.Amount),
        Avg      = g.Average(o => o.Amount),
        Max      = g.Max(o => o.Amount),
    })
    .OrderByDescending(x => x.Total);

// Multi-key grouping (anonymous type as key)
var byCustomerAndCategory = orders
    .GroupBy(o => new { o.Customer, o.Category })
    .Select(g => new { g.Key.Customer, g.Key.Category, Total = g.Sum(o => o.Amount) });

// ToLookup — eager, indexed; GroupBy is lazy
var lookup = orders.ToLookup(o => o.Category);
var elecOrders = lookup["Electronics"]; // O(1) access

record Order(int Id, string Customer, string Category, decimal Amount);
↥ back to top

Q. How do you perform a join operation using LINQ?

var customers = new List<Customer>
{
    new(1, "Alice"),
    new(2, "Bob"),
    new(3, "Carol"),
};

var orders = new List<Order>
{
    new(101, 1, "Laptop",  1200m),
    new(102, 1, "Mouse",     25m),
    new(103, 2, "Phone",    800m),
    new(104, 2, "Case",      20m),
};

// 1. Inner Join — only matching rows (like SQL INNER JOIN)
var joined = customers.Join(
    orders,
    c => c.Id,          // outer key
    o => o.CustomerId,  // inner key
    (c, o) => new { c.Name, o.Product, o.Amount });

// Alice-Laptop, Alice-Mouse, Bob-Phone, Bob-Case

// 2. Group Join — like SQL LEFT OUTER JOIN
var grouped = customers.GroupJoin(
    orders,
    c => c.Id,
    o => o.CustomerId,
    (c, orderGroup) => new
    {
        c.Name,
        Orders = orderGroup.ToList(),
        Total  = orderGroup.Sum(o => o.Amount),
    });
// Carol has empty Orders list (no matching orders)

// 3. Left outer join using GroupJoin + SelectMany
var leftJoin = customers
    .GroupJoin(orders, c => c.Id, o => o.CustomerId,
        (c, os) => new { Customer = c, Orders = os })
    .SelectMany(
        x => x.Orders.DefaultIfEmpty(),
        (x, o) => new { x.Customer.Name, Product = o?.Product ?? "No orders" });
// Carol: No orders

// 4. Query syntax join
var queryJoin =
    from c in customers
    join o in orders on c.Id equals o.CustomerId
    select new { c.Name, o.Product, o.Amount };

// 5. Zip — pair elements by position
var letters = new[] { "A", "B", "C" };
var nums    = new[] { 1, 2, 3 };
var zipped  = letters.Zip(nums, (l, n) => $"{l}{n}"); // A1, B2, C3

record Customer(int Id, string Name);
record Order(int Id, int CustomerId, string Product, decimal Amount);
↥ back to top

Q. What is the difference between deferred execution and immediate execution in LINQ?

  Deferred Execution Immediate Execution
When runs When iterated (foreach, ToList, etc.) Immediately when called
Re-runs Every time you iterate Result captured once
Operators Where, Select, OrderBy, GroupBy, Skip, Take ToList, ToArray, Count, First, Sum, Any, ToDictionary
var data = new List<int> { 1, 2, 3, 4, 5 };

// Deferred — query is a recipe, not a result
var query = data.Where(n => n > 2); // nothing runs here

data.Add(6); // modifying source AFTER query definition

foreach (int n in query) // runs NOW — sees the added 6
    Console.Write($"{n} "); // 3 4 5 6

// Immediate — snapshot taken now
var snapshot = data.Where(n => n > 2).ToList();
data.Add(7);
Console.WriteLine(snapshot.Count); // 4 — does NOT see 7

// Deferred: re-evaluated each iteration
var counter = 0;
var lazy = data.Select(n => { counter++; return n; });
_ = lazy.ToList(); // counter = 7
_ = lazy.ToList(); // counter = 14 — ran again!

var eager = data.Select(n => { counter++; return n; }).ToList();
_ = eager.Count; // counter doesn\'t increase — already materialised

// Practical implication with EF Core
IQueryable<Product> query2 = db.Products.Where(p => p.Price > 100);
// No SQL sent yet

if (filterByCategory)
    query2 = query2.Where(p => p.Category == "Electronics"); // compose

var results = await query2.ToListAsync(); // ONE SQL query with both conditions
↥ back to top

Q. How do you use the Aggregate operator in LINQ?

Aggregate applies an accumulator function over a sequence, allowing arbitrary fold operations that built-in operators like Sum or Max don't cover.

int[] nums = [1, 2, 3, 4, 5];

// 1. Basic — sum (equivalent to nums.Sum())
int sum = nums.Aggregate((acc, n) => acc + n); // 15
// acc starts with first element: ((((1+2)+3)+4)+5)

// 2. With seed
int sumFromTen = nums.Aggregate(10, (acc, n) => acc + n); // 25

// 3. With seed + result selector
string result = nums.Aggregate(
    seed: new System.Text.StringBuilder(),
    func: (sb, n) => { sb.Append(n); sb.Append(','); return sb; },
    resultSelector: sb => sb.ToString().TrimEnd(','));
Console.WriteLine(result); // 1,2,3,4,5

// 4. Product
long product = nums.Aggregate(1L, (acc, n) => acc * n); // 120

// 5. Build a running max
int runMax = nums.Aggregate(int.MinValue, Math.Max); // 5

// 6. Aggregate words into a sentence
string[] words = ["The", "quick", "brown", "fox"];
string sentence = words.Aggregate((a, b) => $"{a} {b}");
Console.WriteLine(sentence); // The quick brown fox

// 7. Group counts with Aggregate (for illustration)
var freq = "abracadabra".Aggregate(
    new Dictionary<char, int>(),
    (dict, ch) =>
    {
        dict[ch] = dict.GetValueOrDefault(ch) + 1;
        return dict;
    });
// a:5, b:2, r:2, c:1, d:1

// Note: Aggregate is sequential. For parallel fold, use PLINQ .Aggregate()
// with partition-aware overloads.
↥ back to top

Q. What is the Let keyword in LINQ and how is it used?

let is a query syntax keyword that introduces a sub-expression (computed value) and gives it a name, which can then be reused in the rest of the query without recomputing it.

var words = new[] { "Hello", "World", "LINQ", "Rocks", "C#" };

// Without let — .Length evaluated twice
var longUpper =
    from w in words
    where w.Length > 4
    select w.ToUpper();

// With let — computed once and reused
var withLet =
    from w in words
    let upper  = w.ToUpper()        // computed once
    let length = upper.Length       // computed once
    where length > 4
    orderby length descending
    select $"{upper} ({length})";

foreach (var s in withLet)
    Console.WriteLine(s);
// HELLO (5)
// WORLD (5)
// ROCKS (5)

// Useful when sub-expression is expensive (e.g., regex match)
using System.Text.RegularExpressions;
var emails = new[] { "alice@example.com", "notanemail", "bob@test.org" };

var validDomains =
    from e in emails
    let m = Regex.Match(e, @"@(.+)$")
    where m.Success
    let domain = m.Groups[1].Value
    select domain;
// example.com, test.org

// Method syntax equivalent — use intermediate Select
var methodEquivalent = words
    .Select(w => (word: w, upper: w.ToUpper(), length: w.Length))
    .Where(x => x.length > 4)
    .OrderByDescending(x => x.length)
    .Select(x => $"{x.upper} ({x.length})");
↥ back to top

Q. How do you handle null values in LINQ queries?

var names = new List<string?> { "Alice", null, "Bob", null, "Carol" };

// 1. Filter out nulls
var nonNull = names.Where(n => n is not null); // Alice, Bob, Carol

// 2. OfType<T> — filters and casts (removes nulls from nullable sequences)
var nonNullOfType = names.OfType<string>(); // Alice, Bob, Carol

// 3. Null-coalescing in Select
var safe = names.Select(n => n ?? "Unknown"); // Alice, Unknown, Bob, ...

// 4. Null-conditional in nested navigation
var orders = new List<Order?> { new(1, null), null, new(3, "Laptop") };

var products = orders
    .Where(o => o?.Product is not null)
    .Select(o => o!.Product!.ToUpper());

// 5. FirstOrDefault / SingleOrDefault safely
var people = new List<Person> { new("Alice", 30), new("Bob", 25) };
Person? found = people.FirstOrDefault(p => p.Name == "Unknown");
string display = found?.Name ?? "Not found"; // Not found

// 6. DefaultIfEmpty — avoid empty sequence exceptions
var numbers = new List<int>();
int maxOrDefault = numbers.DefaultIfEmpty(0).Max(); // 0 instead of exception

// 7. Null guards in GroupBy / Join
var items = new List<Item> { new("x", null), new("y", "cat"), new("z", "cat") };

var grouped = items
    .Where(i => i.Category is not null)
    .GroupBy(i => i.Category!);

// 8. Nullable reference types + LINQ (.NET 10 / C# 14)
IEnumerable<string> guaranteed = names.Where(n => n != null)!; // suppress warning

record Person(string Name, int Age);
record Order(int Id, string? Product);
record Item(string Name, string? Category);
↥ back to top

Q. What are anonymous types in LINQ and how are they used?

Anonymous types are compiler-generated, read-only reference types created with new { ... }. They are commonly used in LINQ Select projections to shape query results without defining an explicit class.

var products = new List<Product>
{
    new(1, "Laptop",  1200m, "Electronics"),
    new(2, "Phone",    800m, "Electronics"),
    new(3, "Notebook",  10m, "Stationery"),
};

// 1. Basic projection into anonymous type
var projections = products.Select(p => new
{
    p.Name,            // member name inferred from property
    p.Price,
    PriceWithTax = p.Price * 1.2m,  // computed member with explicit name
});

foreach (var item in projections)
    Console.WriteLine($"{item.Name}: ${item.PriceWithTax:F2}");
// Laptop: $1440.00 ...

// 2. Grouping result using anonymous type
var grouped = products
    .GroupBy(p => p.Category)
    .Select(g => new
    {
        Category = g.Key,
        Count    = g.Count(),
        Total    = g.Sum(p => p.Price),
    });

// 3. Multi-key grouping
var multiKey = products.GroupBy(p => new { p.Category })
    .Select(g => new { g.Key.Category, Avg = g.Average(p => p.Price) });

// 4. Anonymous types are structurally equal (compiler generates Equals/GetHashCode)
var a = new { Name = "Alice", Age = 30 };
var b = new { Name = "Alice", Age = 30 };
Console.WriteLine(a.Equals(b)); // true — value equality based on properties

// 5. Limitation: cannot return anonymous types from methods
// Use record types when you need to cross method boundaries
var dto = products.Select(p => new ProductDto(p.Id, p.Name, p.Price)).ToList();

// Prefer record over anonymous type in .NET 10
record ProductDto(int Id, string Name, decimal Price);
record Product(int Id, string Name, decimal Price, string Category);
↥ back to top

Q. How do you use LINQ with collections in C#?

LINQ works with any type implementing IEnumerable<T> — arrays, List<T>, Dictionary<K,V>, HashSet<T>, Stack<T>, custom collections, etc.

// Arrays
int[] primes = [2, 3, 5, 7, 11, 13];
var largePrimes = primes.Where(n => n > 5).ToArray(); // [7, 11, 13]

// List<T>
var names = new List<string> { "Charlie", "Alice", "Bob" };
var sorted = names.OrderBy(n => n).ToList(); // [Alice, Bob, Charlie]

// Dictionary<K,V> — query KeyValuePairs
var scores = new Dictionary<string, int>
{
    ["Alice"] = 90, ["Bob"] = 75, ["Carol"] = 88
};
var topStudents = scores
    .Where(kv => kv.Value >= 80)
    .OrderByDescending(kv => kv.Value)
    .Select(kv => $"{kv.Key}: {kv.Value}");
// Alice: 90, Carol: 88

// HashSet<T>
var set1 = new HashSet<int> { 1, 2, 3, 4 };
var set2 = new HashSet<int> { 3, 4, 5, 6 };
var common = set1.Intersect(set2).ToHashSet(); // {3, 4}

// Stack<T> / Queue<T>
var stack = new Stack<int>(new[] { 1, 2, 3, 4, 5 });
var top3  = stack.Take(3).ToList(); // [5, 4, 3]

// Nested collections with SelectMany
var departments = new List<Department>
{
    new("Eng",  [new("Alice"), new("Bob")]),
    new("HR",   [new("Carol")]),
};

var allEmployees = departments.SelectMany(d => d.Employees); // flat list

// Convert LINQ result back to collection types
List<int>            list = primes.Where(n => n > 3).ToList();
int[]                arr  = primes.Where(n => n > 3).ToArray();
HashSet<int>         hs   = primes.ToHashSet();
Dictionary<int, int> dict = primes.ToDictionary(n => n, n => n * n);

record Department(string Name, List<Employee> Employees);
record Employee(string Name);
↥ back to top

Q. How do you optimize LINQ queries for performance?

// 1. Materialise once — avoid re-enumerating IEnumerable
//  Query re-executes on each iteration
IEnumerable<int> query = data.Where(n => n > 0);
int count = query.Count(); // first iteration
int sum   = query.Sum();   // second iteration

// … Materialise once
var list  = data.Where(n => n > 0).ToList();
int count2 = list.Count;
int sum2   = list.Sum();

// 2. AsNoTracking with EF Core (covered in DB section)
// 3. Use Any() not Count() > 0
//  Counts all elements
if (data.Count() > 0) { }
// … Stops at first match
if (data.Any()) { }

// 4. Filter early — Where before Select/OrderBy
//  Sort all 1M items, then filter
data.OrderBy(n => n).Where(n => n > 1000);
// … Filter first, sort fewer items
data.Where(n => n > 1000).OrderBy(n => n);

// 5. Use concrete collection operators over LINQ when possible
//  LINQ on List when you know it\'s a List
int last = list.Last();
// … Direct property
int last2 = list[^1]; // O(1) index vs O(n) LINQ Last()

// 6. PLINQ for CPU-intensive data processing
var result = data.AsParallel()
    .WithDegreeOfParallelism(Environment.ProcessorCount)
    .Where(ExpensivePredicate)
    .Select(ExpensiveTransform)
    .ToList();

// 7. Compiled LINQ for EF Core hot paths (see AsNoTracking section)
// 8. Avoid closures that capture large objects in query lambdas
int threshold = 100; // captured by value — fine
var filtered = data.Where(n => n > threshold);

// 9. Use FrozenDictionary for static lookup tables (.NET 8+)
using System.Collections.Frozen;
FrozenSet<int> allowedIds = new[] { 1, 5, 10, 42 }.ToFrozenSet();
var found = data.Where(n => allowedIds.Contains(n)); // O(1) lookup per element

// 10. Chunk for batched processing (.NET 6+)
foreach (int[] batch in data.Chunk(100))
    ProcessBatch(batch); // process in batches of 100

static bool ExpensivePredicate(int n) => n > 0;
static int  ExpensiveTransform(int n) => n * 2;
static void ProcessBatch(int[] batch) { }
↥ back to top

Q. Explain the differences between Parallel.ForEach and PLINQ (Parallel LINQ)?

  Parallel.ForEach PLINQ (AsParallel())
Style Imperative (action-based) Declarative (LINQ pipeline)
Result Side effects only Returns IEnumerable<T>
Ordering Not preserved Use AsOrdered() to preserve
Exception AggregateException AggregateException
Cancellation ParallelOptions.CancellationToken .WithCancellation(ct)
Best for Fire-and-forget parallel work Data transformation pipelines
int[] data = Enumerable.Range(1, 1_000_000).ToArray();

// 1. Parallel.ForEach — side effects, no return
var bag = new System.Collections.Concurrent.ConcurrentBag<int>();
Parallel.ForEach(data,
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    n => { if (n % 2 == 0) bag.Add(n * n); });
Console.WriteLine(bag.Count); // ~500,000

// 2. PLINQ — transforms and returns
var results = data.AsParallel()
    .WithDegreeOfParallelism(4)
    .Where(n => n % 2 == 0)
    .Select(n => n * n)
    .ToList(); // order not guaranteed

// 3. PLINQ with preserved order
var ordered = data.AsParallel()
    .AsOrdered()
    .Where(n => n % 2 == 0)
    .Select(n => n * n)
    .ToList(); // slower but maintains input order

// 4. PLINQ with cancellation
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
    var r = data.AsParallel()
        .WithCancellation(cts.Token)
        .Select(HeavyWork)
        .ToList();
}
catch (OperationCanceledException) { Console.WriteLine("Cancelled"); }

// 5. ForEachAsync (.NET 6+) — async parallel (best for I/O)
await Parallel.ForEachAsync(data.Take(100),
    new ParallelOptions { MaxDegreeOfParallelism = 10 },
    async (n, ct) => await ProcessAsync(n, ct));

// Recommendation:
// I/O-bound parallel work  ’ Parallel.ForEachAsync
// CPU-bound transformation ’ PLINQ
// CPU-bound side effects   ’ Parallel.ForEach

static int HeavyWork(int n) => n * n;
static async Task ProcessAsync(int n, CancellationToken ct) => await Task.Delay(1, ct);
↥ back to top

Q. How does LINQ's “Where” method work?

Where is a deferred, lazy extension method on IEnumerable<T> that uses an iterator (via yield return) to evaluate the predicate for each element only as the sequence is consumed.

// Conceptual implementation of Where
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
    foreach (T item in source)
        if (predicate(item))
            yield return item; // caller receives control here
}

// 1. Basic usage
var nums = new[] { 1, 2, 3, 4, 5, 6 };
var evens = nums.Where(n => n % 2 == 0);
// Nothing evaluated yet — evens is an IEnumerable<int>

foreach (int n in evens)      // evaluation starts here
    Console.Write($"{n} ");   // 2 4 6

// 2. Predicate with index overload
var withIndex = nums.Where((n, i) => i % 2 == 0); // elements at even indices
// 1 (index 0), 3 (index 2), 5 (index 4)

// 3. Chained Where — each adds a filter (all evaluated in one pass)
var result = nums
    .Where(n => n > 2)    // first predicate
    .Where(n => n < 6);   // second predicate — single iteration total

// 4. Where with IQueryable<T> (EF Core)
// The lambda is NOT a delegate — it\'s an Expression<Func<T, bool>>
// EF Core translates it to SQL: WHERE Price > 100
var products = await db.Products
    .Where(p => p.Price > 100)
    .ToListAsync();

// 5. Short-circuit — Where stops iterating once caller stops
string? firstEven = nums.Where(n => n % 2 == 0).FirstOrDefault()?.ToString();
// Only evaluates until the first even number is found — does not scan the rest

// 6. Side effects and lazy evaluation
int callCount = 0;
var tracked = nums.Where(n => { callCount++; return n > 3; });
Console.WriteLine(callCount); // 0 — not yet evaluated
_ = tracked.ToList();
Console.WriteLine(callCount); // 6 — evaluated all 6 elements
↥ back to top

Q. What are the benefits of a Deferred Execution in LINQ?

Benefit Description
Composability Build complex queries step by step without intermediate allocations
Efficiency Only processes elements that reach the end of the pipeline
Live data Query always sees the current state of the source
EF Core integration Conditions added after query definition still generate one SQL statement
Short-circuiting Operators like First, Any stop as soon as a match is found
var data = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// 1. Composability — no intermediate List allocations
var pipeline = data
    .Where(n => n % 2 == 0)   // IEnumerable — lazy
    .Select(n => n * n)        // IEnumerable — lazy
    .TakeWhile(n => n < 50);   // IEnumerable — lazy
// All three combined into ONE pass when iterated

foreach (int n in pipeline) Console.Write($"{n} "); // 4 16 36

// 2. Live view of source
var query = data.Where(n => n > 7);
data.Add(11);                  // added AFTER query defined
data.Add(12);
Console.WriteLine(string.Join(",", query)); // 8 9 10 11 12 — sees new items

// 3. EF Core — compose before executing
IQueryable<Product> q = db.Products.AsQueryable();
if (minPrice.HasValue) q = q.Where(p => p.Price >= minPrice.Value);
if (category != null) q = q.Where(p => p.Category == category);
var result = await q.ToListAsync(); // ONE SQL query with all conditions

// 4. Short-circuiting saves work
int checks = 0;
bool found = data
    .Where(n => { checks++; return n > 5; })
    .Any();  // stops at first match (n=6)
Console.WriteLine($"Checks: {checks}"); // 6 — not all 10 elements

//  Caveat: re-enumeration re-executes the query
var expensive = data.Where(n => Expensive(n)); // avoid calling twice
var list = expensive.ToList(); // materialise once
Console.WriteLine(list.Count);
Console.WriteLine(list.Sum());

static bool Expensive(int n) => n > 0;
↥ back to top

Q. Can you explain the difference between a query expression and a method chain in LINQ?

Both forms are equivalent — the compiler transforms query syntax into method calls during compilation. Method syntax is more powerful (supports all operators); query syntax is more readable for complex joins and grouping.

var products = new List<Product>
{
    new("Laptop",  1200m, "Electronics"),
    new("Phone",    800m, "Electronics"),
    new("Notebook",  10m, "Stationery"),
    new("Pen",        2m, "Stationery"),
};

// ” Query syntax (SQL-like) ”——————————————————————————————————————————
var queryExpr =
    from p in products
    where p.Price > 50
    orderby p.Category, p.Price descending
    select new { p.Name, p.Price };

// ” Equivalent method chain ”——————————————————————————————————————————
var methodChain = products
    .Where(p => p.Price > 50)
    .OrderBy(p => p.Category)
    .ThenByDescending(p => p.Price)
    .Select(p => new { p.Name, p.Price });

// ” Join — query syntax is more readable ”————————————————————————————
var customers = new List<(int Id, string Name)> { (1, "Alice"), (2, "Bob") };
var orders    = new List<(int CId, string Item)> { (1, "Laptop"), (1, "Mouse"), (2, "Phone") };

// Query syntax
var joinQuery =
    from c in customers
    join o in orders on c.Id equals o.CId
    select new { c.Name, o.Item };

// Method syntax (equivalent)
var joinMethod = customers.Join(
    orders, c => c.Id, o => o.CId,
    (c, o) => new { c.Name, o.Item });

// ” Operators ONLY available in method syntax ”———————————————————————
// (no query syntax equivalent)
var count = products.Count(p => p.Price > 100);
var first = products.FirstOrDefault(p => p.Price > 100);
var dist  = products.DistinctBy(p => p.Category);
var chunk = products.Chunk(2);

// ” let in query syntax = intermediate Select in method syntax ”——————
var qLet =
    from p in products
    let discounted = p.Price * 0.9m
    where discounted > 100
    select new { p.Name, discounted };

var mLet = products
    .Select(p => (p, discounted: p.Price * 0.9m))
    .Where(x => x.discounted > 100)
    .Select(x => new { x.p.Name, x.discounted });

record Product(string Name, decimal Price, string Category);
↥ back to top

Q. Can you give an example of using LINQ to filter data in a collection?

var employees = new List<Employee>
{
    new(1, "Alice",   "Engineering", 95_000, new DateTime(2019, 3, 15)),
    new(2, "Bob",     "Marketing",   60_000, new DateTime(2021, 6,  1)),
    new(3, "Charlie", "Engineering", 85_000, new DateTime(2018, 1, 20)),
    new(4, "Diana",   "Engineering", 110_000, new DateTime(2015, 9, 10)),
    new(5, "Eve",     "HR",          55_000, new DateTime(2022, 4, 30)),
    new(6, "Frank",   "Marketing",   70_000, new DateTime(2020, 11, 5)),
};

// Filter by department
var engineers = employees.Where(e => e.Department == "Engineering");

// Filter by salary range
var midRange = employees.Where(e => e.Salary is >= 60_000 and <= 90_000);

// Filter by hire date (joined before 2020)
var senior = employees.Where(e => e.HireDate.Year < 2020);

// Multiple conditions
var seniorEngineers = employees
    .Where(e => e.Department == "Engineering"
             && e.Salary > 80_000
             && e.HireDate.Year < 2021);

// Filter with string operations
var alice = employees.Where(e => e.Name.StartsWith("A", StringComparison.OrdinalIgnoreCase));

// Filter in query syntax
var mktQuery =
    from e in employees
    where e.Department == "Marketing" && e.Salary >= 65_000
    select e;

// Complex filter — employees who joined in the last 3 years OR earn over 100k
var complex = employees.Where(e =>
    e.HireDate >= DateTime.UtcNow.AddYears(-3) || e.Salary > 100_000);

// Chained filters (same as AND)
var chained = employees
    .Where(e => e.Department == "Engineering")
    .Where(e => e.Salary > 80_000);

foreach (var e in seniorEngineers)
    Console.WriteLine($"{e.Name}: ${e.Salary:N0}, hired {e.HireDate:yyyy-MM-dd}");
// Diana: $110,000, hired 2015-09-10
// Charlie: $85,000, hired 2018-01-20

record Employee(int Id, string Name, string Department, decimal Salary, DateTime HireDate);
↥ back to top

Q. How can LINQ be used to perform grouping and aggregation operations on data?

var sales = new List<Sale>
{
    new("Alice",   "Electronics", "Laptop",  1200m, new DateTime(2026, 1, 15)),
    new("Bob",     "Electronics", "Phone",    800m, new DateTime(2026, 1, 20)),
    new("Alice",   "Stationery",  "Notebook",  10m, new DateTime(2026, 2, 3)),
    new("Carol",   "Electronics", "Tablet",   600m, new DateTime(2026, 2, 10)),
    new("Bob",     "Stationery",  "Pens",       5m, new DateTime(2026, 2, 15)),
    new("Alice",   "Electronics", "Headphones",150m,new DateTime(2026, 3, 1)),
};

// 1. Group by single key with aggregate
var byCategory = sales
    .GroupBy(s => s.Category)
    .Select(g => new
    {
        Category = g.Key,
        Count    = g.Count(),
        Total    = g.Sum(s => s.Amount),
        Average  = g.Average(s => s.Amount),
        Min      = g.Min(s => s.Amount),
        Max      = g.Max(s => s.Amount),
    });

// 2. Group by multiple keys
var bySalesperson = sales
    .GroupBy(s => new { s.Salesperson, s.Category })
    .Select(g => new
    {
        g.Key.Salesperson,
        g.Key.Category,
        Total = g.Sum(s => s.Amount),
    })
    .OrderBy(x => x.Salesperson).ThenByDescending(x => x.Total);

// 3. Group by time period (month)
var byMonth = sales
    .GroupBy(s => new { s.Date.Year, s.Date.Month })
    .Select(g => new
    {
        Month = $"{g.Key.Year}-{g.Key.Month:D2}",
        Revenue = g.Sum(s => s.Amount),
        Orders  = g.Count(),
    })
    .OrderBy(x => x.Month);

// 4. Running totals with Aggregate
decimal running = 0;
var runningTotals = sales
    .OrderBy(s => s.Date)
    .Select(s => { running += s.Amount; return new { s.Salesperson, s.Amount, Running = running }; });

// 5. Top N per group
var topSalePerCategory = sales
    .GroupBy(s => s.Category)
    .SelectMany(g => g.OrderByDescending(s => s.Amount).Take(1));

foreach (var row in byCategory)
    Console.WriteLine($"{row.Category}: {row.Count} sales, total ${row.Total}");
// Electronics: 4 sales, total $2750
// Stationery: 2 sales, total $15

record Sale(string Salesperson, string Category, string Product, decimal Amount, DateTime Date);
↥ back to top

Q. How does the Single Responsibility Principle (SRP) apply to LINQ code?

SRP states that a class/method should have only one reason to change. In LINQ: each query or method should do one thing — don't mix filtering, transforming, and persisting in the same expression.

//  Violates SRP — one method filters, transforms, logs, AND saves
public async Task ProcessOrdersAsync(List<Order> orders, AppDbContext db)
{
    var result = orders
        .Where(o => o.Status == "Pending" && o.Amount > 100)
        .Select(o => { Console.WriteLine($"Processing {o.Id}"); return o; }) // side effect!
        .Select(o => new InvoiceDto(o.Id, o.Amount * 1.1m));

    db.Invoices.AddRange(result.Select(dto => new Invoice(dto)));
    await db.SaveChangesAsync();
}

// … SRP — each method has one responsibility
public IEnumerable<Order> FilterEligibleOrders(IEnumerable<Order> orders) =>
    orders.Where(o => o.Status == "Pending" && o.Amount > 100);

public IEnumerable<InvoiceDto> ProjectToInvoiceDtos(IEnumerable<Order> orders) =>
    orders.Select(o => new InvoiceDto(o.Id, o.Amount * 1.1m));

public async Task SaveInvoicesAsync(IEnumerable<InvoiceDto> dtos, AppDbContext db)
{
    db.Invoices.AddRange(dtos.Select(dto => new Invoice(dto)));
    await db.SaveChangesAsync();
}

// Compose at call site
public async Task RunAsync(List<Order> orders, AppDbContext db)
{
    var eligible = FilterEligibleOrders(orders);
    var dtos     = ProjectToInvoiceDtos(eligible);
    await SaveInvoicesAsync(dtos, db);
}

record Order(int Id, string Status, decimal Amount);
record InvoiceDto(int OrderId, decimal Total);
record Invoice(InvoiceDto dto);
↥ back to top

Q. Can you demonstrate the use of LINQ to implement the Open-Closed Principle (OCP)?

OCP — open for extension, closed for modification. Represent query logic as injectable Func<T, bool> / Expression<Func<T, bool>> predicates so new filters can be added without modifying existing query code.

// Specification pattern — encapsulates query logic, open to extension
public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
}

public class PriceAboveSpecification(decimal min) : ISpecification<Product>
{
    public Expression<Func<Product, bool>> Criteria => p => p.Price > min;
}

public class CategorySpecification(string category) : ISpecification<Product>
{
    public Expression<Func<Product, bool>> Criteria => p => p.Category == category;
}

// Repository — does NOT change when new specs are added
public class ProductRepository(AppDbContext db)
{
    public async Task<List<Product>> FindAsync(ISpecification<Product> spec) =>
        await db.Products.Where(spec.Criteria).ToListAsync();
}

// Combine specs without modifying either
public static class SpecificationExtensions
{
    public static ISpecification<T> And<T>(
        this ISpecification<T> left, ISpecification<T> right) =>
        new AndSpecification<T>(left, right);
}

public class AndSpecification<T>(ISpecification<T> left, ISpecification<T> right)
    : ISpecification<T>
{
    public Expression<Func<T, bool>> Criteria
    {
        get
        {
            // Combine two expressions: left.Criteria AND right.Criteria
            var param = Expression.Parameter(typeof(T), "x");
            var body  = Expression.AndAlso(
                Expression.Invoke(left.Criteria,  param),
                Expression.Invoke(right.Criteria, param));
            return Expression.Lambda<Func<T, bool>>(body, param);
        }
    }
}

// Usage — extend by composing, not by modifying
var repo = new ProductRepository(db);
var spec = new PriceAboveSpecification(100)
    .And(new CategorySpecification("Electronics"));

var results = await repo.FindAsync(spec);

record Product(int Id, string Name, decimal Price, string Category);
↥ back to top

Q. How does the Liskov Substitution Principle (LSP) apply to LINQ code?

LSP — a derived type must be substitutable for its base type. In LINQ: any IEnumerable<T> implementation (array, List<T>, EF Core IQueryable<T>) should be usable interchangeably in LINQ pipelines.

// … Methods accept IEnumerable<T> — substitutable with any collection type
public static IEnumerable<Product> FilterExpensive(
    IEnumerable<Product> products, decimal threshold) =>
    products.Where(p => p.Price > threshold);

// All of these are substitutable — no code change required
Product[] array      = [new("Laptop", 1200m), new("Pen", 2m)];
List<Product> list   = [new("Laptop", 1200m), new("Pen", 2m)];
IQueryable<Product> query = db.Products; // EF Core

var r1 = FilterExpensive(array, 100);   // array
var r2 = FilterExpensive(list, 100);    // List<T>
var r3 = FilterExpensive(query, 100);   // IQueryable<T> (executes as SQL via EF)

//  LSP violation — casting to concrete type breaks substitutability
public static List<Product> FilterBad(IEnumerable<Product> products, decimal t)
{
    var list = (List<Product>)products; // throws if array or IQueryable
    return list.Where(p => p.Price > t).ToList();
}

// … Custom IEnumerable<T> that behaves like a sequence
public class ProductCatalog : IEnumerable<Product>
{
    private readonly List<Product> _items = [];
    public void Add(Product p) => _items.Add(p);
    public IEnumerator<Product> GetEnumerator() => _items.GetEnumerator();
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() =>
        GetEnumerator();
}

var catalog = new ProductCatalog();
catalog.Add(new("Laptop", 1200m));
var expensive = FilterExpensive(catalog, 100); // substitutable — works!

record Product(string Name, decimal Price);
↥ back to top

Q. Can you give an example of using LINQ to implement the Interface Segregation Principle (ISP)?

ISP — clients should not be forced to depend on interfaces they don't use. Split large data-source interfaces into focused ones; LINQ queries program against only what they need.

//  Fat interface — query code must depend on write operations it doesn\'t use
public interface IProductRepository
{
    IQueryable<Product> Query();
    Task AddAsync(Product p);
    Task UpdateAsync(Product p);
    Task DeleteAsync(int id);
    Task SaveAsync();
}

// … Segregated interfaces
public interface IProductReader     { IQueryable<Product> Query(); }
public interface IProductWriter
{
    Task AddAsync(Product p);
    Task UpdateAsync(Product p);
    Task DeleteAsync(int id);
    Task SaveAsync();
}

// LINQ service depends only on the reader
public class ProductQueryService(IProductReader reader)
{
    public Task<List<Product>> GetExpensiveAsync(decimal min) =>
        reader.Query()
              .Where(p => p.Price > min)
              .AsNoTracking()
              .ToListAsync();

    public Task<Dictionary<string, decimal>> GetAverageByCategory() =>
        reader.Query()
              .AsNoTracking()
              .GroupBy(p => p.Category)
              .Select(g => new { g.Key, Avg = g.Average(p => p.Price) })
              .ToDictionaryAsync(x => x.Key, x => x.Avg);
}

// Write service depends only on the writer
public class ProductWriteService(IProductWriter writer)
{
    public Task CreateAsync(Product p) => writer.AddAsync(p);
}

// Concrete repository implements both (no ISP violation here)
public class ProductRepository(AppDbContext db) : IProductReader, IProductWriter
{
    public IQueryable<Product> Query() => db.Products;
    public async Task AddAsync(Product p) { db.Products.Add(p); await db.SaveChangesAsync(); }
    public async Task UpdateAsync(Product p) { db.Products.Update(p); await db.SaveChangesAsync(); }
    public async Task DeleteAsync(int id) { /* ... */ }
    public Task SaveAsync() => db.SaveChangesAsync();
}

record Product(int Id, string Name, decimal Price, string Category);
↥ back to top

Q. Can you explain the Dependency Inversion Principle (DIP) and how it relates to LINQ code?

DIP — high-level modules should depend on abstractions, not concrete implementations. In LINQ: depend on IEnumerable<T> / IQueryable<T> abstractions, not on List<T>, DbSet<T>, or SQL.

//  Violates DIP — high-level class depends on concrete EF Core DbSet
public class ReportService(AppDbContext db)
{
    public List<string> GetTopProductNames(int count) =>
        db.Products                    // concrete EF Core dependency
            .OrderByDescending(p => p.Price)
            .Take(count)
            .Select(p => p.Name)
            .ToList();
}

// … DIP — depend on abstraction (IQueryable<T> or IProductReader)
public interface IProductReader
{
    IQueryable<Product> Query();
}

public class ReportService(IProductReader reader)  // depends on abstraction
{
    public async Task<List<string>> GetTopProductNamesAsync(int count) =>
        await reader.Query()
            .OrderByDescending(p => p.Price)
            .Take(count)
            .Select(p => p.Name)
            .ToListAsync();
}

// Production implementation — EF Core
public class EfProductReader(AppDbContext db) : IProductReader
{
    public IQueryable<Product> Query() => db.Products;
}

// Test implementation — in-memory
public class FakeProductReader(IEnumerable<Product> products) : IProductReader
{
    public IQueryable<Product> Query() => products.AsQueryable();
}

// Composition root (Program.cs)
builder.Services.AddScoped<IProductReader, EfProductReader>();
builder.Services.AddScoped<ReportService>();

// Unit test — no database needed
var fakeReader = new FakeProductReader(
[
    new(1, "Laptop", 1200m),
    new(2, "Phone",   800m),
    new(3, "Pen",       2m),
]);
var service = new ReportService(fakeReader);
var top2 = await service.GetTopProductNamesAsync(2);
// ["Laptop", "Phone"] — fully testable, no DB required

record Product(int Id, string Name, decimal Price);
↥ back to top

Q. Explain the difference between Select and Where?

  Where Select
Purpose Filter — removes elements Project — transforms elements
Output count input count = input count
Element type Same T Can change to any TResult
Predicate Func<T, bool> Func<T, TResult>
var products = new List<Product>
{
    new("Laptop",  1200m, "Electronics"),
    new("Phone",    800m, "Electronics"),
    new("Notebook",  10m, "Stationery"),
};

// Where — filter (keeps same type, reduces count)
var electronics = products.Where(p => p.Category == "Electronics");
// [Laptop, Phone]  — still Product objects, count: 2

// Select — project (transforms type, same count)
var names = products.Select(p => p.Name);
// ["Laptop", "Phone", "Notebook"] — string objects, count: 3

// Select into a different type
var dtos = products.Select(p => new { p.Name, Discounted = p.Price * 0.9m });
// [{Laptop, 1080}, {Phone, 720}, {Notebook, 9}]

// Typical pattern: Where first, then Select (filter then project)
var expensiveNames = products
    .Where(p => p.Price > 100)       // 2 elements remain
    .Select(p => p.Name.ToUpper());  // ["LAPTOP", "PHONE"]

// Select does NOT filter — null projection requires Where
var allMaybeNull = products.Select(p => p.Price > 100 ? p.Name : null);
// ["Laptop", "Phone", null] — 3 elements, one null

// Filter nulls with Where
var filtered = allMaybeNull.Where(n => n is not null);

record Product(string Name, decimal Price, string Category);
↥ back to top

Q. What is the difference between IEnumerable and IQueryable?

  IEnumerable<T> IQueryable<T>
Namespace System.Collections.Generic System.Linq
Execution In-process (CLR) Translated to provider query (SQL, etc.)
Expression Delegate (Func<T, bool>) Expression tree (Expression<Func<T, bool>>)
Data pulled All data from source, then filtered in memory Only filtered data returned from DB
Use case In-memory collections EF Core, ORMs, remote data sources
Extends IEnumerable IEnumerable<T> + IQueryable
// IEnumerable — in-memory filtering (pulls all rows first)
IEnumerable<Product> memProducts = db.Products.ToList(); // ALL rows loaded
var cheap = memProducts.Where(p => p.Price < 100);       // filtered in CLR
// SQL: SELECT * FROM Products  (no WHERE clause)

// IQueryable — DB-side filtering (only matching rows returned)
IQueryable<Product> dbProducts = db.Products;            // no SQL yet
var cheapQ = dbProducts.Where(p => p.Price < 100);       // builds expression tree
var result = await cheapQ.ToListAsync();                  // NOW executes SQL
// SQL: SELECT * FROM Products WHERE Price < 100

// Practical impact on performance
// Table with 1M rows, 10 match filter:
// IEnumerable: loads 1M rows ’ filters ’ 10 objects
// IQueryable:  DB filters ’ loads only 10 rows

// The Where predicate is different internally
IEnumerable<Product> e = [new("Laptop", 1200m)];
// Takes Func<Product, bool> — a compiled delegate
e.Where(p => p.Price > 100);

IQueryable<Product> q = e.AsQueryable();
// Takes Expression<Func<Product, bool>> — an expression tree
q.Where(p => p.Price > 100); // can be inspected and translated to SQL

// AsEnumerable — switch from IQueryable to IEnumerable mid-pipeline
// Useful when the final transform can\'t be translated to SQL
var data = await db.Products
    .Where(p => p.Price > 100)      // SQL WHERE
    .AsEnumerable()                 // switch to in-memory
    .Select(p => new { p.Name, Tag = FormatTag(p) }) // CLR method, no SQL translation needed
    .ToListAsync();                 //  ToListAsync only on IQueryable; use ToList() here

var data2 = db.Products
    .Where(p => p.Price > 100)      // SQL WHERE
    .AsEnumerable()
    .Select(p => new { p.Name, Tag = FormatTag(p) })
    .ToList(); // …

static string FormatTag(Product p) => $"[{p.Name}]";
record Product(string Name, decimal Price);
↥ back to top

Q. How do you use SelectMany in LINQ to flatten nested collections?

SelectMany projects each element to an IEnumerable<T> and then flattens all those sequences into a single flat sequence. It is the LINQ equivalent of a flatMap in other languages.

// ” 1. Basic flatten ”————————————————————————————————————————————
var departments = new[]
{
    new { Name = "Engineering", Employees = new[] { "Alice", "Bob", "Carol" } },
    new { Name = "Design",      Employees = new[] { "Dave", "Eve" } },
    new { Name = "Marketing",   Employees = new[] { "Frank" } },
};

// Without SelectMany — nested loops required
IEnumerable<string> allEmployeesNested =
    departments.SelectMany(d => d.Employees);

Console.WriteLine(string.Join(", ", allEmployeesNested));
// Alice, Bob, Carol, Dave, Eve, Frank

// ” 2. With result selector — access both parent and child ”———————
var withDept = departments.SelectMany(
    d => d.Employees,
    (dept, emp) => $"{emp} ({dept.Name})");

Console.WriteLine(string.Join(", ", withDept));
// Alice (Engineering), Bob (Engineering), Carol (Engineering), Dave (Design), ...

// ” 3. Flatten a list of lists ”——————————————————————————————————
var matrix = new List<List<int>>
{
    [1, 2, 3],
    [4, 5],
    [6, 7, 8, 9],
};

List<int> flat = matrix.SelectMany(row => row).ToList();
Console.WriteLine(string.Join(", ", flat));  // 1, 2, 3, 4, 5, 6, 7, 8, 9

// ” 4. Flatten with filtering ”———————————————————————————————————
var orders = new[]
{
    new { Id = 1, Items = new[] { "Widget", "Gadget", "Widget" } },
    new { Id = 2, Items = new[] { "Gizmo" } },
    new { Id = 3, Items = new[] { "Widget", "Doohickey" } },
};

var widgetOrderIds = orders
    .Where(o => o.Items.Contains("Widget"))
    .Select(o => o.Id);
Console.WriteLine(string.Join(", ", widgetOrderIds));  // 1, 3

// All distinct items ever ordered
var distinctItems = orders
    .SelectMany(o => o.Items)
    .Distinct()
    .OrderBy(i => i);
Console.WriteLine(string.Join(", ", distinctItems));  // Doohickey, Gadget, Gizmo, Widget

// ” 5. Query syntax equivalent ”——————————————————————————————————
var queryResult =
    from d in departments
    from emp in d.Employees         // second `from` = SelectMany
    where emp.StartsWith('A') || emp.StartsWith('E')
    select $"{emp}{d.Name}";

foreach (var r in queryResult)
    Console.WriteLine(r);
// Alice — Engineering
// Eve — Design

// ” 6. String as char sequence (practical) ”——————————————————————
string[] words = ["hello", "world"];
char[] allChars = words.SelectMany(w => w).Distinct().OrderBy(c => c).ToArray();
Console.WriteLine(new string(allChars));  // dehlorw

// ” 7. Cross join (Cartesian product) ”——————————————————————————
var colors  = new[] { "Red", "Blue" };
var sizes   = new[] { "S", "M", "L" };

var variants = colors.SelectMany(
    _ => sizes,
    (color, size) => $"{color}-{size}");

Console.WriteLine(string.Join(", ", variants));
// Red-S, Red-M, Red-L, Blue-S, Blue-M, Blue-L

Select vs SelectMany:

Aspect Select SelectMany
Input T T
Output per element Single TResult IEnumerable<TResult>
Result shape Same count, possibly nested Flattened single sequence
Use case Transform 1-to-1 Flatten 1-to-many
↥ back to top

Q. What are LINQ set operators (Union, Intersect, Except, Distinct) and how are they used?

LINQ set operators work on sequences the same way mathematical sets work — they compare elements for equality and produce results without duplicates (unless using the €By or WithComparer overloads).

int[] a = [1, 2, 3, 4, 5];
int[] b = [3, 4, 5, 6, 7];

// ” Distinct — remove duplicates ”————————————————————————————————
int[] withDups = [1, 2, 2, 3, 3, 3, 4];
int[] unique = withDups.Distinct().ToArray();
Console.WriteLine(string.Join(", ", unique));   // 1, 2, 3, 4

// ” Union — all elements from both, no duplicates ”———————————————
int[] union = a.Union(b).ToArray();
Console.WriteLine(string.Join(", ", union));    // 1, 2, 3, 4, 5, 6, 7

// ” Intersect — only elements in BOTH ”———————————————————————————
int[] intersect = a.Intersect(b).ToArray();
Console.WriteLine(string.Join(", ", intersect)); // 3, 4, 5

// ” Except — elements in a but NOT in b (set difference) ”————————
int[] except = a.Except(b).ToArray();
Console.WriteLine(string.Join(", ", except));    // 1, 2

// Reverse — elements in b but not a
int[] exceptReverse = b.Except(a).ToArray();
Console.WriteLine(string.Join(", ", exceptReverse)); // 6, 7

// ” DistinctBy / UnionBy / IntersectBy / ExceptBy (.NET 6+) ”————
record Person(string Name, int DeptId);

var team1 = new[]
{
    new Person("Alice", 1), new Person("Bob", 2), new Person("Carol", 1),
};
var team2 = new[]
{
    new Person("Dave", 1), new Person("Alice", 3), new Person("Eve", 2),
};

// UnionBy — merge teams, deduplicate by Name
var merged = team1.UnionBy(team2, p => p.Name);
Console.WriteLine(string.Join(", ", merged.Select(p => p.Name)));
// Alice, Bob, Carol, Dave, Eve

// IntersectBy — people in BOTH teams (by name)
var inBoth = team1.IntersectBy(team2.Select(p => p.Name), p => p.Name);
Console.WriteLine(string.Join(", ", inBoth.Select(p => p.Name)));  // Alice

// ExceptBy — people only in team1 (not in team2, by name)
var onlyTeam1 = team1.ExceptBy(team2.Select(p => p.Name), p => p.Name);
Console.WriteLine(string.Join(", ", onlyTeam1.Select(p => p.Name))); // Bob, Carol

// DistinctBy — one person per department (first occurrence wins)
var onePerDept = team1.DistinctBy(p => p.DeptId);
Console.WriteLine(string.Join(", ", onePerDept.Select(p => p.Name))); // Alice, Bob

// ” Custom equality comparer ”————————————————————————————————————
class CaseInsensitiveComparer : IEqualityComparer<string>
{
    public bool Equals(string? x, string? y)
        => string.Equals(x, y, StringComparison.OrdinalIgnoreCase);
    public int GetHashCode(string obj) => obj.ToLowerInvariant().GetHashCode();
}

string[] words1 = ["apple", "Banana", "cherry"];
string[] words2 = ["APPLE", "Date", "Cherry"];

var caseInsensitiveUnion = words1.Union(words2, new CaseInsensitiveComparer());
Console.WriteLine(string.Join(", ", caseInsensitiveUnion));
// apple, Banana, cherry, Date

Set operator summary:

Operator Returns Description
Distinct() IEnumerable<T> Unique elements from one sequence
DistinctBy(key) IEnumerable<T> Unique by key selector (.NET 6+)
Union(b) IEnumerable<T> All unique elements from a and b
Intersect(b) IEnumerable<T> Elements in both a and b
Except(b) IEnumerable<T> Elements in a but not b
UnionBy/IntersectBy/ExceptBy IEnumerable<T> Keyed versions (.NET 6+)
↥ back to top

Q. What are LINQ partitioning and element operators in C#?

Partitioning operators (Take, Skip, TakeWhile, SkipWhile, Chunk) split a sequence into parts. Element operators (First, Last, Single, ElementAt, Any, All, Count) retrieve or test individual elements.

int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// PARTITIONING
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••

// Take — first N elements
Console.WriteLine(string.Join(", ", numbers.Take(3)));          // 1, 2, 3

// Skip — skip first N elements
Console.WriteLine(string.Join(", ", numbers.Skip(7)));          // 8, 9, 10

// Take + Skip — paging
int pageSize = 3, page = 2;
var paged = numbers.Skip((page - 1) * pageSize).Take(pageSize);
Console.WriteLine(string.Join(", ", paged));                    // 4, 5, 6

// TakeWhile — take while condition is true (stops at first false)
Console.WriteLine(string.Join(", ", numbers.TakeWhile(n => n < 5)));  // 1, 2, 3, 4

// SkipWhile — skip while condition is true, then take the rest
Console.WriteLine(string.Join(", ", numbers.SkipWhile(n => n < 5)));  // 5, 6, 7, 8, 9, 10

// TakeLast / SkipLast (.NET Core 2.0+)
Console.WriteLine(string.Join(", ", numbers.TakeLast(3)));      // 8, 9, 10
Console.WriteLine(string.Join(", ", numbers.SkipLast(3)));      // 1, 2, 3, 4, 5, 6, 7

// Chunk — split into fixed-size batches (.NET 6+)
foreach (int[] chunk in numbers.Chunk(3))
    Console.WriteLine(string.Join(", ", chunk));
// 1, 2, 3
// 4, 5, 6
// 7, 8, 9
// 10

// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// ELEMENT OPERATORS
// ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
int[] evens = numbers.Where(n => n % 2 == 0).ToArray();   // [2,4,6,8,10]
int[] empty = [];

// First / Last — throw if sequence is empty
Console.WriteLine(evens.First());                    // 2
Console.WriteLine(evens.Last());                     // 10
Console.WriteLine(evens.First(n => n > 6));          // 8

// FirstOrDefault / LastOrDefault — return default(T) if empty
Console.WriteLine(empty.FirstOrDefault());           // 0 (int default)
Console.WriteLine(empty.FirstOrDefault(-1));         // -1 (custom default, .NET 6+)
Console.WriteLine(evens.FirstOrDefault(n => n > 100, -99)); // -99

// Single — exactly one element; throws if 0 or >1
Console.WriteLine(evens.Single(n => n == 6));        // 6
// evens.Single()   throws — more than one element

// SingleOrDefault — 0 or 1 elements; throws if >1
Console.WriteLine(evens.SingleOrDefault(n => n == 5));  // 0 (not found)
Console.WriteLine(evens.SingleOrDefault(n => n == 5, -1)); // -1

// ElementAt / ElementAtOrDefault
Console.WriteLine(evens.ElementAt(2));               // 6
Console.WriteLine(evens.ElementAtOrDefault(99));     // 0 (out of range)

// ” Boolean aggregates ”——————————————————————————————————————————
Console.WriteLine(numbers.Any());                    // True (not empty)
Console.WriteLine(empty.Any());                      // False
Console.WriteLine(numbers.Any(n => n > 9));          // True

Console.WriteLine(numbers.All(n => n > 0));          // True
Console.WriteLine(numbers.All(n => n > 5));          // False

Console.WriteLine(numbers.Contains(7));              // True

// ” Count / LongCount ”———————————————————————————————————————————
Console.WriteLine(numbers.Count());                  // 10
Console.WriteLine(numbers.Count(n => n % 3 == 0));  // 3  (3, 6, 9)
Console.WriteLine(numbers.LongCount());              // 10L

// ” Min, Max, Sum, Average ”——————————————————————————————————————
Console.WriteLine(numbers.Min());    // 1
Console.WriteLine(numbers.Max());    // 10
Console.WriteLine(numbers.Sum());    // 55
Console.WriteLine(numbers.Average()); // 5.5

// ” MinBy / MaxBy (.NET 6+) ”—————————————————————————————————————
record Product2(string Name, decimal Price);
var products = new[] { new Product2("A", 5m), new Product2("B", 2m), new Product2("C", 8m) };
Console.WriteLine(products.MinBy(p => p.Price)?.Name);  // B
Console.WriteLine(products.MaxBy(p => p.Price)?.Name);  // C

Operator behaviour on empty sequences:

Operator Empty sequence behaviour
First() Throws InvalidOperationException
FirstOrDefault() Returns default(T) or custom default
Single() Throws InvalidOperationException
SingleOrDefault() Returns default(T) or custom default
Last() Throws InvalidOperationException
Any() Returns false
Count() Returns 0
Min() / Max() Throws on empty non-nullable
↥ back to top

# 14. MICROSERVICES


Q. What are microservices and why are they used?

Microservices is an architectural style where an application is composed of small, independently deployable services, each responsible for a specific business capability and communicating via APIs.

Monolith                         Microservices
””—————————————————————————      ””———————————  ””———————————  ””———————————
”  UI + Business + Data   ”      ”  Order    ”  ”  Catalog  ”  ”  Payment  ”
”  (all in one process)   ”  ’   ”  Service  ”  ”  Service  ”  ”  Service  ”
”””—————————————————————————      ”””———————————  ”””———————————  ”””———————————
                                                                   
                                 Each has its own DB, deploy, scale, team

Why use microservices?

Benefit Detail
Independent deployment Deploy Order Service without touching Payment Service
Independent scaling Scale only the Catalog Service during a sale
Technology diversity Each service can use a different language/DB
Fault isolation Catalog failure doesn't bring down Orders
Team autonomy Small teams own end-to-end services
Faster release cycles Smaller, focused deployments

When NOT to use microservices:

↥ back to top

Q. How do you implement microservices in .NET Core?

Each microservice is a separate ASP.NET Core Web API project with its own database, deployed independently as a Docker container.

// 1. Create a minimal microservice (OrderService)
// dotnet new webapi -n OrderService

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<OrderDbContext>(opt =>
    opt.UseNpgsql(builder.Configuration.GetConnectionString("Orders")));
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<OrderService>();

// Register HttpClient for inter-service calls
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>(client =>
    client.BaseAddress = new Uri(builder.Configuration["Services:Catalog"]!));

var app = builder.Build();
app.MapOrderEndpoints(); // feature-sliced minimal API endpoints
app.Run();

// 2. Typed HttpClient for service-to-service communication
public class CatalogClient(HttpClient client)
{
    public async Task<CatalogItem?> GetItemAsync(int id, CancellationToken ct = default)
    {
        var response = await client.GetAsync($"/api/catalog/{id}", ct);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<CatalogItem>(cancellationToken: ct);
    }
}

// 3. Simple endpoint
app.MapPost("/orders", async (CreateOrderRequest req, OrderService svc, CancellationToken ct) =>
{
    var order = await svc.CreateAsync(req, ct);
    return Results.Created($"/orders/{order.Id}", order);
});

// 4. Health checks — required for Kubernetes probes
builder.Services.AddHealthChecks()
    .AddNpgSql(connStr, name: "database")
    .AddUrlGroup(new Uri("http://catalog-service/health"), name: "catalog");

app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready", new() { Predicate = r => r.Tags.Contains("ready") });
↥ back to top

Q. What are the main advantages of using microservices architecture?

Advantage Description
Independent scaling Scale only bottleneck services
Independent deployment Deploy/rollback one service without affecting others
Technology flexibility Python ML model + C# API + Go service in same system
Fault isolation Circuit breakers prevent cascade failures
Team autonomy Teams own and deploy their service end-to-end
Smaller codebases Easier to understand, test, and onboard
Faster iteration Frequent small releases without full-system regression
Horizontal scalability Run 10 instances of Order Service, 2 of Admin
Example: E-Commerce Platform

””—————————————   ””——————————————   ””—————————————   ””—————————————
”  API Gateway ””–” Order Service””–”Catalog Svc  ”   ”Payment Svc  ”
” (YARP/Ocelot)”   ”  (C# + PG)  ”   ”(C# + PG)    ”   ”(C# + Redis) ”
”””—————————————   ”””——————————————   ”””—————————————   ”””—————————————
                         ”                                      ”
                  ””————–”——————                    ””———————–”——————
                  ” RabbitMQ /  ”                    ”  Notification  ”
                  ” Azure SB    ””————————————————–”  Service       ”
                  ”””—————————————                    ”””————————————————

Each service:
 - Owns its database (no shared DB)
 - Has its own Docker image
 - Has its own CI/CD pipeline
 - Scales independently in Kubernetes
↥ back to top

Q. How do you handle communication between microservices in .NET Core?

Synchronous (request-response): HTTP/REST, gRPC Asynchronous (event-driven): message queues (RabbitMQ, Azure Service Bus, Kafka)

// 1. HTTP REST — typed HttpClient via IHttpClientFactory
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>(client =>
    client.BaseAddress = new Uri("http://catalog-service"));

public class CatalogClient(HttpClient client)
{
    public Task<Product?> GetProductAsync(int id) =>
        client.GetFromJsonAsync<Product>($"/products/{id}");
}

// 2. gRPC — binary protocol, strongly-typed contracts (.proto files)
// dotnet add package Grpc.AspNetCore
// Service: catalog.proto ’ generated CatalogService.CatalogServiceClient

builder.Services.AddGrpcClient<CatalogService.CatalogServiceClient>(opts =>
    opts.Address = new Uri("https://catalog-service:5001"));

public class OrderService(CatalogService.CatalogServiceClient grpcClient)
{
    public async Task<ProductInfo> GetProductInfoAsync(int id)
    {
        var reply = await grpcClient.GetProductAsync(new ProductRequest { Id = id });
        return new ProductInfo(reply.Name, reply.Price);
    }
}

// 3. Async messaging — MassTransit + RabbitMQ
// dotnet add package MassTransit.RabbitMQ
builder.Services.AddMassTransit(x =>
{
    x.AddConsumer<OrderCreatedConsumer>();
    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host("rabbitmq://localhost");
        cfg.ConfigureEndpoints(ctx);
    });
});

// Publisher
public class OrderService(IPublishEndpoint publish)
{
    public async Task CreateOrderAsync(CreateOrderRequest req)
    {
        // ... create order in DB ...
        await publish.Publish(new OrderCreated(orderId, req.Items));
    }
}

// Consumer in another service
public class OrderCreatedConsumer : IConsumer<OrderCreated>
{
    public async Task Consume(ConsumeContext<OrderCreated> ctx)
    {
        var msg = ctx.Message;
        // process the event (e.g., send confirmation email)
    }
}

record OrderCreated(int OrderId, List<OrderItem> Items);
↥ back to top

Q. What is the role of API Gateway in microservices architecture?

The API Gateway is the single entry point for all clients. It handles routing, authentication, rate limiting, SSL termination, and request aggregation — preventing clients from knowing about individual services.

Client (React App / Mobile)
          ”
          –
  ””———————————————
  ”  API Gateway  ”   YARP / Ocelot / Azure API Management
  ”               ”    - Route /orders ’ OrderService
  ”  Auth (JWT)   ”    - Route /catalog ’ CatalogService
  ”  Rate Limit   ”    - Aggregate /dashboard ’ multiple services
  ”  Load Balance ”    - Strip/add headers
  ”””———————————————
     /      |      \
Order   Catalog  Payment
Service Service  Service
// YARP (Yet Another Reverse Proxy) — Microsoft\'s API Gateway (.NET 10)
// dotnet add package Yarp.ReverseProxy

builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

app.MapReverseProxy();

// appsettings.json
{
  "ReverseProxy": {
    "Routes": {
      "orders-route": {
        "ClusterId": "orders-cluster",
        "Match": { "Path": "/api/orders/{**catch-all}" },
        "AuthorizationPolicy": "default"
      },
      "catalog-route": {
        "ClusterId": "catalog-cluster",
        "Match": { "Path": "/api/catalog/{**catch-all}" }
      }
    },
    "Clusters": {
      "orders-cluster": {
        "Destinations": {
          "primary": { "Address": "http://order-service:8080/" }
        }
      },
      "catalog-cluster": {
        "Destinations": {
          "primary":   { "Address": "http://catalog-service:8080/" },
          "secondary": { "Address": "http://catalog-service-2:8080/" }
        },
        "LoadBalancingPolicy": "RoundRobin"
      }
    }
  }
}

// Add rate limiting
builder.Services.AddRateLimiter(opts =>
    opts.AddFixedWindowLimiter("api", o =>
    {
        o.PermitLimit = 100;
        o.Window = TimeSpan.FromMinutes(1);
    }));
app.UseRateLimiter();
↥ back to top

Q. How do you manage data consistency in microservices?

Each microservice owns its database — no shared DB. Consistency is maintained through eventual consistency patterns.

// 1. Saga Pattern (Choreography) — services react to events
// OrderService publishes ’ InventoryService and PaymentService consume

// OrderService
await publishEndpoint.Publish(new OrderPlaced(orderId, customerId, items));

// InventoryService consumer
public class OrderPlacedConsumer : IConsumer<OrderPlaced>
{
    public async Task Consume(ConsumeContext<OrderPlaced> ctx)
    {
        var reserved = await inventory.ReserveAsync(ctx.Message.Items);
        if (reserved)
            await ctx.Publish(new InventoryReserved(ctx.Message.OrderId));
        else
            await ctx.Publish(new InventoryFailed(ctx.Message.OrderId));
    }
}

// 2. Saga Pattern (Orchestration) — MassTransit StateMachine
public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
    public OrderStateMachine()
    {
        Initially(
            When(OrderPlacedEvent)
                .Activity(x => x.OfInstanceType<ReserveInventoryActivity>())
                .TransitionTo(AwaitingInventory));

        During(AwaitingInventory,
            When(InventoryReservedEvent)
                .Activity(x => x.OfInstanceType<ChargePaymentActivity>())
                .TransitionTo(AwaitingPayment),
            When(InventoryFailedEvent)
                .TransitionTo(Cancelled));
    }

    public State AwaitingInventory { get; private set; } = default!;
    public State AwaitingPayment   { get; private set; } = default!;
    public State Cancelled         { get; private set; } = default!;
    public Event<OrderPlaced>         OrderPlacedEvent         { get; private set; } = default!;
    public Event<InventoryReserved>   InventoryReservedEvent   { get; private set; } = default!;
    public Event<InventoryFailed>     InventoryFailedEvent     { get; private set; } = default!;
}

// 3. Outbox Pattern — guarantee event delivery even if service crashes
// Store event in DB (same transaction as business data), then publish
await using var tx = await db.Database.BeginTransactionAsync();
db.Orders.Add(newOrder);
db.OutboxMessages.Add(new OutboxMessage(
    nameof(OrderCreated),
    JsonSerializer.Serialize(new OrderCreated(newOrder.Id))));
await db.SaveChangesAsync(); // atomic: order + outbox message
await tx.CommitAsync();
// Background worker reads outbox and publishes to message bus
↥ back to top

Q. What are some common challenges when working with microservices?

Challenge Description Solution
Distributed tracing Requests span multiple services OpenTelemetry + Jaeger/Zipkin
Data consistency No shared DB, eventual consistency Saga pattern, outbox
Network failures Inter-service calls can fail Retry, circuit breaker (Polly)
Service discovery Services need to find each other Kubernetes DNS, Consul
Testing complexity Integration tests across services Contract testing (Pact), test containers
Security JWT propagation, mTLS JWT forwarding, service mesh
Configuration Many services, many configs Kubernetes ConfigMaps, Azure App Config
Versioning API changes break consumers Versioned APIs, backward compat
Operational overhead Many deployments to manage Kubernetes, Helm, GitOps
// Polly — resilience library for network failures
// dotnet add package Microsoft.Extensions.Http.Resilience (.NET 8+)

builder.Services.AddHttpClient<ICatalogClient, CatalogClient>()
    .AddStandardResilienceHandler(opts =>
    {
        opts.Retry.MaxRetryAttempts = 3;
        opts.Retry.Delay = TimeSpan.FromMilliseconds(200);
        opts.Retry.BackoffType = DelayBackoffType.Exponential;
        opts.CircuitBreaker.FailureRatio = 0.5;
        opts.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(10);
        opts.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(30);
    });

// OpenTelemetry — distributed tracing across services
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddEntityFrameworkCoreInstrumentation()
        .AddOtlpExporter(opts => opts.Endpoint = new Uri("http://jaeger:4317")));
↥ back to top

Q. How do you deploy microservices in a containerized environment?

# Dockerfile — multi-stage build for OrderService
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["OrderService/OrderService.csproj", "OrderService/"]
RUN dotnet restore "OrderService/OrderService.csproj"
COPY . .
RUN dotnet publish "OrderService/OrderService.csproj" -c Release -o /app/publish \
    --no-restore /p:UseAppHost=false

FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "OrderService.dll"]
# Kubernetes deployment — order-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: myregistry.azurecr.io/order-service:1.2.0
        ports:
        - containerPort: 8080
        env:
        - name: ConnectionStrings__Orders
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: orders-conn-string
        resources:
          requests: { cpu: "100m", memory: "128Mi" }
          limits:   { cpu: "500m", memory: "512Mi" }
        livenessProbe:
          httpGet: { path: /health, port: 8080 }
          initialDelaySeconds: 10
        readinessProbe:
          httpGet: { path: /health/ready, port: 8080 }
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
  - port: 80
    targetPort: 8080
# Deploy
kubectl apply -f order-service.yaml

# Rolling update to new version
kubectl set image deployment/order-service order-service=myregistry.azurecr.io/order-service:1.3.0

# Scale
kubectl scale deployment order-service --replicas=5
↥ back to top

Q. What is the purpose of service discovery in microservices?

Service discovery allows microservices to find each other's network locations dynamically — without hardcoded IP addresses — as services scale, restart, or move.

Without service discovery:
  OrderService ’ "http://192.168.1.42:8080" (hardcoded — breaks on redeploy)

With service discovery:
  OrderService ’ "http://catalog-service" ’ Discovery resolves ’ "http://10.0.0.15:8080"
Approach Tools .NET Integration
Kubernetes DNS K8s built-in http://catalog-service resolves via kube-dns
Consul HashiCorp Consul Steeltoe.Discovery.Consul
Eureka Netflix Eureka Steeltoe.Discovery.Eureka
Azure Service Fabric Service Fabric DNS Built-in naming service
// Kubernetes — simplest; use service name as hostname
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>(client =>
{
    // K8s DNS resolves "catalog-service" to the ClusterIP
    client.BaseAddress = new Uri(
        builder.Configuration["Services:Catalog"] ?? "http://catalog-service");
});

// Consul service discovery (Steeltoe)
// dotnet add package Steeltoe.Discovery.Consul

builder.Services.AddServiceDiscovery(b => b.UseConsul());
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>()
    .AddServiceDiscovery(); // resolves "catalog-service" via Consul

// appsettings.json
{
  "Consul": { "Host": "consul-server", "Port": 8500 },
  "Spring": {
    "Application": { "Name": "order-service" },
    "Cloud": { "Discovery": { "Enabled": true } }
  }
}
↥ back to top

Q. How do you implement logging and monitoring in microservices?

// 1. Structured logging with Serilog — correlate across services
// dotnet add package Serilog.AspNetCore Serilog.Sinks.OpenTelemetry

builder.Host.UseSerilog((ctx, cfg) => cfg
    .ReadFrom.Configuration(ctx.Configuration)
    .Enrich.FromLogContext()
    .Enrich.WithProperty("Service", "OrderService")
    .Enrich.WithProperty("Environment", ctx.HostingEnvironment.EnvironmentName)
    .WriteTo.Console(new JsonFormatter())
    .WriteTo.OpenTelemetry(opts =>
        opts.Endpoint = "http://otel-collector:4317"));

// Use correlation ID middleware
app.Use(async (ctx, next) =>
{
    var correlationId = ctx.Request.Headers["X-Correlation-ID"].FirstOrDefault()
                        ?? Guid.NewGuid().ToString();
    ctx.Response.Headers["X-Correlation-ID"] = correlationId;
    using (Serilog.Context.LogContext.PushProperty("CorrelationId", correlationId))
        await next(ctx);
});

// 2. OpenTelemetry — metrics + tracing + logs
builder.Services.AddOpenTelemetry()
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddSource("OrderService")
        .AddOtlpExporter(o => o.Endpoint = new Uri("http://otel-collector:4317")))
    .WithMetrics(m => m
        .AddAspNetCoreInstrumentation()
        .AddRuntimeInstrumentation()
        .AddOtlpExporter());

// 3. Health checks with detailed status
builder.Services.AddHealthChecks()
    .AddNpgSql(connStr)
    .AddRabbitMQ(rabbitUri)
    .AddCheck("self", () => HealthCheckResult.Healthy());

app.MapHealthChecks("/health/live",  new() { Predicate = _ => false });  // liveness
app.MapHealthChecks("/health/ready", new() { Predicate = _ => true  });  // readiness

// 4. Custom metrics
var meter = new System.Diagnostics.Metrics.Meter("OrderService");
var ordersCreated = meter.CreateCounter<long>("orders.created");
ordersCreated.Add(1, new("status", "success"));

// 5. Propagate trace context across HTTP calls (automatic with HttpClient instrumentation)
// X-B3-TraceId / traceparent headers forwarded automatically
↥ back to top

Q. What is the difference between monolithic and microservices architecture?

Aspect Monolith Microservices
Codebase Single deployable unit Multiple independent services
Deployment Deploy entire app for any change Deploy only changed service
Scaling Scale the whole app Scale individual services
Technology Single stack Polyglot — each service chooses
Data Single shared database Each service owns its DB
Failure One bug can crash everything Failures isolated per service
Complexity Simple locally, hard to scale Complex ops, easy to scale
Team size Small-medium teams Large orgs, multiple teams
Testing Simpler end-to-end Complex distributed testing
Latency In-process calls (fast) Network calls (slower)
When to choose what:

Monolith …                    Microservices …
”———————————————              ”———————————————————————————————
Early-stage startup            Large org with multiple teams
Small team (<10 devs)          High scale requirements
Unclear domain boundaries      Well-understood bounded contexts
Simple operational needs       Independent release cadence needed
Proof of concept               Different scaling needs per component

Migration path:
Monolith ’ Strangler Fig Pattern ’ Microservices
  1. Identify bounded context (e.g., Payment)
  2. Wrap it behind an interface
  3. Extract to separate service behind API Gateway
  4. Route requests to new service
  5. Remove from monolith
↥ back to top

Q. How do you handle security in microservices?

// 1. JWT authentication — validate in each service
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(opts =>
    {
        opts.Authority = "https://identity-server"; // OIDC discovery
        opts.Audience  = "order-service";
        opts.TokenValidationParameters = new()
        {
            ValidateIssuer   = true,
            ValidateAudience = true,
            ValidateLifetime = true,
        };
    });

builder.Services.AddAuthorization(opts =>
    opts.AddPolicy("orders:write", p => p.RequireClaim("scope", "orders:write")));

app.UseAuthentication();
app.UseAuthorization();

app.MapPost("/orders", CreateOrder).RequireAuthorization("orders:write");

// 2. Forward JWT between services (propagate identity)
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>()
    .AddHttpMessageHandler<JwtForwardingHandler>();

public class JwtForwardingHandler(IHttpContextAccessor accessor) : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken ct)
    {
        var token = accessor.HttpContext?.Request.Headers.Authorization.ToString();
        if (!string.IsNullOrEmpty(token))
            request.Headers.Authorization =
                System.Net.Http.Headers.AuthenticationHeaderValue.Parse(token);
        return base.SendAsync(request, ct);
    }
}

// 3. mTLS — mutual TLS for service-to-service (via service mesh: Istio / Linkerd)
// Zero-code change; sidecar proxy handles certificate verification

// 4. Secrets management — never store secrets in code
// Kubernetes secrets
var connStr = builder.Configuration["ConnectionStrings__Orders"]; // from K8s Secret
// Azure Key Vault
builder.Configuration.AddAzureKeyVault(new Uri("https://myvault.vault.azure.net/"),
    new DefaultAzureCredential());
↥ back to top

Q. What is the role of Docker in microservices?

Docker packages each microservice and its dependencies into an image — a portable, reproducible unit that runs consistently everywhere.

# Each microservice has its own Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .
EXPOSE 8080
ENTRYPOINT ["dotnet", "CatalogService.dll"]
# docker-compose.yml — run all services locally
version: "3.9"
services:
  api-gateway:
    build: ./ApiGateway
    ports: ["5000:8080"]
    depends_on: [order-service, catalog-service]

  order-service:
    build: ./OrderService
    environment:
      - ConnectionStrings__Orders=Host=postgres;Database=orders;Username=app;Password=secret
    depends_on: [postgres, rabbitmq]

  catalog-service:
    build: ./CatalogService
    depends_on: [postgres]

  postgres:
    image: postgres:16-alpine
    volumes: ["pgdata:/var/lib/postgresql/data"]
    environment:
      POSTGRES_PASSWORD: secret

  rabbitmq:
    image: rabbitmq:3-management
    ports: ["15672:15672"]

volumes:
  pgdata:
# Build and run all services
docker compose up --build

# Build a single image
docker build -t myregistry.azurecr.io/order-service:1.0.0 ./OrderService

# Push to registry
docker push myregistry.azurecr.io/order-service:1.0.0

# Run a single service
docker run -p 8080:8080 myregistry.azurecr.io/order-service:1.0.0
↥ back to top

Q. How do you implement resilience and fault tolerance in microservices?

// Microsoft.Extensions.Http.Resilience (.NET 8+ / Polly v8)
// dotnet add package Microsoft.Extensions.Http.Resilience

builder.Services.AddHttpClient<ICatalogClient, CatalogClient>()
    .AddStandardResilienceHandler(opts =>
    {
        // Retry — 3 times with exponential backoff + jitter
        opts.Retry.MaxRetryAttempts = 3;
        opts.Retry.Delay            = TimeSpan.FromMilliseconds(200);
        opts.Retry.BackoffType      = DelayBackoffType.Exponential;
        opts.Retry.UseJitter        = true;
        opts.Retry.ShouldHandle     = args =>
            ValueTask.FromResult(args.Outcome.Exception is HttpRequestException);

        // Circuit Breaker — open after 50% failure in 10-second window
        opts.CircuitBreaker.FailureRatio    = 0.5;
        opts.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(10);
        opts.CircuitBreaker.MinimumThroughput = 10;
        opts.CircuitBreaker.BreakDuration    = TimeSpan.FromSeconds(30);

        // Timeout per attempt
        opts.AttemptTimeout.Timeout = TimeSpan.FromSeconds(5);

        // Total timeout across all retries
        opts.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(30);
    });

// Fallback / graceful degradation
builder.Services.AddResiliencePipeline("catalog-fallback", builder =>
{
    builder.AddFallback(new FallbackStrategyOptions<Product?>
    {
        FallbackAction = _ => ValueTask.FromResult<Product?>(Product.Default),
        ShouldHandle   = args => ValueTask.FromResult(
            args.Outcome.Exception is BrokenCircuitException),
    });
});

// Bulkhead — limit concurrent requests to a service
builder.Services.AddResiliencePipeline("bulkhead", b =>
    b.AddConcurrencyLimiter(permitLimit: 10, queueLimit: 20));

// Health checks for circuit breaker status
builder.Services.AddHealthChecks()
    .AddCheck("catalog-circuit-breaker", () =>
        circuitBreakerState == CircuitState.Closed
            ? HealthCheckResult.Healthy()
            : HealthCheckResult.Degraded("Circuit breaker is open"));
↥ back to top

Q. What are some best practices for designing microservices?

Practice Description
Design around business domains Use Domain-Driven Design bounded contexts
Single responsibility Each service does one thing well
Own your data No shared databases between services
API-first Define contracts (OpenAPI/gRPC) before implementation
Async by default Prefer events over synchronous calls
Design for failure Retry, circuit breaker, fallback everywhere
Health checks Liveness + readiness probes
Structured logging Correlation IDs, JSON logs
Distributed tracing OpenTelemetry propagation
Versioned APIs Never break consumers
Small, frequent releases CI/CD per service
Automate everything Docker + Kubernetes + GitOps
// Checklist for a new microservice:

// … 1. Health endpoints
app.MapHealthChecks("/health/live");
app.MapHealthChecks("/health/ready");

// … 2. Structured logging with correlation
builder.Host.UseSerilog((ctx, cfg) => cfg
    .Enrich.FromLogContext()
    .WriteTo.Console(new JsonFormatter()));

// … 3. OpenTelemetry tracing
builder.Services.AddOpenTelemetry()
    .WithTracing(t => t.AddAspNetCoreInstrumentation().AddOtlpExporter());

// … 4. Resilient HTTP clients
builder.Services.AddHttpClient<IDownstreamClient, DownstreamClient>()
    .AddStandardResilienceHandler();

// … 5. Versioned API
app.MapGroup("/api/v1").MapOrderEndpoints();

// … 6. Graceful shutdown
app.Lifetime.ApplicationStopping.Register(() =>
    logger.LogInformation("Shutting down gracefully..."));
↥ back to top

Q. Name the key components of Microservices?

Key Components of a Microservices System:

””—————————————————————————————————————————————————————————————————————
”  CLIENT (Browser / Mobile / 3rd-party)                              ”
”””——————————————————————”——————————————————————————————————————————————
                       ”
              ””——————–”—————————
              ”   API Gateway    ”  Routing, Auth, Rate Limit, SSL
              ”  (YARP / Ocelot) ”
              ”””————————”—————————
          ””————————————”————————————
    ””———–”————— ””——–”———— ””——–”——————
    ”  Order    ” ”Catalog  ” ” Payment   ”    Individual Services
    ”  Service  ” ” Service ” ” Service   ”
    ”””—————”————— ”””————”———— ”””————”——————
          ”            ”           ”
    ””———–”——   ””———–”——  ””——–”———
    ” Orders ”   ”Products”  ”Payments”    Per-service Databases
    ”  DB    ”   ”   DB   ”  ”   DB   ”
    ”””————————   ”””————————  ”””————————
          ”            ”           ”
          ”””————————————”———————————
                  ””——–”—————
                  ” Message  ”    Async Communication (RabbitMQ / Kafka)
                  ”   Bus    ”
                  ”””——————————
                       ”
         ””—————————————”———————————————
   ””———–”——————              ””—————–”——————
   ” Notification”             ”  Audit / Log ”    Event Consumers
   ”  Service   ”             ”   Service    ”
   ”””————————————              ”””—————————————
Component Role
API Gateway Single entry point — routing, auth, rate limiting
Services Independent business capabilities
Service Registry Service discovery (Consul, K8s DNS)
Message Bus Async communication (RabbitMQ, Kafka, Azure Service Bus)
Configuration Server Centralised config (Azure App Config, Consul KV)
Identity Provider Authentication/authorisation (IdentityServer, Azure AD B2C)
Container Runtime Docker for packaging, Kubernetes for orchestration
Observability Logs (Serilog), Metrics (Prometheus), Traces (Jaeger)
CI/CD Pipeline Per-service build and deploy (GitHub Actions, Azure DevOps)
↥ back to top

Q. What are the tools commonly used tools for Microservices?

Category Tool Purpose
Service framework ASP.NET Core Minimal API Build HTTP microservices
API Gateway YARP, Ocelot, Azure APIM Routing, auth, rate limiting
gRPC Grpc.AspNetCore High-performance service-to-service
Messaging MassTransit + RabbitMQ Async pub/sub, saga orchestration
Messaging (cloud) Azure Service Bus, Amazon SQS Managed message queues
Streaming Apache Kafka, Azure Event Hubs High-throughput event streaming
Containers Docker, containerd Package and run services
Orchestration Kubernetes, Azure AKS Scale, deploy, manage containers
Service mesh Istio, Linkerd mTLS, traffic management, observability
Service discovery K8s DNS, Consul Locate services dynamically
Configuration Azure App Config, Consul KV Centralised config + feature flags
Identity IdentityServer, Azure AD B2C OAuth2/OIDC for authentication
Resilience Polly / M.E.Http.Resilience Retry, circuit breaker, timeout
Tracing OpenTelemetry + Jaeger/Zipkin Distributed request tracing
Metrics Prometheus + Grafana Dashboards and alerting
Logging Serilog + ELK / Azure Monitor Structured log aggregation
CI/CD GitHub Actions, Azure DevOps Automated build and deploy
Secrets Azure Key Vault, HashiCorp Vault Secrets management
Health ASP.NET Core Health Checks Liveness/readiness probes
↥ back to top

Q. What are the key principles to follow when designing microservices?

// 1. Single Responsibility — one service, one bounded context
// OrderService handles order lifecycle only; Catalog handles product info

// 2. Database per service — no shared DB
//  Shared DB creates coupling
// … Each service owns its schema; communicate via events/API

// 3. Design for failure
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>()
    .AddStandardResilienceHandler(); // retry + circuit breaker + timeout

// 4. Async-first communication
// Prefer events over synchronous calls for non-critical paths
await publishEndpoint.Publish(new OrderShipped(orderId, trackingNumber));

// 5. API versioning — never break consumers
var v1 = app.MapGroup("/api/v1");
var v2 = app.MapGroup("/api/v2");
v1.MapGet("/orders/{id}", GetOrderV1);
v2.MapGet("/orders/{id}", GetOrderV2); // new shape, v1 still works

// 6. Observability from day one
builder.Services.AddOpenTelemetry()
    .WithTracing(t => t.AddAspNetCoreInstrumentation().AddOtlpExporter())
    .WithMetrics(m => m.AddAspNetCoreInstrumentation().AddOtlpExporter());

builder.Host.UseSerilog((ctx, cfg) => cfg
    .Enrich.FromLogContext()
    .WriteTo.Console(new JsonFormatter()));

// 7. Idempotent consumers — safe to process same message twice
public class OrderCreatedConsumer : IConsumer<OrderCreated>
{
    public async Task Consume(ConsumeContext<OrderCreated> ctx)
    {
        // Check if already processed (idempotency key)
        if (await db.ProcessedEvents.AnyAsync(e => e.Id == ctx.MessageId))
            return; // duplicate — skip

        // ... process ...

        db.ProcessedEvents.Add(new ProcessedEvent(ctx.MessageId!.Value));
        await db.SaveChangesAsync();
    }
}

// 8. Health checks for Kubernetes
app.MapHealthChecks("/health/live",  new() { Predicate = _ => false }); // always alive
app.MapHealthChecks("/health/ready", new() { Predicate = _ => true  }); // checks deps

// 9. Use semantic versioning for Docker images + chart versions
// v1.2.3 — never use :latest in production

// 10. 12-Factor App principles
// Config from environment; logs to stdout; stateless processes
↥ back to top

Q. How do you implement gRPC in .NET Core for service-to-service communication?

gRPC is a high-performance RPC framework using Protocol Buffers (protobuf) for serialization. It provides strongly-typed contracts, bi-directional streaming, and is significantly faster than REST for internal service calls.

# Create gRPC server
dotnet new grpc -n OrderGrpcService
cd OrderGrpcService
dotnet add package Grpc.AspNetCore
// Protos/order.proto — shared contract (copy to both projects or use NuGet)
syntax = "proto3";
option csharp_namespace = "OrderGrpcService";

package order;

service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderReply);
  rpc GetOrder    (GetOrderRequest)    returns (OrderReply);
  rpc StreamOrders(StreamRequest)      returns (stream OrderReply); // server streaming
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
}
message CreateOrderReply { string order_id = 1; }
message GetOrderRequest  { string order_id = 1; }
message OrderReply       { string order_id = 1; string status = 2; double total = 3; }
message OrderItem        { string product_id = 1; int32 quantity = 2; }
message StreamRequest    { string customer_id = 1; }
<!-- Server .csproj — auto-generates C# from proto -->
<ItemGroup>
  <Protobuf Include="Protos\order.proto" GrpcServices="Server" />
</ItemGroup>
// ” gRPC SERVER ”——————————————————————————————————————————————————————
// Services/OrderGrpcService.cs
using Grpc.Core;
using OrderGrpcService;

public class OrderGrpcServiceImpl(IOrderRepository repo) : OrderService.OrderServiceBase
{
    public override async Task<CreateOrderReply> CreateOrder(
        CreateOrderRequest request, ServerCallContext ctx)
    {
        var order = await repo.CreateAsync(request.CustomerId,
            request.Items.Select(i => (i.ProductId, i.Quantity)).ToList(),
            ctx.CancellationToken);

        return new CreateOrderReply { OrderId = order.Id.ToString() };
    }

    public override async Task<OrderReply> GetOrder(
        GetOrderRequest request, ServerCallContext ctx)
    {
        var order = await repo.GetAsync(Guid.Parse(request.OrderId), ctx.CancellationToken)
            ?? throw new RpcException(new Status(StatusCode.NotFound, "Order not found"));

        return new OrderReply
        {
            OrderId = order.Id.ToString(),
            Status  = order.Status.ToString(),
            Total   = (double)order.Total
        };
    }

    // Server-side streaming — push multiple responses
    public override async Task StreamOrders(
        StreamRequest request,
        IServerStreamWriter<OrderReply> stream,
        ServerCallContext ctx)
    {
        await foreach (var order in repo.GetByCustomerAsync(request.CustomerId, ctx.CancellationToken))
        {
            await stream.WriteAsync(new OrderReply
            {
                OrderId = order.Id.ToString(),
                Status  = order.Status.ToString(),
                Total   = (double)order.Total
            });
        }
    }
}

// Program.cs (server)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc(opt =>
{
    opt.EnableDetailedErrors = builder.Environment.IsDevelopment();
    opt.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4 MB
});
builder.Services.AddScoped<IOrderRepository, OrderRepository>();

var app = builder.Build();
app.MapGrpcService<OrderGrpcServiceImpl>();
app.MapGet("/", () => "gRPC server. Use a gRPC client to communicate.");
app.Run();
<!-- Client .csproj -->
<ItemGroup>
  <PackageReference Include="Grpc.Net.ClientFactory" Version="2.*" />
  <PackageReference Include="Google.Protobuf"         Version="3.*" />
  <PackageReference Include="Grpc.Tools"              Version="2.*" PrivateAssets="All" />
  <Protobuf Include="Protos\order.proto" GrpcServices="Client" />
</ItemGroup>
// ” gRPC CLIENT ”——————————————————————————————————————————————————————
// Program.cs (consumer service)
builder.Services.AddGrpcClient<OrderService.OrderServiceClient>(o =>
{
    o.Address = new Uri(builder.Configuration["Services:OrderGrpc"]!);
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
    KeepAlivePingDelay          = TimeSpan.FromSeconds(60),
    KeepAlivePingTimeout        = TimeSpan.FromSeconds(30),
    EnableMultipleHttp2Connections = true
})
.AddStandardResilienceHandler(); // retry + circuit breaker

// Usage in a controller / service
public class CheckoutService(OrderService.OrderServiceClient grpcClient)
{
    public async Task<string> PlaceOrderAsync(string customerId, List<(string, int)> items)
    {
        var request = new CreateOrderRequest { CustomerId = customerId };
        request.Items.AddRange(items.Select(i =>
            new OrderItem { ProductId = i.Item1, Quantity = i.Item2 }));

        var reply = await grpcClient.CreateOrderAsync(request);
        return reply.OrderId;
    }

    // Consume server-side stream
    public async IAsyncEnumerable<OrderReply> StreamCustomerOrdersAsync(string customerId)
    {
        using var stream = grpcClient.StreamOrders(new StreamRequest { CustomerId = customerId });
        await foreach (var order in stream.ResponseStream.ReadAllAsync())
            yield return order;
    }
}

gRPC vs REST: | | gRPC | REST | |–|——|——| | Protocol | HTTP/2 + protobuf | HTTP/1.1 + JSON | | Performance | ~7–10— faster | Baseline | | Streaming | Client/server/bidirectional | Limited (SSE) | | Contract | Strongly typed .proto | OpenAPI/Swagger | | Browser support | Limited (needs gRPC-Web) | Universal | | Best for | Internal service mesh | Public APIs |

↥ back to top

Q. How do you use message brokers with MassTransit and RabbitMQ in .NET?

MassTransit is an open-source service bus abstraction for .NET that supports RabbitMQ, Azure Service Bus, Kafka, and more. It provides publish/subscribe, request/reply, and saga patterns.

dotnet add package MassTransit.RabbitMQ
dotnet add package MassTransit.EntityFrameworkCore  # for saga persistence
// ” 1. DEFINE MESSAGES (contracts — shared library) ”—————————————————
namespace Contracts;

// Events (past tense — something happened)
public record OrderPlaced(Guid OrderId, string CustomerId, decimal Total, DateTimeOffset PlacedAt);
public record OrderShipped(Guid OrderId, string TrackingNumber, DateTimeOffset ShippedAt);
public record PaymentProcessed(Guid OrderId, bool Success, string? FailureReason);

// Commands (imperative — do something)
public record ProcessPayment(Guid OrderId, decimal Amount, string PaymentToken);
public record SendOrderConfirmation(Guid OrderId, string CustomerEmail);

// ” 2. PRODUCER — PUBLISH EVENT ”—————————————————————————————————————
public class OrderService(IPublishEndpoint publishEndpoint, AppDbContext db)
{
    public async Task<Order> PlaceOrderAsync(PlaceOrderRequest req, CancellationToken ct)
    {
        var order = new Order(req.CustomerId, req.Items);
        db.Orders.Add(order);
        await db.SaveChangesAsync(ct);

        // Publish event — all subscribers receive it
        await publishEndpoint.Publish(
            new OrderPlaced(order.Id, order.CustomerId, order.Total, DateTimeOffset.UtcNow),
            ct);

        return order;
    }
}

// ” 3. CONSUMER — HANDLE EVENT ”——————————————————————————————————————
public class OrderPlacedConsumer(IEmailService email, ILogger<OrderPlacedConsumer> logger)
    : IConsumer<OrderPlaced>
{
    public async Task Consume(ConsumeContext<OrderPlaced> context)
    {
        var evt = context.Message;
        logger.LogInformation("Processing OrderPlaced {OrderId}", evt.OrderId);

        // Send confirmation email
        await email.SendOrderConfirmationAsync(evt.CustomerId, evt.OrderId);

        // Optionally respond (for request/reply pattern)
        // await context.RespondAsync(new OrderConfirmationSent(evt.OrderId));
    }
}

// Payment consumer with retry/error handling
public class ProcessPaymentConsumer : IConsumer<ProcessPayment>
{
    public async Task Consume(ConsumeContext<ProcessPayment> context)
    {
        var cmd = context.Message;
        try
        {
            var result = await ProcessPaymentInternalAsync(cmd);
            await context.Publish(new PaymentProcessed(cmd.OrderId, result.Success, null));
        }
        catch (PaymentGatewayException ex)
        {
            // Throw to trigger MassTransit retry policy
            throw new Exception($"Payment gateway error: {ex.Message}", ex);
        }
    }

    private Task<PaymentResult> ProcessPaymentInternalAsync(ProcessPayment cmd)
        => Task.FromResult(new PaymentResult(true)); // stub
}

record PaymentResult(bool Success);

// ” 4. SAGA — COORDINATE LONG-RUNNING WORKFLOW ”—————————————————————
public class OrderStateMachine : MassTransitStateMachine<OrderSagaState>
{
    public State Placed    { get; private set; } = null!;
    public State Paid      { get; private set; } = null!;
    public State Shipped   { get; private set; } = null!;

    public Event<OrderPlaced>       OrderPlaced       { get; private set; } = null!;
    public Event<PaymentProcessed>  PaymentProcessed  { get; private set; } = null!;
    public Event<OrderShipped>      OrderShipped      { get; private set; } = null!;

    public OrderStateMachine()
    {
        InstanceState(x => x.CurrentState);

        Event(() => OrderPlaced,      e => e.CorrelateById(m => m.Message.OrderId));
        Event(() => PaymentProcessed, e => e.CorrelateById(m => m.Message.OrderId));
        Event(() => OrderShipped,     e => e.CorrelateById(m => m.Message.OrderId));

        Initially(
            When(OrderPlaced)
                .Then(ctx => ctx.Saga.CustomerId = ctx.Message.CustomerId)
                .Publish(ctx => new ProcessPayment(ctx.Saga.CorrelationId, ctx.Message.Total, "token"))
                .TransitionTo(Placed));

        During(Placed,
            When(PaymentProcessed, ctx => ctx.Message.Success)
                .TransitionTo(Paid),
            When(PaymentProcessed, ctx => !ctx.Message.Success)
                .Finalize()); // failed — end saga

        During(Paid,
            When(OrderShipped)
                .TransitionTo(Shipped)
                .Finalize());
    }
}

public class OrderSagaState : SagaStateMachineInstance
{
    public Guid   CorrelationId { get; set; }
    public string CurrentState  { get; set; } = null!;
    public string CustomerId    { get; set; } = null!;
}

// ” 5. REGISTRATION ”—————————————————————————————————————————————————
builder.Services.AddMassTransit(x =>
{
    x.AddConsumer<OrderPlacedConsumer>();
    x.AddConsumer<ProcessPaymentConsumer>();
    x.AddSagaStateMachine<OrderStateMachine, OrderSagaState>()
        .EntityFrameworkRepository(r =>
        {
            r.ExistingDbContext<AppDbContext>();
            r.UsePostgres();
        });

    x.UsingRabbitMq((ctx, cfg) =>
    {
        cfg.Host("rabbitmq://localhost", h =>
        {
            h.Username("guest");
            h.Password("guest");
        });

        // Retry policy — exponential backoff, 3 attempts
        cfg.UseMessageRetry(r => r.Exponential(3,
            TimeSpan.FromSeconds(1),
            TimeSpan.FromSeconds(10),
            TimeSpan.FromSeconds(2)));

        // Dead-letter queue after exhausted retries
        cfg.UseDelayedRedelivery(r => r.Intervals(
            TimeSpan.FromMinutes(5),
            TimeSpan.FromMinutes(15),
            TimeSpan.FromHours(1)));

        cfg.ConfigureEndpoints(ctx); // auto-configure queues from registered consumers
    });
});
↥ back to top

Q. What are distributed patterns in microservices (Outbox, Circuit Breaker, Idempotency)?

Distributed patterns address the fundamental challenges of reliability and consistency when services communicate over a network.

// ” 1. OUTBOX PATTERN — guaranteed event delivery ”———————————————————
// Problem: DB save and message publish can fail independently ’ lost messages
// Solution: Write event to outbox table in SAME transaction as domain changes

// Outbox message entity
public class OutboxMessage
{
    public Guid     Id           { get; init; } = Guid.NewGuid();
    public string   Type         { get; init; } = null!; // full type name
    public string   Payload      { get; init; } = null!; // JSON
    public DateTime CreatedAt    { get; init; } = DateTime.UtcNow;
    public DateTime? ProcessedAt { get; set; }
}

// Service — writes domain change + outbox in same transaction
public class OrderService(AppDbContext db)
{
    public async Task PlaceOrderAsync(PlaceOrderRequest req, CancellationToken ct)
    {
        var order = new Order(req.CustomerId, req.Items);
        var evt   = new OrderPlaced(order.Id, order.CustomerId, order.Total, DateTimeOffset.UtcNow);

        db.Orders.Add(order);
        db.OutboxMessages.Add(new OutboxMessage
        {
            Type    = typeof(OrderPlaced).FullName!,
            Payload = JsonSerializer.Serialize(evt)
        });

        await db.SaveChangesAsync(ct); // atomic — either both succeed or both fail
    }
}

// Background processor — reads outbox and publishes to broker
public class OutboxProcessor(AppDbContext db, IPublishEndpoint bus) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            var messages = await db.OutboxMessages
                .Where(m => m.ProcessedAt == null)
                .OrderBy(m => m.CreatedAt)
                .Take(50)
                .ToListAsync(ct);

            foreach (var msg in messages)
            {
                var type    = Type.GetType(msg.Type)!;
                var payload = JsonSerializer.Deserialize(msg.Payload, type)!;
                await bus.Publish(payload, type, ct);
                msg.ProcessedAt = DateTime.UtcNow;
            }

            await db.SaveChangesAsync(ct);
            await Task.Delay(TimeSpan.FromSeconds(5), ct);
        }
    }
}

// ” 2. CIRCUIT BREAKER — stop cascading failures ”————————————————————
// Using Microsoft.Extensions.Http.Resilience (.NET 8+)
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>()
    .AddResilienceHandler("catalog-pipeline", p =>
    {
        // Retry: 3 attempts, exponential backoff
        p.AddRetry(new HttpRetryStrategyOptions
        {
            MaxRetryAttempts = 3,
            BackoffType      = DelayBackoffType.Exponential,
            UseJitter        = true,
            Delay            = TimeSpan.FromMilliseconds(500)
        });

        // Circuit breaker: open after 50% failure rate over 10-second sampling
        p.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
        {
            SamplingDuration          = TimeSpan.FromSeconds(10),
            FailureRatio              = 0.5,
            MinimumThroughput         = 5,
            BreakDuration             = TimeSpan.FromSeconds(30)
        });

        // Total timeout per full request (including retries)
        p.AddTimeout(TimeSpan.FromSeconds(10));
    });

// ” 3. IDEMPOTENCY KEY — safe retries ”———————————————————————————————
// Ensure duplicate requests produce the same result
public class IdempotentOrderService(AppDbContext db)
{
    public async Task<OrderResult> PlaceOrderAsync(
        PlaceOrderRequest req,
        Guid idempotencyKey, // client-generated key
        CancellationToken ct)
    {
        // Check if already processed
        var existing = await db.IdempotencyRecords
            .FirstOrDefaultAsync(r => r.Key == idempotencyKey, ct);
        if (existing != null)
            return JsonSerializer.Deserialize<OrderResult>(existing.Response)!;

        var order = new Order(req.CustomerId, req.Items);
        db.Orders.Add(order);

        var result = new OrderResult(order.Id, order.Status.ToString());
        db.IdempotencyRecords.Add(new IdempotencyRecord
        {
            Key      = idempotencyKey,
            Response = JsonSerializer.Serialize(result),
            ExpiresAt = DateTime.UtcNow.AddDays(1)
        });

        await db.SaveChangesAsync(ct);
        return result;
    }
}

// ” 4. CORRELATION ID — trace requests across services ”——————————————
public class CorrelationIdMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext ctx)
    {
        const string header = "X-Correlation-Id";
        if (!ctx.Request.Headers.TryGetValue(header, out var correlationId))
            correlationId = Guid.NewGuid().ToString();

        ctx.Response.Headers[header] = correlationId;

        using (Serilog.Context.LogContext.PushProperty("CorrelationId", (string)correlationId!))
            await next(ctx);
    }
}

// Registration
app.UseMiddleware<CorrelationIdMiddleware>();

// Propagate to downstream HTTP calls
builder.Services.AddHttpClient<ICatalogClient, CatalogClient>()
    .AddHttpMessageHandler<CorrelationIdPropagationHandler>();
↥ back to top

# 15. PERFORMANCE AND OPTIMIZATION


Q. How can you improve string concatenation performance in C#?

String concatenation with + inside loops creates a new heap allocation on every iteration. Use StringBuilder, interpolated string handlers, or string.Create depending on context.

using System.Text;

//  O(n) — each += allocates a new string
string result = "";
for (int i = 0; i < 100_000; i++)
    result += i.ToString(); // 100,000 heap allocations

// … StringBuilder — single buffer, amortised O(1) append
var sb = new StringBuilder(capacity: 1_024_000); // pre-allocate if size is known
for (int i = 0; i < 100_000; i++)
    sb.Append(i);
string r1 = sb.ToString(); // single final allocation

// … string.Concat / Join — best for fixed number of strings
string r2 = string.Concat("Hello", " ", "World");
string r3 = string.Join(", ", new[] { "Alice", "Bob", "Carol" });

// … Interpolated strings — compiler-optimised in .NET 6+
// Uses DefaultInterpolatedStringHandler internally — no intermediate string
string name = "Alice"; int age = 30;
string r4 = $"{name} is {age}";

// … string.Create — zero-copy, write directly into final buffer (.NET 6+)
int[] numbers = [1, 2, 3, 4, 5];
string r5 = string.Create(numbers.Length * 2 - 1, numbers, (span, nums) =>
{
    for (int i = 0; i < nums.Length; i++)
    {
        span[i * 2] = (char)('0' + nums[i]);
        if (i < nums.Length - 1) span[i * 2 + 1] = ',';
    }
});
Console.WriteLine(r5); // 1,2,3,4,5

// … ValueStringBuilder / stackalloc for hot paths (advanced)
Span<char> buf = stackalloc char[256];
var vsb = new System.Text.StringBuilder(); // or use MemoryExtensions for Span-based ops

// Benchmark result (relative):
// string +=          : 10,000 ms (100k iterations)
// StringBuilder      :      3 ms
// string.Create      :      1 ms (zero intermediate allocations)
↥ back to top

Q. How do you work with parallelism and concurrency in C#?

Concurrency — multiple tasks making progress (may share a single thread via async). Parallelism — multiple tasks executing simultaneously on multiple CPU cores.

// 1. async/await — concurrency for I/O-bound work (no extra threads)
async Task<string[]> FetchAllAsync(string[] urls)
{
    using var client = new HttpClient();
    var tasks = urls.Select(url => client.GetStringAsync(url));
    return await Task.WhenAll(tasks); // all in parallel, no thread blocking
}

// 2. Parallel.For / Parallel.ForEach — parallelism for CPU-bound work
int[] data = Enumerable.Range(1, 1_000_000).ToArray();
long sum = 0;
Parallel.For(0, data.Length,
    () => 0L,                                          // local state
    (i, _, local) => local + data[i],                 // body
    local => Interlocked.Add(ref sum, local));         // merge
Console.WriteLine(sum);

// 3. Parallel.ForEachAsync (.NET 6+) — async work with bounded parallelism
var urls = Enumerable.Range(1, 20).Select(i => $"https://api.example.com/item/{i}");
await Parallel.ForEachAsync(urls,
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    async (url, ct) =>
    {
        using var client = new HttpClient();
        var data = await client.GetStringAsync(url, ct);
        Console.WriteLine($"Got {data.Length} chars");
    });

// 4. PLINQ — parallel LINQ for data processing
var results = ParallelEnumerable.Range(1, 1_000_000)
    .AsParallel()
    .WithDegreeOfParallelism(Environment.ProcessorCount)
    .Where(n => n % 2 == 0)
    .Select(n => n * n)
    .Sum();

// 5. Channel<T> — producer/consumer pipelines (.NET Core 2.1+)
using System.Threading.Channels;

var channel = Channel.CreateBounded<int>(capacity: 100);

// Producer
var producer = Task.Run(async () =>
{
    for (int i = 0; i < 1000; i++)
    {
        await channel.Writer.WriteAsync(i);
    }
    channel.Writer.Complete();
});

// Consumer
var consumer = Task.Run(async () =>
{
    await foreach (int item in channel.Reader.ReadAllAsync())
        Console.WriteLine(item);
});

await Task.WhenAll(producer, consumer);
↥ back to top

Q. What are some common performance issues in .NET Core applications?

Issue Symptom Fix
Excessive allocations High GC pressure, frequent Gen 0 collections Span<T>, ArrayPool<T>, object pooling
Sync-over-async Thread starvation, deadlocks Use async/await throughout
N+1 queries Slow DB responses EF Core .Include(), batch queries
Large object heap (LOH) Heap fragmentation, long GC pauses Avoid large arrays; use ArrayPool
String concatenation in loops High memory, slow performance StringBuilder, string.Create
Missing caching Repeated expensive computations IMemoryCache, IDistributedCache
Over-fetching data Large payloads, slow queries Projection (Select), pagination
Boxing value types Hidden allocations Use generics, avoid object parameters
Blocking async code Deadlocks (.Result, .Wait()) await everywhere, ConfigureAwait(false)
Missing AsNoTracking Unnecessary EF change tracking Add .AsNoTracking() for read-only queries
//  Common anti-pattern: sync-over-async
public string GetData() => FetchAsync().Result; // blocks thread pool thread

// … Fix: async all the way
public async Task<string> GetDataAsync() => await FetchAsync();

//  Anti-pattern: unnecessary ToList() materialisation
var count = dbContext.Orders.ToList().Count; // loads ALL rows into memory

// … Fix: query at DB level
var count = await dbContext.Orders.CountAsync();

//  Anti-pattern: missing ConfigureAwait in libraries
await SomeLibraryMethodAsync(); // may deadlock in ASP.NET Framework context

// … Fix in library code
await SomeLibraryMethodAsync().ConfigureAwait(false);

// Detect issues via dotnet-counters
// dotnet-counters monitor --process-id <PID> System.Runtime
// Watch: gc-heap-size, gen-0-gc-count, threadpool-queue-length, alloc-rate
↥ back to top

Q. How do you measure the performance of a .NET Core application?

// 1. Stopwatch — quick timing
using System.Diagnostics;

var sw = Stopwatch.StartNew();
PerformWork();
sw.Stop();
Console.WriteLine($"Elapsed: {sw.Elapsed.TotalMilliseconds:F3} ms");

// 2. BenchmarkDotNet — production-grade micro-benchmarks (gold standard)
// dotnet add package BenchmarkDotNet

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
[SimpleJob(iterationCount: 10)]
public class StringBenchmarks
{
    private const int N = 10_000;

    [Benchmark(Baseline = true)]
    public string Concatenation()
    {
        string s = "";
        for (int i = 0; i < N; i++) s += i;
        return s;
    }

    [Benchmark]
    public string StringBuilderBenchmark()
    {
        var sb = new System.Text.StringBuilder(N * 5);
        for (int i = 0; i < N; i++) sb.Append(i);
        return sb.ToString();
    }
}

BenchmarkRunner.Run<StringBenchmarks>();
// Reports: Mean, Allocated, Gen0/1/2 GC counts

// 3. Activity / OpenTelemetry — distributed tracing
using System.Diagnostics;

var source = new ActivitySource("MyApp.Performance");
using var activity = source.StartActivity("ProcessOrder");
activity?.SetTag("order.id", 42);
// ... work ...
activity?.Stop();

// 4. EventCounters — production runtime metrics
// dotnet-counters monitor -p <PID> System.Runtime Microsoft.AspNetCore.Hosting
↥ back to top

Q. What tools are available for profiling .NET Core applications?

Tool Type Use case
dotnet-counters CLI / live Real-time CPU, GC, allocation rates
dotnet-trace CLI / file CPU sampling, GC events, custom events
dotnet-dump CLI / post-mortem Memory dumps, OOM analysis
dotnet-gcdump CLI / heap GC heap snapshot, object retention
Visual Studio Profiler GUI CPU sampling, memory snapshots, timeline
PerfView GUI / ETW Deep GC, JIT, thread pool analysis
JetBrains dotMemory GUI Memory profiling, heap diff
JetBrains dotTrace GUI CPU profiling, async call trees
BenchmarkDotNet Code Micro-benchmarks with statistics
OpenTelemetry Code / infra Distributed tracing, metrics, logs
# Install global tools
dotnet tool install -g dotnet-counters
dotnet tool install -g dotnet-trace
dotnet tool install -g dotnet-dump
dotnet tool install -g dotnet-gcdump

# Live metrics
dotnet-counters monitor -p <PID> System.Runtime

# CPU trace (30 seconds)
dotnet-trace collect -p <PID> --duration 00:00:30 --output trace.nettrace

# Open in Visual Studio or PerfView:
# perfview /GCCollectOnly trace.nettrace

# Heap dump
dotnet-gcdump collect -p <PID> --output heap.gcdump
# Open in Visual Studio: File ’ Open ’ heap.gcdump

# Full memory dump
dotnet-dump collect -p <PID>
dotnet-dump analyze core_<PID>
# Then: dumpheap -stat, gcroot <address>, etc.
↥ back to top

Q. How do you use the dotnet-counters tool to monitor performance?

dotnet-counters streams real-time .NET runtime counters (GC, thread pool, exception rates, allocation rates) to the console without restarting the process.

# Install
dotnet tool install -g dotnet-counters

# List available counter providers
dotnet-counters list

# Monitor default System.Runtime counters for a process
dotnet-counters monitor --process-id 1234

# Monitor multiple providers
dotnet-counters monitor -p 1234 \
  System.Runtime \
  Microsoft.AspNetCore.Hosting \
  Microsoft.AspNetCore.Http.Connections

# Monitor by process name
dotnet-counters monitor --name MyApi

# Export to CSV for analysis
dotnet-counters collect -p 1234 \
  --output counters.csv \
  --duration 00:02:00 \
  --format csv

# Key counters to watch:
# cpu-usage                  — CPU %
# gc-heap-size               — managed heap size (MB)
# gen-0-gc-count             — Gen 0 GC frequency
# alloc-rate                 — allocation rate (bytes/sec)
# threadpool-queue-length    — pending thread pool work items
# active-timer-count         — active timers
# exception-count            — unhandled exceptions/sec
# requests-per-second        — ASP.NET Core RPS
# current-requests           — in-flight HTTP requests
// Emit custom EventCounters from your app
using System.Diagnostics.Tracing;

[EventSource(Name = "MyApp")]
public sealed class MyEventSource : EventSource
{
    public static readonly MyEventSource Log = new();
    private EventCounter? _requestDuration;

    public MyEventSource()
    {
        _requestDuration = new EventCounter("request-duration-ms", this)
        {
            DisplayName = "Request Duration (ms)"
        };
    }

    public void RecordRequest(double ms) => _requestDuration?.WriteMetric(ms);
}

// Usage
MyEventSource.Log.RecordRequest(42.5);

// Monitor custom counters
// dotnet-counters monitor -p <PID> MyApp
↥ back to top

Q. What is the dotnet-trace tool and how is it used?

dotnet-trace captures ETW/EventPipe events from a running .NET process into a .nettrace file for offline analysis of CPU, GC, JIT, and custom events.

# Install
dotnet tool install -g dotnet-trace

# List available event profiles
dotnet-trace list-profiles
# cpu-sampling   — CPU call stacks (default)
# gc-verbose     — detailed GC events
# gc-collect     — GC collection only
# none           — no built-in; specify providers manually

# Capture 30-second CPU profile
dotnet-trace collect -p <PID> \
  --profile cpu-sampling \
  --duration 00:00:30 \
  --output myapp.nettrace

# GC-focused trace
dotnet-trace collect -p <PID> \
  --profile gc-verbose \
  --output gc.nettrace

# Custom providers (e.g., ASP.NET Core + GC)
dotnet-trace collect -p <PID> \
  --providers "Microsoft.AspNetCore:4:5,System.Runtime:4:4" \
  --output custom.nettrace

# Trace from startup (catches cold-start JIT)
dotnet-trace collect -- dotnet MyApp.dll

# Analyse the trace
# Open in: Visual Studio ’ File ’ Open ’ myapp.nettrace
# Or: PerfView myapp.nettrace
# Or: speedscope.app (convert first)
dotnet-trace convert myapp.nettrace --format Speedscope
# Open myapp.speedscope.json at https://speedscope.app
↥ back to top

Q. How do you use the dotnet-dump tool to collect and analyze memory dumps?

dotnet-dump captures a full managed memory dump and provides a REPL for analysis — useful for diagnosing OOM crashes, deadlocks, and high memory usage.

# Install
dotnet tool install -g dotnet-dump

# Capture dump from running process
dotnet-dump collect -p <PID> --output myapp.dmp

# Capture on crash (set environment variable before starting)
export DOTNET_DbgEnableMiniDump=1
export DOTNET_DbgMiniDumpType=4       # 1=Mini, 2=Heap, 3=Triage, 4=Full
export DOTNET_DbgMiniDumpName=/tmp/crash_%p.dmp

# Analyze interactively
dotnet-dump analyze myapp.dmp
# Inside the analyze REPL — useful commands:

# Show all managed threads
clrthreads

# Show call stacks for all threads
clrstack -all

# Heap statistics — top object types by count/size
dumpheap -stat

# Find all instances of a type
dumpheap -type System.String -stat

# Show object at address
dumpobj 0x00007f8b1c002a18

# Find what holds a reference to an object (root analysis)
gcroot 0x00007f8b1c002a18

# Show finalizer queue
finalizequeue

# Show thread pool state
threadpool

# Show sync blocks (locks)
syncblk

# Exit
exit
↥ back to top

Q. What is the dotnet-gcdump tool and how is it used?

dotnet-gcdump captures a GC heap snapshot in .gcdump format — a lighter alternative to a full memory dump. It triggers a GC collection, then records all live objects and their references.

# Install
dotnet tool install -g dotnet-gcdump

# Capture heap dump from running process
dotnet-gcdump collect -p <PID> --output myapp.gcdump

# Capture from process by name
dotnet-gcdump collect --name MyApi

# Analyse in VS Code / Visual Studio
# Visual Studio: File ’ Open ’ myapp.gcdump
# Shows: object counts, sizes, retention trees

# Report from command line (top types by size)
dotnet-gcdump report myapp.gcdump

# Compare two snapshots (detect leaks)
dotnet-gcdump report baseline.gcdump after.gcdump --diff
// Programmatic heap size info (no external tool needed)
GCMemoryInfo info = GC.GetGCMemoryInfo();
Console.WriteLine($"Heap size:       {info.HeapSizeBytes / 1_048_576:F1} MB");
Console.WriteLine($"Committed:       {info.TotalCommittedBytes / 1_048_576:F1} MB");
Console.WriteLine($"Fragmented:      {info.FragmentedBytes / 1_024:F0} KB");
Console.WriteLine($"Gen0 size after: {info.GenerationInfo[0].SizeAfterBytes:N0} bytes");
Console.WriteLine($"Gen2 size after: {info.GenerationInfo[2].SizeAfterBytes:N0} bytes");

// Force GC and collect snapshot programmatically (for testing only)
GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true);
long heapBytes = GC.GetTotalMemory(forceFullCollection: false);
Console.WriteLine($"Heap after GC: {heapBytes / 1_048_576:F2} MB");
↥ back to top

Q. How do you optimize memory usage in .NET Core applications?

// 1. Use Span<T> / Memory<T> to avoid allocations
byte[] buffer = new byte[4096];
ReadOnlySpan<byte> slice = buffer.AsSpan(0, 100); // zero allocation

// 2. ArrayPool<T> — reuse buffers instead of allocating
using System.Buffers;

byte[] rented = ArrayPool<byte>.Shared.Rent(4096);
try   { /* use rented */ }
finally { ArrayPool<byte>.Shared.Return(rented, clearArray: false); }

// 3. ObjectPool<T> — reuse expensive objects
using Microsoft.Extensions.ObjectPool;

var policy = new DefaultPooledObjectPolicy<StringBuilder>();
var pool   = new DefaultObjectPool<StringBuilder>(policy, maximumRetained: 10);
var sb     = pool.Get();
try   { sb.Append("work"); var result = sb.ToString(); }
finally { pool.Return(sb); } // sb.Clear() called automatically

// 4. Use struct instead of class for small, short-lived value containers
// (avoids heap allocation when stored as local / struct field)
public readonly record struct Point(double X, double Y);

// 5. Avoid unnecessary LINQ materialisation
//  Loads everything into memory
var names = db.Products.ToList().Select(p => p.Name).ToList();
// … Projection at DB level
var names2 = await db.Products.Select(p => p.Name).ToListAsync();

// 6. String interning for frequently repeated strings
string s = string.Intern(someString);

// 7. Weak references — allow GC to collect when under pressure
var weak = new WeakReference<ExpensiveObject>(new ExpensiveObject());
if (weak.TryGetTarget(out var obj))
    obj.Use();
// else obj was collected — recreate if needed

// 8. Dispose IDisposable resources promptly
await using var stream = new FileStream("file.txt", FileMode.Open);
// stream disposed at end of using block
↥ back to top

Q. What are some best practices for managing memory in .NET Core?

// 1. Implement IDisposable / IAsyncDisposable for unmanaged resources
public sealed class ResourceHolder : IAsyncDisposable
{
    private readonly FileStream _stream = File.Open("data.bin", FileMode.OpenOrCreate);
    private bool _disposed;

    public async ValueTask DisposeAsync()
    {
        if (_disposed) return;
        await _stream.DisposeAsync();
        _disposed = true;
    }
}

// 2. Avoid static collections that grow without bound (memory leaks)
//  Unbounded static cache — never cleaned up
private static readonly Dictionary<int, byte[]> _cache = new();

// … Use IMemoryCache with expiry
builder.Services.AddMemoryCache();
// cache.Set(key, value, TimeSpan.FromMinutes(5));

// 3. Prefer value types for hot-path data
record struct Coordinate(double Lat, double Lon); // stack-allocated when local

// 4. GC tuning for server workloads
// In runtimeconfig.json or environment variables:
// DOTNET_GCConserveMemory=5      (0-9, higher = more aggressive GC)
// DOTNET_GCHeapHardLimit=1073741824  (1 GB hard cap)
// DOTNET_gcServer=1              (server GC — better throughput)

// 5. Use cancellation tokens to abort long operations
async Task<string> FetchWithTimeoutAsync(string url, CancellationToken ct)
{
    using var client = new HttpClient();
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
    cts.CancelAfter(TimeSpan.FromSeconds(10));
    return await client.GetStringAsync(url, cts.Token);
}

// 6. Avoid finalizers — use IDisposable pattern instead
// Finalizers delay GC (object survives to Gen 1 before collection)

// 7. Pre-size collections when count is known
var list = new List<int>(capacity: 10_000);   // avoids re-allocations
var dict = new Dictionary<string, int>(capacity: 500);
↥ back to top

Q. How do you reduce the memory footprint of a .NET Core application?

# 1. Publish as trimmed self-contained (remove unused framework code)
dotnet publish -c Release -r linux-x64 --self-contained \
  -p:PublishTrimmed=true \
  -p:TrimMode=full \
  -o ./publish
# Reduces from ~80 MB to ~15-20 MB for a minimal API

# 2. Native AOT — no JIT compiler or reflection metadata in memory
dotnet publish -c Release -r linux-x64 -p:PublishAot=true

# 3. Use minimal framework components
<!-- .csproj — exclude unused features -->
<PropertyGroup>
  <InvariantGlobalization>true</InvariantGlobalization>  <!-- save ~10 MB -->
  <MetadataUpdaterSupport>false</MetadataUpdaterSupport>
  <UseSystemResourceKeys>true</UseSystemResourceKeys>
</PropertyGroup>
// 4. Server GC with memory limit
// DOTNET_GCHeapHardLimitPercent=75   — use max 75% of container memory
// DOTNET_gcServer=1                  — server GC (one heap per CPU core)
// DOTNET_GCConserveMemory=5          — favour smaller heap over throughput

// 5. Reduce per-request allocations
// Use IObjectPool, Span<T>, ArrayPool<T> (covered above)

// 6. Disable features not needed
var builder = WebApplication.CreateSlimBuilder(args); // .NET 8+ minimal builder
// CreateSlimBuilder omits: Kestrel auto-config, startup filters, hosting startup
// Result: faster startup + lower baseline memory

// 7. Monitor allocation rate with dotnet-counters
// dotnet-counters monitor -p <PID> System.Runtime
// Target: alloc-rate < 1 MB/s for steady-state server
↥ back to top

Q. How do you optimize CPU usage in .NET Core applications?

// 1. Offload CPU-bound work to thread pool with Task.Run
app.MapGet("/compute", async () =>
{
    var result = await Task.Run(() => ExpensiveCpuWork());
    return result;
});

// 2. PLINQ for data-parallel CPU work
var results = data.AsParallel()
    .WithDegreeOfParallelism(Environment.ProcessorCount)
    .Select(item => Transform(item))
    .ToList();

// 3. Aggressive JIT inlining for hot paths
[System.Runtime.CompilerServices.MethodImpl(
    System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
static int FastMath(int x) => x * x + 2 * x + 1;

// 4. SIMD / hardware intrinsics (.NET 10)
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

static float SumSimd(float[] values)
{
    if (Avx.IsSupported && values.Length >= 8)
    {
        var sum = Vector256<float>.Zero;
        int i = 0;
        for (; i <= values.Length - 8; i += 8)
            sum = Avx.Add(sum, Vector256.LoadUnsafe(ref values[i]));
        // handle remainder
        float total = 0;
        for (int j = 0; j < Vector256<float>.Count; j++) total += sum[j];
        for (; i < values.Length; i++) total += values[i];
        return total;
    }
    return values.Sum();
}

// 5. Use Vector<T> / generic SIMD (works on all platforms)
using System.Numerics;

static long SumVector(int[] data)
{
    var vSum = Vector<long>.Zero;
    int vLen = Vector<int>.Count;
    int i = 0;
    for (; i <= data.Length - vLen; i += vLen)
    {
        var v = new Vector<int>(data, i);
        Vector.Widen(v, out var lo, out var hi);
        vSum += Vector.ConvertToInt64(lo) + Vector.ConvertToInt64(hi);
    }
    long sum = 0;
    for (int j = 0; j < Vector<long>.Count; j++) sum += vSum[j];
    for (; i < data.Length; i++) sum += data[i];
    return sum;
}

// 6. Avoid unnecessary thread context switches
// Use SemaphoreSlim instead of Monitor for async-compatible locking
var semaphore = new SemaphoreSlim(1, 1);
await semaphore.WaitAsync();
try { /* protected work */ }
finally { semaphore.Release(); }
↥ back to top

Q. What are some best practices for optimizing CPU-bound operations?

// 1. Profile before optimising — measure first with BenchmarkDotNet or dotnet-trace
// 2. Use correct data structures — O(1) HashSet lookup vs O(n) List.Contains
var set = new HashSet<int>(data);
bool found = set.Contains(target); // O(1)

// 3. Avoid LINQ in the tightest loops — use plain for loops
//  LINQ in hot loop — delegate invocation overhead
for (int i = 0; i < 1_000_000; i++)
    total += data.Where(x => x > 0).Sum(); // re-evaluates every iteration

// … Pre-filter, use for loop in hot path
var positive = data.Where(x => x > 0).ToArray();
for (int i = 0; i < positive.Length; i++) total += positive[i];

// 4. Prefer stackalloc for small temporary buffers
Span<int> buf = stackalloc int[32]; // no heap allocation

// 5. Use ReadOnlySpan<char> for string parsing (no substrings)
ReadOnlySpan<char> input = "2026-04-19".AsSpan();
int year  = int.Parse(input[..4]);
int month = int.Parse(input[5..7]);
int day   = int.Parse(input[8..]);

// 6. Frozen collections for read-only lookup tables (.NET 8+)
using System.Collections.Frozen;
FrozenDictionary<string, int> lookup =
    new Dictionary<string, int> { ["a"] = 1, ["b"] = 2 }.ToFrozenDictionary();
// Lookup is ~30% faster than Dictionary<K,V> for read-only scenarios

// 7. Use object pooling for expensive-to-create objects
// (see ObjectPool<T> example above)

// 8. Prefer ValueTask over Task for frequently-completing async paths
public ValueTask<int> GetCachedValueAsync(int key)
{
    if (_cache.TryGetValue(key, out int v))
        return ValueTask.FromResult(v); // no Task allocation
    return new ValueTask<int>(FetchFromDbAsync(key));
}
↥ back to top

Q. How do you use asynchronous programming to improve performance in .NET Core?

// 1. async/await — releases thread during I/O wait
app.MapGet("/data", async (HttpClient client) =>
{
    // Thread returned to pool while waiting — handles more concurrent requests
    string data = await client.GetStringAsync("https://api.example.com/data");
    return data;
});

// 2. Parallel async I/O with Task.WhenAll
async Task<(string, string)> FetchTwoAsync()
{
    var t1 = httpClient.GetStringAsync("https://api1.example.com");
    var t2 = httpClient.GetStringAsync("https://api2.example.com");
    var results = await Task.WhenAll(t1, t2);   // both run concurrently
    return (results[0], results[1]);
}

// 3. Streaming with IAsyncEnumerable (avoids loading all data at once)
async IAsyncEnumerable<Product> StreamProductsAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    await foreach (var product in dbContext.Products.AsAsyncEnumerable().WithCancellation(ct))
        yield return product;
}

app.MapGet("/products/stream", (AppDbContext db) =>
    db.Products.AsAsyncEnumerable()); // ASP.NET Core streams the JSON array

// 4. ValueTask for cached/synchronous fast paths (avoid Task allocation)
private int _cached;
public ValueTask<int> GetValueAsync()
{
    if (_cached != 0) return ValueTask.FromResult(_cached); // no allocation
    return new ValueTask<int>(LoadFromDbAsync());
}

// 5. ConfigureAwait(false) in library code (avoid context capture overhead)
public async Task<string> LibraryMethodAsync()
{
    var data = await FetchAsync().ConfigureAwait(false);
    return Process(data);
}

// 6. CancellationToken — abort abandoned requests
app.MapGet("/slow", async (CancellationToken ct) =>
{
    await Task.Delay(5000, ct); // if client disconnects, ct is cancelled
    return "done";
});
↥ back to top

Q. What is the async and await keywords and how are they used?

async marks a method as asynchronous. await suspends execution of the method until the awaited task completes, without blocking the thread.

// Basic pattern
public async Task<string> FetchDataAsync(string url)
{
    using var client = new HttpClient();
    string data = await client.GetStringAsync(url); // thread released here
    return data.ToUpper(); // resumes here when response arrives
}

// Return types
async Task DoWorkAsync() { /* no return value */ }
async Task<int> GetCountAsync() { return 42; }
async ValueTask<int> GetCachedAsync() { return _cached; } // avoids Task alloc
async IAsyncEnumerable<int> GenerateAsync()
{
    for (int i = 0; i < 10; i++) { await Task.Delay(10); yield return i; }
}

// Awaiting multiple tasks
var t1 = FetchDataAsync("https://api1.example.com");
var t2 = FetchDataAsync("https://api2.example.com");
string[] results = await Task.WhenAll(t1, t2);   // parallel

// WhenAny — first to complete wins
var fastest = await Task.WhenAny(t1, t2);
Console.WriteLine(await fastest);

// Exception handling
try
{
    await RiskyOperationAsync();
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"HTTP error: {ex.StatusCode}");
}

// Async in a console app (.NET 10 supports top-level await)
await foreach (int n in GenerateAsync())
    Console.Write($"{n} ");
↥ back to top

Q. How do you use the Task class to perform asynchronous operations?

// 1. Task.Run — offload CPU-bound work to thread pool
var result = await Task.Run(() =>
{
    return Enumerable.Range(1, 1_000_000).Sum(x => (long)x);
});
Console.WriteLine(result); // 500000500000

// 2. Task.Delay — non-blocking pause (replaces Thread.Sleep)
await Task.Delay(TimeSpan.FromSeconds(1));

// 3. Task.WhenAll — wait for all tasks (parallel)
var tasks = urls.Select(url => httpClient.GetStringAsync(url));
string[] responses = await Task.WhenAll(tasks);

// 4. Task.WhenAny — first task to complete
var timeout = Task.Delay(5000);
var work    = DoWorkAsync();
if (await Task.WhenAny(work, timeout) == timeout)
    throw new TimeoutException();

// 5. Task<T> — task with return value
Task<int> countTask = CountItemsAsync();
int count = await countTask;

// 6. TaskCompletionSource — wrap callback-based APIs
Task<string> WrapCallback()
{
    var tcs = new TaskCompletionSource<string>();
    LegacyLibrary.DoWork(
        onSuccess: result => tcs.SetResult(result),
        onError:   ex     => tcs.SetException(ex));
    return tcs.Task;
}

// 7. Task.FromResult / Task.CompletedTask — completed tasks (no allocation for common values)
Task<int> zero = Task.FromResult(0);
Task done      = Task.CompletedTask;

// 8. Chaining with ContinueWith (prefer await over this)
Task<string> chain = Task.Run(() => 42)
    .ContinueWith(t => $"Result: {t.Result}");
Console.WriteLine(await chain);

// 9. Task cancellation
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
    await LongRunningAsync(cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Cancelled after 5 seconds");
}
↥ back to top

Q. How do you optimize I/O operations in .NET Core applications?

// 1. Always use async I/O — never block on file/network/DB
//  Blocks a thread pool thread
string text = File.ReadAllText("file.txt");

// … Async — thread returns to pool during I/O wait
string text2 = await File.ReadAllTextAsync("file.txt");

// 2. Buffer reads/writes — reduce syscall count
await using var reader = new StreamReader(
    new FileStream("large.csv", FileMode.Open, FileAccess.Read,
        FileShare.Read, bufferSize: 65536, useAsync: true));
string? line;
while ((line = await reader.ReadLineAsync()) is not null)
    Process(line);

// 3. Pipelines API — zero-copy parsing (System.IO.Pipelines)
using System.IO.Pipelines;

async Task ProcessPipeAsync(PipeReader reader, CancellationToken ct)
{
    while (true)
    {
        ReadResult result = await reader.ReadAsync(ct);
        ReadOnlySequence<byte> buffer = result.Buffer;
        // Process buffer without copying
        reader.AdvanceTo(buffer.End);
        if (result.IsCompleted) break;
    }
}

// 4. HttpClient — reuse, configure timeouts, use IHttpClientFactory
builder.Services.AddHttpClient("api", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
    client.Timeout = TimeSpan.FromSeconds(10);
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

// 5. Parallel downloads with bounded parallelism
var semaphore = new SemaphoreSlim(10); // max 10 concurrent downloads
var tasks = urls.Select(async url =>
{
    await semaphore.WaitAsync();
    try   { return await client.GetStringAsync(url); }
    finally { semaphore.Release(); }
});
string[] results = await Task.WhenAll(tasks);

// 6. SocketsHttpHandler — fine-grained connection pool tuning
var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime     = TimeSpan.FromMinutes(2),
    PooledConnectionIdleTimeout  = TimeSpan.FromMinutes(1),
    MaxConnectionsPerServer      = 20,
    EnableMultipleHttp2Connections = true,
};
↥ back to top

Q. What are some best practices for optimizing disk and network I/O?

// DISK I/O

// 1. Async file operations
await File.WriteAllTextAsync("output.txt", content);
await File.AppendAllTextAsync("log.txt", $"{DateTime.UtcNow}: event\n");

// 2. File.OpenHandle + RandomAccess (.NET 6+) — best performance, no Stream overhead
using var handle = File.OpenHandle("data.bin",
    FileMode.Open, FileAccess.Read, FileShare.Read,
    FileOptions.Asynchronous | FileOptions.SequentialScan);
var buffer = new byte[4096];
int read = await RandomAccess.ReadAsync(handle, buffer, fileOffset: 0);

// 3. MemoryMappedFile — for very large files or shared memory
using var mmf = MemoryMappedFile.CreateFromFile("huge.dat");
using var accessor = mmf.CreateViewAccessor(offset: 0, size: 4096);
byte value = accessor.ReadByte(0);

// NETWORK I/O

// 4. Reuse HttpClient via IHttpClientFactory (never new HttpClient() per request)
public class MyService(IHttpClientFactory factory)
{
    public async Task<string> GetAsync(string url)
    {
        using var client = factory.CreateClient("api");
        return await client.GetStringAsync(url);
    }
}

// 5. HTTP/2 and HTTP/3 reduce connection overhead
builder.WebHost.ConfigureKestrel(opts =>
    opts.ListenLocalhost(8080, o =>
        o.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core
            .HttpProtocols.Http1AndHttp2AndHttp3));

// 6. Response compression — reduce bytes over the wire
builder.Services.AddResponseCompression(opts =>
{
    opts.EnableForHttps = true;
    opts.Providers.Add<BrotliCompressionProvider>();
    opts.Providers.Add<GzipCompressionProvider>();
});
app.UseResponseCompression();

// 7. Output caching — serve cached responses without hitting the app
builder.Services.AddOutputCache();
app.UseOutputCache();
app.MapGet("/products", GetProducts).CacheOutput(p => p.Expire(TimeSpan.FromMinutes(5)));
↥ back to top

Q. How do you use caching to improve performance in .NET Core applications?

// 1. IMemoryCache — in-process, fast
builder.Services.AddMemoryCache();

public class ProductService(IMemoryCache cache, IProductRepository repo)
{
    public async Task<Product?> GetByIdAsync(int id)
    {
        return await cache.GetOrCreateAsync($"product:{id}", async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
            entry.SlidingExpiration               = TimeSpan.FromMinutes(1);
            entry.Priority                        = CacheItemPriority.Normal;
            return await repo.GetByIdAsync(id);
        });
    }
}

// 2. Output caching (.NET 7+) — cache full HTTP responses
builder.Services.AddOutputCache(options =>
{
    options.AddPolicy("ProductsCache", b => b
        .Expire(TimeSpan.FromMinutes(10))
        .SetVaryByQuery("category", "page")
        .Tag("products"));
});
app.UseOutputCache();

app.MapGet("/products", async (AppDbContext db) =>
    await db.Products.ToListAsync())
  .CacheOutput("ProductsCache");

// Evict by tag when data changes
app.MapPost("/products", async (Product p, IOutputCacheStore store, CancellationToken ct) =>
{
    // ... save product ...
    await store.EvictByTagAsync("products", ct); // invalidate cached /products responses
    return Results.Created($"/products/{p.Id}", p);
});
↥ back to top

Q. What is the IMemoryCache interface and how is it used?

IMemoryCache is the ASP.NET Core in-process cache abstraction. It stores key-value pairs in the process's memory with expiry policies.

// Register
builder.Services.AddMemoryCache(options =>
{
    options.SizeLimit = 1024;               // max 1024 size units
    options.CompactionPercentage = 0.25;    // remove 25% when full
});

// Inject and use
public class WeatherService(IMemoryCache cache)
{
    private static readonly string CacheKey = "weather:london";

    // Pattern 1: GetOrCreate (most common)
    public async Task<WeatherData> GetWeatherAsync()
    {
        return await cache.GetOrCreateAsync(CacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
            entry.SlidingExpiration               = TimeSpan.FromMinutes(3);
            entry.Size                            = 1;  // counts toward SizeLimit
            entry.RegisterPostEvictionCallback((key, value, reason, state) =>
                Console.WriteLine($"Cache evicted: {key}, reason: {reason}"));

            return await FetchFromApiAsync();
        }) ?? throw new InvalidOperationException();
    }

    // Pattern 2: TryGetValue + Set
    public WeatherData? GetCachedWeather()
    {
        if (cache.TryGetValue(CacheKey, out WeatherData? data))
            return data;
        return null;
    }

    // Pattern 3: explicit Set
    public void SetWeather(WeatherData data)
    {
        var options = new MemoryCacheEntryOptions
        {
            AbsoluteExpiration = DateTimeOffset.UtcNow.AddHours(1),
            Priority = CacheItemPriority.High,
        };
        cache.Set(CacheKey, data, options);
    }

    // Invalidate
    public void InvalidateWeather() => cache.Remove(CacheKey);
}
↥ back to top

Q. How do you use distributed caching in .NET Core?

Distributed caching stores data outside the process — shared across multiple app instances. ASP.NET Core provides IDistributedCache backed by Redis, SQL Server, or NCache.

// 1. Redis distributed cache (recommended for production)
// dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration["Redis:ConnectionString"];
    options.InstanceName  = "MyApp:";
});

// 2. SQL Server distributed cache
// dotnet add package Microsoft.Extensions.Caching.SqlServer
builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString("Default");
    options.SchemaName = "dbo";
    options.TableName  = "Cache";
});

// 3. In-memory (development / single-instance only)
builder.Services.AddDistributedMemoryCache();

// Usage via IDistributedCache
public class SessionService(IDistributedCache cache)
{
    public async Task SetUserDataAsync(string userId, UserData data, CancellationToken ct)
    {
        var json  = JsonSerializer.SerializeToUtf8Bytes(data);
        var opts  = new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
            SlidingExpiration               = TimeSpan.FromMinutes(20),
        };
        await cache.SetAsync($"user:{userId}", json, opts, ct);
    }

    public async Task<UserData?> GetUserDataAsync(string userId, CancellationToken ct)
    {
        byte[]? bytes = await cache.GetAsync($"user:{userId}", ct);
        return bytes is null ? null : JsonSerializer.Deserialize<UserData>(bytes);
    }

    public async Task RemoveAsync(string userId, CancellationToken ct) =>
        await cache.RemoveAsync($"user:{userId}", ct);
}
↥ back to top

Q. What is the IDistributedCache interface and how is it used?

IDistributedCache is the abstraction for distributed caching in ASP.NET Core. It stores byte[] values by string key with expiry support.

// Core methods:
// Get / GetAsync           — retrieve bytes (null if not found)
// Set / SetAsync           — store bytes with options
// Refresh / RefreshAsync   — reset sliding expiration
// Remove / RemoveAsync     — delete entry

public class ProductCacheService(IDistributedCache cache)
{
    private static string Key(int id) => $"product:{id}";

    public async Task<Product?> GetAsync(int id, CancellationToken ct = default)
    {
        byte[]? bytes = await cache.GetAsync(Key(id), ct);
        return bytes is null
            ? null
            : JsonSerializer.Deserialize<Product>(bytes);
    }

    public async Task SetAsync(Product product, CancellationToken ct = default)
    {
        byte[] bytes = JsonSerializer.SerializeToUtf8Bytes(product);
        await cache.SetAsync(Key(product.Id), bytes,
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
                SlidingExpiration               = TimeSpan.FromMinutes(5),
            }, ct);
    }

    // Get-or-create pattern
    public async Task<Product> GetOrCreateAsync(int id,
        Func<CancellationToken, Task<Product>> factory, CancellationToken ct = default)
    {
        var cached = await GetAsync(id, ct);
        if (cached is not null) return cached;

        var product = await factory(ct);
        await SetAsync(product, ct);
        return product;
    }
}

// Extension method (HybridCache — .NET 9+, recommended)
// dotnet add package Microsoft.Extensions.Caching.Hybrid
builder.Services.AddHybridCache();

// HybridCache combines IMemoryCache (L1) + IDistributedCache (L2) automatically
public class CachedService(HybridCache hybridCache)
{
    public async Task<Product?> GetProductAsync(int id, CancellationToken ct)
        => await hybridCache.GetOrCreateAsync(
            $"product:{id}",
            async token => await FetchFromDbAsync(id, token),
            cancellationToken: ct);
}
↥ back to top

Q. How do you use the ResponseCaching middleware to cache responses?

Response caching stores complete HTTP responses and serves them for subsequent identical requests — reduces server-side processing.

// ResponseCaching — HTTP cache headers based (RFC 7234)
builder.Services.AddResponseCaching(options =>
{
    options.MaximumBodySize = 64 * 1024; // max cached body = 64 KB
    options.UseCaseSensitivePaths = false;
});

app.UseResponseCaching();

// Controller — add cache headers
[ResponseCache(Duration = 60, VaryByQueryKeys = ["category"])]
[HttpGet]
public IActionResult GetProducts(string? category) => Ok();

// Minimal API
app.MapGet("/products", async (AppDbContext db) =>
    await db.Products.ToListAsync())
  .WithMetadata(new ResponseCacheAttribute
  {
      Duration = 300,         // cache for 5 minutes
      Location = ResponseCacheLocation.Any,
      VaryByHeader = "Accept-Language",
  });

//  ResponseCaching has limitations:
// - Only caches GET/HEAD responses with 200 status
// - Does NOT work with authenticated requests by default
// - Cannot be invalidated programmatically

// … Output Caching (.NET 7+) — recommended replacement
builder.Services.AddOutputCache(opts =>
{
    opts.AddBasePolicy(b => b.Expire(TimeSpan.FromMinutes(5)));
    opts.AddPolicy("Short", b => b.Expire(TimeSpan.FromSeconds(30)));
});
app.UseOutputCache();

app.MapGet("/products", GetProducts).CacheOutput();
app.MapGet("/prices",   GetPrices).CacheOutput("Short");

// Invalidate output cache programmatically
app.MapPost("/products", async (Product p, IOutputCacheStore store, CancellationToken ct) =>
{
    // ... save ...
    await store.EvictByTagAsync("products", ct);
    return Results.Created($"/products/{p.Id}", p);
});
↥ back to top

Q. How do you optimize database access in .NET Core applications?

// 1. AsNoTracking — skip change tracking for read-only queries
var products = await db.Products
    .AsNoTracking()
    .Where(p => p.Category == "Electronics")
    .ToListAsync();

// 2. Projection — fetch only needed columns
var dtos = await db.Products
    .AsNoTracking()
    .Select(p => new ProductDto(p.Id, p.Name, p.Price))
    .ToListAsync();

// 3. Compiled queries — avoid repeated query compilation (.NET 7+)
private static readonly Func<AppDbContext, int, Task<Product?>> _getById =
    EF.CompileAsyncQuery((AppDbContext db, int id) =>
        db.Products.AsNoTracking().FirstOrDefault(p => p.Id == id));

var product = await _getById(db, 42);

// 4. Batch operations — EF Core 7+ ExecuteUpdate / ExecuteDelete
//  Load-modify-save (N round trips)
var prods = await db.Products.Where(p => p.Price < 10).ToListAsync();
foreach (var p in prods) p.Price *= 1.1m;
await db.SaveChangesAsync();

// … Single SQL UPDATE
await db.Products
    .Where(p => p.Price < 10)
    .ExecuteUpdateAsync(s => s.SetProperty(p => p.Price, p => p.Price * 1.1m));

// … Single SQL DELETE
await db.Products.Where(p => p.Stock == 0).ExecuteDeleteAsync();

// 5. Eager loading to avoid N+1
var orders = await db.Orders
    .Include(o => o.Customer)
    .Include(o => o.Lines).ThenInclude(l => l.Product)
    .AsNoTracking()
    .ToListAsync();

// 6. Pagination — never load all rows
var page = await db.Products
    .OrderBy(p => p.Name)
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize)
    .AsNoTracking()
    .ToListAsync();

// 7. Raw SQL for complex queries
var results = await db.Database
    .SqlQuery<ProductSummary>($"SELECT Id, Name, SUM(Qty) AS TotalSold FROM ...")
    .ToListAsync();
↥ back to top

Q. How do you use connection pooling to improve database performance?

Connection pooling reuses existing database connections rather than creating a new one per request. ADO.NET and EF Core use pooling by default — the key is configuring it correctly.

// 1. ADO.NET — pooling is automatic when using connection strings
// The connection string controls pool size:
var connStr = "Server=myserver;Database=mydb;User=sa;Password=pass;" +
              "Min Pool Size=5;Max Pool Size=100;Connection Timeout=30;";

// Open/Close does NOT destroy the connection — it returns it to the pool
await using var conn = new SqlConnection(connStr);
await conn.OpenAsync();
// ... query ...
// conn.Close() / dispose ’ returned to pool

// 2. EF Core — uses ADO.NET pooling automatically
builder.Services.AddDbContext<AppDbContext>(opt =>
    opt.UseSqlServer(connStr,
        sql => sql.CommandTimeout(30)));

// 3. DbContext pooling — EF Core level (reuse DbContext instances, .NET 6+)
// Use AddDbContextPool instead of AddDbContext
builder.Services.AddDbContextPool<AppDbContext>(opt =>
    opt.UseSqlServer(connStr), poolSize: 128);

// With factory (for Blazor, manual scope control)
builder.Services.AddPooledDbContextFactory<AppDbContext>(opt =>
    opt.UseSqlServer(connStr));

// Usage with factory
public class ProductService(IDbContextFactory<AppDbContext> factory)
{
    public async Task<List<Product>> GetAllAsync()
    {
        await using var db = await factory.CreateDbContextAsync();
        return await db.Products.AsNoTracking().ToListAsync();
    }
}

// 4. Monitor pool health
// dotnet-counters monitor -p <PID> Microsoft.Data.SqlClient
// Watch: active-hard-connects, active-soft-connects, number-of-pooled-connections

// 5. Connection resiliency (transient fault handling)
builder.Services.AddDbContext<AppDbContext>(opt =>
    opt.UseSqlServer(connStr, sql =>
        sql.EnableRetryOnFailure(
            maxRetryCount: 3,
            maxRetryDelay: TimeSpan.FromSeconds(5),
            errorNumbersToAdd: null)));
↥ back to top

Q. How do you use the AsNoTracking method to optimize query performance?

AsNoTracking() tells EF Core to skip change tracking for returned entities — the context does not monitor them for modifications. This saves memory and CPU for read-only queries.

//  Default — EF Core tracks all returned entities (needed for updates)
var tracked = await db.Products.ToListAsync();
// EF holds a snapshot of each entity for change detection

// … AsNoTracking — no snapshot stored, faster and less memory
var readOnly = await db.Products
    .AsNoTracking()
    .ToListAsync();

// … AsNoTrackingWithIdentityResolution — avoids duplicates in navigation props
// Useful when Include() returns the same entity multiple times
var orders = await db.Orders
    .AsNoTrackingWithIdentityResolution()
    .Include(o => o.Lines)
    .ThenInclude(l => l.Product)
    .ToListAsync();

// … Global setting — all queries in this context are non-tracked
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

// Benchmark context (queries per second):
// With tracking    : ~15,000 QPS
// AsNoTracking     : ~25,000 QPS  (+67%)
// Projection only  : ~35,000 QPS  (+133%)

// When NOT to use AsNoTracking:
//  When you need to modify and save the entity
var product = await db.Products.FindAsync(id); // tracked — needed for update
product!.Price = newPrice;
await db.SaveChangesAsync(); // EF detects change via tracking

// AsNoTracking + manual update (EF Core 7+)
await db.Products
    .Where(p => p.Id == id)
    .ExecuteUpdateAsync(s => s.SetProperty(p => p.Price, newPrice));
// No loading needed — single UPDATE statement
↥ back to top

Q. What are the secure coding practices (XSS, CSRF, SQL Injection) available in .NET?

// ” 1. XSS (Cross-Site Scripting) ”——————————————————————————————————
// ASP.NET Core Razor auto-encodes output by default
@Model.UserInput  // HTML-encoded automatically — safe

// HtmlEncoder for manual encoding
using System.Text.Encodings.Web;
string safe = HtmlEncoder.Default.Encode("<script>alert(1)</script>");
// ’ &lt;script&gt;alert(1)&lt;/script&gt;

// Content Security Policy header
app.Use(async (ctx, next) =>
{
    ctx.Response.Headers.Append("Content-Security-Policy",
        "default-src 'self'; script-src 'self'; style-src 'self'");
    await next(ctx);
});

// ” 2. CSRF (Cross-Site Request Forgery) ”———————————————————————————
// MVC — Antiforgery token (automatic with [ValidateAntiForgeryToken])
builder.Services.AddAntiforgery(opt =>
{
    opt.HeaderName = "X-XSRF-TOKEN";  // for SPA clients
    opt.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    opt.Cookie.SameSite = SameSiteMode.Strict;
});

// Controller
[HttpPost, ValidateAntiForgeryToken]
public IActionResult Create(ProductForm form) { /* safe */ return Ok(); }

// Minimal API — SameSite cookie + antiforgery
app.MapPost("/products", (IAntiforgery af, HttpContext ctx) =>
{
    af.ValidateRequestAsync(ctx);
    // ...
}).RequireAuthorization();

// ” 3. SQL Injection ”————————————————————————————————————————————————
//  Vulnerable — string interpolation into SQL
var name = userInput;
var sql = $"SELECT * FROM Products WHERE Name = '{name}'";  // NEVER DO THIS

// … EF Core — parameterised automatically
var products = await db.Products
    .Where(p => p.Name == name)
    .ToListAsync();

// … Raw SQL with parameters (EF Core 7+)
var results = await db.Products
    .FromSql($"SELECT * FROM Products WHERE Name = {name}")  // interpolated ’ parameterised
    .ToListAsync();

// … ADO.NET — explicit parameters
await using var cmd = new SqlCommand("SELECT * FROM Products WHERE Name = @name", conn);
cmd.Parameters.AddWithValue("@name", name);

// ” 4. Additional practices ”—————————————————————————————————————————
// Enforce HTTPS
app.UseHttpsRedirection();
app.UseHsts(); // HTTP Strict Transport Security

// Secure cookies
builder.Services.ConfigureApplicationCookie(opts =>
{
    opts.Cookie.HttpOnly   = true;
    opts.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    opts.Cookie.SameSite  = SameSiteMode.Strict;
});

// Security headers
app.Use(async (ctx, next) =>
{
    ctx.Response.Headers.Append("X-Content-Type-Options", "nosniff");
    ctx.Response.Headers.Append("X-Frame-Options", "DENY");
    ctx.Response.Headers.Append("Referrer-Policy", "no-referrer");
    await next(ctx);
});

// Input validation at boundary
app.MapPost("/products", ([FromBody] CreateProductRequest req) =>
{
    // Data annotations validated by [ApiController] before action runs
    return Results.Ok();
});
↥ back to top

Q. What is Span<T> and Memory<T> and when should you use them?

Span<T> and Memory<T> are stack-friendly, allocation-free views over contiguous memory. They enable high-performance parsing and buffer manipulation without heap allocations.

using System;
using System.Buffers;

// ” Span<T> — stack-only, synchronous code ”——————————————————————————
// Points to: stack memory (stackalloc), array slices, or unmanaged memory
// Cannot be stored in class fields or used across await boundaries

// 1. Slice an array without allocation
int[] data = { 10, 20, 30, 40, 50 };
Span<int> slice = data.AsSpan(1, 3); // no copy — just a view of [20, 30, 40]
Console.WriteLine(slice[0]); // 20
slice[0] = 99;               // mutates the original array
Console.WriteLine(data[1]);  // 99

// 2. Stack allocation — zero heap allocation
Span<byte> buffer = stackalloc byte[256];
int length = Encoding.UTF8.GetBytes("Hello, World!", buffer);
string result = Encoding.UTF8.GetString(buffer[..length]);
Console.WriteLine(result); // Hello, World!

// 3. High-performance string parsing (no substring allocations)
ReadOnlySpan<char> csv = "Alice,30,Engineer".AsSpan();
int comma1 = csv.IndexOf(',');
ReadOnlySpan<char> name = csv[..comma1];       // "Alice" — no allocation
ReadOnlySpan<char> rest = csv[(comma1 + 1)..];
int comma2 = rest.IndexOf(',');
ReadOnlySpan<char> age = rest[..comma2];       // "30"
Console.WriteLine(name.ToString()); // Alice

// 4. Span<T> in methods
static int SumSpan(ReadOnlySpan<int> numbers)
{
    int total = 0;
    foreach (var n in numbers) total += n;
    return total;
}
int[] arr = [1, 2, 3, 4, 5];
Console.WriteLine(SumSpan(arr));           // works with array
Console.WriteLine(SumSpan(arr.AsSpan(1, 3))); // slice without allocation

// ” Memory<T> — can cross await, can be stored in fields ”————————————
// Use Memory<T> when you need to store the view or use it with async code
public class DataProcessor
{
    private readonly Memory<byte> _buffer;

    public DataProcessor(byte[] data) => _buffer = data.AsMemory();

    public async Task ProcessAsync(CancellationToken ct)
    {
        // Memory<T> CAN cross await — Span<T> cannot
        await Task.Delay(10, ct);

        Span<byte> span = _buffer.Span; // get Span for synchronous work
        span[0] = 0xFF;
    }
}

// ” MemoryPool<T> and ArrayPool<T> — reuse buffers ”——————————————————
// ArrayPool<T>.Shared — pool of reusable byte arrays
async Task ProcessRequestAsync(Stream requestStream)
{
    byte[] buffer = ArrayPool<byte>.Shared.Rent(4096); // borrow from pool
    try
    {
        int read = await requestStream.ReadAsync(buffer.AsMemory(0, 4096));
        // Process buffer[0..read]
        var data = buffer.AsSpan(0, read);
        // ...
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(buffer, clearArray: true); // return to pool
    }
}

// ” ReadOnlySequence<T> — for fragmented memory (e.g., Pipelines) ”———
// System.IO.Pipelines — zero-copy network I/O
async Task ReadPipeAsync(PipeReader reader)
{
    while (true)
    {
        ReadResult result = await reader.ReadAsync();
        ReadOnlySequence<byte> buffer = result.Buffer;

        // Parse data without copying
        if (TryParseMessage(buffer, out var message, out var consumed))
        {
            ProcessMessage(message);
            reader.AdvanceTo(consumed);
        }

        if (result.IsCompleted) break;
    }
    await reader.CompleteAsync();
}

bool TryParseMessage(ReadOnlySequence<byte> buffer, out string message, out SequencePosition consumed)
{
    message = string.Empty;
    consumed = buffer.Start;
    // ... parse logic
    return false;
}

void ProcessMessage(string msg) { }

When to use:

Type Use when
Span<T> Synchronous, hot-path, zero-allocation buffer work
ReadOnlySpan<T> Parsing strings/bytes without copying
Memory<T> Async code, stored in fields, cross-await
ArrayPool<T> Frequently allocating large temporary arrays
System.IO.Pipelines High-throughput network/stream parsing
↥ back to top

Q. How do you use ObjectPool<T> and ArrayPool<T> for object pooling?

Object pooling reuses expensive-to-create objects instead of allocating and garbage-collecting them on each use. .NET provides ArrayPool<T> for arrays and ObjectPool<T> (from Microsoft.Extensions.ObjectPool) for general objects.

using Microsoft.Extensions.ObjectPool;
using System.Buffers;
using System.Text;

// ” ArrayPool<T> — reuse arrays, avoid GC pressure ”——————————————————
public class CsvParser
{
    public List<string[]> Parse(string csvContent)
    {
        var rows = new List<string[]>();
        // Rent a buffer instead of creating new char[]
        char[] buffer = ArrayPool<char>.Shared.Rent(csvContent.Length);
        try
        {
            csvContent.CopyTo(0, buffer, 0, csvContent.Length);
            // Process buffer...
            rows.Add(new[] { "parsed", "row" }); // example
        }
        finally
        {
            ArrayPool<char>.Shared.Return(buffer, clearArray: false);
        }
        return rows;
    }
}

// ” ObjectPool<T> for StringBuilder ”——————————————————————————————————
// Register pool in DI
builder.Services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
builder.Services.AddSingleton(sp =>
    sp.GetRequiredService<ObjectPoolProvider>().CreateStringBuilderPool());

// Usage in a service
public class HtmlRenderer(ObjectPool<StringBuilder> sbPool)
{
    public string Render(IEnumerable<string> items)
    {
        StringBuilder sb = sbPool.Get(); // borrow from pool
        try
        {
            sb.Append("<ul>");
            foreach (var item in items)
                sb.Append("<li>").Append(HtmlEncode(item)).Append("</li>");
            sb.Append("</ul>");
            return sb.ToString();
        }
        finally
        {
            sbPool.Return(sb); // return (pool resets it automatically)
        }
    }

    private static string HtmlEncode(string s)
        => System.Net.WebUtility.HtmlEncode(s);
}

// ” Custom ObjectPool policy ”—————————————————————————————————————————
public class HttpClientPolicy : IPooledObjectPolicy<HttpClient>
{
    public HttpClient Create() => new HttpClient
    {
        Timeout = TimeSpan.FromSeconds(30),
        DefaultRequestHeaders = { { "User-Agent", "MyApp/1.0" } }
    };

    public bool Return(HttpClient obj)
    {
        // Reset state before returning to pool
        obj.DefaultRequestHeaders.Clear();
        return true; // return true to keep in pool, false to discard
    }
}

// Register custom pool
builder.Services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
builder.Services.AddSingleton<ObjectPool<HttpClient>>(sp =>
    sp.GetRequiredService<ObjectPoolProvider>().Create(new HttpClientPolicy()));

// ” MemoryPool<T> for streaming scenarios ”———————————————————————————
async Task ProcessStreamAsync(Stream stream, CancellationToken ct)
{
    using IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(8192);
    Memory<byte> buffer = owner.Memory[..8192];

    int read;
    while ((read = await stream.ReadAsync(buffer, ct)) > 0)
    {
        ProcessChunk(buffer.Span[..read]);
    }
}

void ProcessChunk(ReadOnlySpan<byte> data) { /* process */ }

// ” Benchmark: pool vs new allocation ”———————————————————————————————
// Without pooling: new StringBuilder() per request
// With pooling:    ~6— faster, near-zero GC for StringBuilder operations
↥ back to top

Q. How do you benchmark .NET code with BenchmarkDotNet?

BenchmarkDotNet is the standard .NET benchmarking library that measures method execution time, memory allocations, and GC pressure with statistical accuracy.

dotnet add package BenchmarkDotNet
dotnet add package BenchmarkDotNet.Diagnostics.Windows  # for memory profiling on Windows
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using System.Text;

// ” 1. Basic benchmark ”——————————————————————————————————————————————
[MemoryDiagnoser]          // track GC allocations
[SimpleJob(launchCount: 1, warmupCount: 3, iterationCount: 10)]
[RankColumn]               // show relative rank
public class StringBenchmarks
{
    private const int N = 10_000;
    private readonly string[] _words;

    public StringBenchmarks()
    {
        _words = Enumerable.Range(0, N)
            .Select(i => $"word{i}")
            .ToArray();
    }

    [Benchmark(Baseline = true)]
    public string StringConcat()
    {
        string result = "";
        foreach (var w in _words) result += w + " ";
        return result;
    }

    [Benchmark]
    public string StringBuilderAppend()
    {
        var sb = new StringBuilder();
        foreach (var w in _words) sb.Append(w).Append(' ');
        return sb.ToString();
    }

    [Benchmark]
    public string StringJoin() => string.Join(" ", _words);

    [Benchmark]
    public string StringCreate()
    {
        int totalLen = _words.Sum(w => w.Length + 1);
        return string.Create(totalLen, _words, (span, words) =>
        {
            int pos = 0;
            foreach (var w in words)
            {
                w.CopyTo(span[pos..]);
                pos += w.Length;
                span[pos++] = ' ';
            }
        });
    }
}

// ” 2. Parametrized benchmark ”———————————————————————————————————————
[MemoryDiagnoser]
public class CollectionBenchmarks
{
    [Params(100, 1_000, 10_000)]
    public int Size { get; set; }

    private int[] _data = null!;

    [GlobalSetup]
    public void Setup() => _data = Enumerable.Range(0, Size).ToArray();

    [Benchmark(Baseline = true)]
    public int LinqSum() => _data.Sum();

    [Benchmark]
    public int ForLoopSum()
    {
        int sum = 0;
        for (int i = 0; i < _data.Length; i++) sum += _data[i];
        return sum;
    }

    [Benchmark]
    public int SpanSum()
    {
        ReadOnlySpan<int> span = _data;
        int sum = 0;
        foreach (var n in span) sum += n;
        return sum;
    }

    [Benchmark]
    public int ParallelSum() => _data.AsParallel().Sum();
}

// ” 3. Advanced config with categories and filters ”——————————————————
[Config(typeof(AntiVirusFriendlyConfig))]
[CategoriesColumn]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
public class SerializationBenchmarks
{
    private static readonly object _data = new { Name = "Alice", Age = 30 };

    [Benchmark, BenchmarkCategory("JSON")]
    public string SystemTextJson()
        => System.Text.Json.JsonSerializer.Serialize(_data);

    [Benchmark, BenchmarkCategory("JSON")]
    public string NewtonsoftJson()
        => Newtonsoft.Json.JsonConvert.SerializeObject(_data);
}

class AntiVirusFriendlyConfig : ManualConfig
{
    public AntiVirusFriendlyConfig()
    {
        AddJob(BenchmarkDotNet.Jobs.Job.MediumRun
            .WithEnvironmentVariable("COMPlus_EnableAVX2", "1"));
    }
}

// ” 4. Run benchmarks ”———————————————————————————————————————————————
// Program.cs — must run in Release mode: dotnet run -c Release
class Program
{
    static void Main(string[] args)
    {
        // Run all benchmarks in the assembly
        var summary = BenchmarkSwitcher
            .FromAssembly(typeof(Program).Assembly)
            .Run(args);

        // Or run a specific class
        // BenchmarkRunner.Run<StringBenchmarks>();
    }
}

/*
Sample output:
| Method              | N      | Mean        | Allocated |
|---------------------|--------|-------------|-----------|
| StringConcat        | 10000  | 52,834.3 us | 500.1 MB  |   baseline
| StringBuilderAppend | 10000  |    281.6 us |   0.7 MB  |   187x faster
| StringJoin          | 10000  |    178.4 us |   0.4 MB  |   296x faster
| StringCreate        | 10000  |    121.3 us |   0.2 MB  |   435x faster
*/
↥ back to top

Q. How do you profile .NET applications for performance issues?

.NET provides built-in CLI tools (dotnet-trace, dotnet-counters, dotnet-dump) and integrates with Visual Studio and PerfView.

# ” Install .NET diagnostic tools ”——————————————————————————————————
dotnet tool install --global dotnet-trace
dotnet tool install --global dotnet-counters
dotnet tool install --global dotnet-dump
dotnet tool install --global dotnet-gcdump

# ” dotnet-counters — real-time metrics monitoring ”——————————————————
# List available counters
dotnet-counters list

# Monitor a running process (by PID or process name)
dotnet-counters monitor --process-id 12345 \
  --counters System.Runtime,Microsoft.AspNetCore.Hosting

# Key counters to watch:
# cpu-usage                 ’ CPU %
# gc-heap-size              ’ total managed heap
# gen-0/1/2-gc-count        ’ GC frequency
# exception-count           ’ exception rate
# threadpool-thread-count   ’ thread saturation
# requests-per-second       ’ ASP.NET Core throughput
# requests-current          ’ in-flight requests

# ” dotnet-trace — CPU sampling and event tracing ”———————————————————
# Collect CPU profile (30 seconds)
dotnet-trace collect --process-id 12345 \
  --profile cpu-sampling \
  --duration 00:00:30 \
  --output trace.nettrace

# Collect with GC events
dotnet-trace collect --process-id 12345 \
  --providers "Microsoft-Windows-DotNETRuntime:0x1:5" \
  --output gc-trace.nettrace

# Convert to speedscope format (view at speedscope.app)
dotnet-trace convert trace.nettrace --format Speedscope

# ” dotnet-dump — memory analysis ”———————————————————————————————————
# Capture memory dump
dotnet-dump collect --process-id 12345 --output dump.dmp

# Analyze dump
dotnet-dump analyze dump.dmp

# Useful commands inside the analyzer:
# dumpheap -stat          ’ objects by type and size
# dumpheap -type string   ’ all string objects
# gcroot <address>        ’ find what\'s keeping an object alive
# finalizequeue           ’ objects with finalizers
# sos threads             ’ all managed threads and stack traces

# ” dotnet-gcdump — GC heap snapshot ”———————————————————————————————
dotnet-gcdump collect --process-id 12345 --output heap.gcdump
# Open in Visual Studio or dotnet-gcdump report heap.gcdump
// ” In-code diagnostics ”——————————————————————————————————————————————
using System.Diagnostics;
using System.Diagnostics.Metrics;

// 1. Activity (distributed tracing)
private static readonly ActivitySource _activitySource = new("MyApp.OrderService");

public async Task<Order> ProcessOrderAsync(Guid orderId, CancellationToken ct)
{
    using var activity = _activitySource.StartActivity("ProcessOrder");
    activity?.SetTag("order.id", orderId.ToString());
    activity?.SetTag("order.source", "api");

    try
    {
        var order = await GetOrderAsync(orderId, ct);
        activity?.SetTag("order.total", order.Total);
        activity?.SetStatus(ActivityStatusCode.Ok);
        return order;
    }
    catch (Exception ex)
    {
        activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
        activity?.RecordException(ex);
        throw;
    }
}

// 2. Custom metrics with System.Diagnostics.Metrics (.NET 8+)
private static readonly Meter _meter = new("MyApp.OrderService", "1.0");
private static readonly Counter<long> _ordersPlaced = _meter.CreateCounter<long>(
    "orders.placed",
    description: "Total orders placed");
private static readonly Histogram<double> _processingTime = _meter.CreateHistogram<double>(
    "orders.processing_time_ms",
    unit: "ms",
    description: "Order processing time");

public async Task<Order> PlaceOrderAsync(PlaceOrderRequest req, CancellationToken ct)
{
    var sw = Stopwatch.StartNew();
    try
    {
        var order = await CreateOrderInternalAsync(req, ct);
        _ordersPlaced.Add(1, new TagList { { "status", "success" } });
        return order;
    }
    catch
    {
        _ordersPlaced.Add(1, new TagList { { "status", "error" } });
        throw;
    }
    finally
    {
        _processingTime.Record(sw.Elapsed.TotalMilliseconds);
    }
}

// 3. EventCounters for lightweight runtime monitoring
public class RequestEventCounters : EventSource
{
    public static readonly RequestEventCounters Log = new();
    private EventCounter? _requestDuration;
    private IncrementingEventCounter? _requestCount;

    protected override void OnEventSourceCreated()
    {
        _requestDuration = new EventCounter("request-duration", this);
        _requestCount    = new IncrementingEventCounter("request-count", this);
    }

    public void RecordRequest(double durationMs)
    {
        _requestDuration?.WriteMetric(durationMs);
        _requestCount?.Increment();
    }
}

Common profiling tools:

Tool Best for
dotnet-counters Live monitoring, CPU/GC/requests
dotnet-trace CPU flame graphs, hot method identification
dotnet-dump Memory leaks, large heap analysis
Visual Studio Profiler Detailed call tree, allocation tracking
PerfView Advanced ETW/GC event analysis
JetBrains dotMemory Memory snapshots, object retention
Application Insights Production distributed tracing
↥ back to top

# 16. DEPLOYMENT


Q. What is the dotnet publish command and how is it used?

dotnet publish compiles the application and copies the output (binaries, dependencies, assets) to a folder ready for deployment.

# Framework-dependent (requires runtime on target machine — smaller output)
dotnet publish -c Release -o ./publish

# Self-contained (includes the runtime — no .NET needed on target)
dotnet publish -c Release -r linux-x64 --self-contained -o ./publish

# Single-file executable (everything packed into one .exe/.bin)
dotnet publish -c Release -r win-x64 \
  --self-contained \
  -p:PublishSingleFile=true \
  -p:IncludeNativeLibrariesForSelfExtract=true \
  -o ./publish

# Native AOT (.NET 7+ — no JIT, fast startup, small binary)
dotnet publish -c Release -r linux-x64 -p:PublishAot=true -o ./publish

# Trim unused code (reduces binary size)
dotnet publish -c Release -r linux-x64 --self-contained \
  -p:PublishTrimmed=true -o ./publish

# ReadyToRun — pre-JIT code for faster startup (not as small as AOT)
dotnet publish -c Release -r win-x64 --self-contained \
  -p:PublishReadyToRun=true -o ./publish

Or configure in .csproj:

<PropertyGroup>
  <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
  <SelfContained>true</SelfContained>
  <PublishSingleFile>true</PublishSingleFile>
  <PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
↥ back to top

Q. What is the WebHostBuilder class and how is it used?

WebHostBuilder is the legacy (.NET Core 1.x–2.x) host builder. In .NET 6+, it was superseded by WebApplication.CreateBuilder() (minimal hosting model). The older Host.CreateDefaultBuilder() + ConfigureWebHostDefaults() pattern from .NET 3.1–5 is also still supported.

//  Legacy — .NET Core 2.x WebHostBuilder (avoid in new code)
public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .Build();

//   .NET 3.1 / 5 — Generic Host + ConfigureWebHostDefaults
Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
        webBuilder.UseKestrel(opts => opts.Limits.MaxRequestBodySize = 10 * 1024 * 1024);
    })
    .Build().Run();

// … .NET 10 — WebApplication.CreateBuilder (current best practice)
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(opts =>
    opts.Limits.MaxConcurrentConnections = 1000);

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.MapControllers();
app.Run();
↥ back to top

Q. How do you deploy a .NET Core application to Azure?

# 1. Azure App Service — simplest option
# Publish directly from CLI
dotnet publish -c Release -o ./publish
az login
az webapp deployment source config-zip \
  --resource-group myRG \
  --name myAppService \
  --src ./publish.zip

# Or use the Azure Web Apps deploy action (see GitHub Actions below)
# 2. GitHub Actions — deploy to Azure App Service
name: Deploy to Azure
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Setup .NET 10
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '10.0.x'

    - name: Publish
      run: dotnet publish -c Release -o ./publish

    - name: Deploy to Azure App Service
      uses: azure/webapps-deploy@v3
      with:
        app-name: 'myAppService'
        publish-profile: $
        package: ./publish
# 3. Azure Container Apps — deploy as Docker container
docker build -t myapp:latest .
az acr login --name myRegistry
docker tag myapp:latest myregistry.azurecr.io/myapp:latest
docker push myregistry.azurecr.io/myapp:latest

az containerapp update \
  --name myapp \
  --resource-group myRG \
  --image myregistry.azurecr.io/myapp:latest

# 4. Azure Kubernetes Service (AKS)
kubectl apply -f deployment.yaml
kubectl set image deployment/myapp myapp=myregistry.azurecr.io/myapp:v2
↥ back to top

Q. What is the Azure App Service and how is it used?

Azure App Service is a fully managed PaaS (Platform-as-a-Service) for hosting web apps, REST APIs, and mobile backends. It handles OS patching, scaling, SSL, and load balancing automatically.

# Create and deploy via Azure CLI
az group create --name myRG --location uksouth

az appservice plan create \
  --name myPlan \
  --resource-group myRG \
  --sku B1 \          # Free(F1), Basic(B1), Standard(S1), Premium(P1v3)
  --is-linux

az webapp create \
  --resource-group myRG \
  --plan myPlan \
  --name my-dotnet-app \
  --runtime "DOTNET|10.0"

# Deploy a ZIP package
dotnet publish -c Release -o ./publish
Compress-Archive ./publish/* publish.zip
az webapp deploy \
  --resource-group myRG \
  --name my-dotnet-app \
  --src-path publish.zip

# Set environment variables / app settings
az webapp config appsettings set \
  --resource-group myRG \
  --name my-dotnet-app \
  --settings \
    ASPNETCORE_ENVIRONMENT=Production \
    ConnectionStrings__Default="Server=mydb.database.windows.net;..."

# Enable auto-scaling (Standard plan or higher)
az monitor autoscale create \
  --resource-group myRG \
  --resource my-dotnet-app \
  --resource-type Microsoft.Web/serverfarms \
  --name myAutoscale \
  --min-count 1 --max-count 5 --count 1
// Read App Service environment variables in code (same as any config)
var builder = WebApplication.CreateBuilder(args);
// App settings from Azure portal automatically appear as environment variables
string? dbConn = builder.Configuration.GetConnectionString("Default");
string? env    = builder.Configuration["ASPNETCORE_ENVIRONMENT"];
↥ back to top

Q. How do you use Docker to containerize a .NET Core application?

# Dockerfile — multi-stage build for minimal final image (.NET 10)
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# Restore dependencies (cached layer)
COPY *.csproj ./
RUN dotnet restore

# Build and publish
COPY . ./
RUN dotnet publish -c Release -o /app/publish --no-restore

# Runtime image — smaller than sdk image
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .

# Non-root user (security best practice)
RUN adduser --disabled-password appuser
USER appuser

EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "MyApp.dll"]
# Build image
docker build -t myapp:1.0 .

# Run locally
docker run -d -p 8080:8080 \
  -e ASPNETCORE_ENVIRONMENT=Development \
  -e ConnectionStrings__Default="Server=host.docker.internal;..." \
  --name myapp myapp:1.0

# Test
curl http://localhost:8080/health

# Push to registry
docker tag myapp:1.0 myregistry.azurecr.io/myapp:1.0
docker push myregistry.azurecr.io/myapp:1.0
# docker-compose.yml — local dev with database
version: '3.8'
services:
  api:
    build: .
    ports: ["8080:8080"]
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__Default=Server=db;Database=MyDb;User=sa;Password=Pass@123
    depends_on: [db]

  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      SA_PASSWORD: "Pass@123"
      ACCEPT_EULA: "Y"
    ports: ["1433:1433"]
↥ back to top

Q. How do you create a Dockerfile for a .NET Core application?

# Dockerfile — production-grade .NET 10 Web API

# ” Stage 1: Restore ”————————————————————————————————————————
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore
WORKDIR /src
COPY ["MyApi/MyApi.csproj", "MyApi/"]
COPY ["MyApi.Core/MyApi.Core.csproj", "MyApi.Core/"]
RUN dotnet restore "MyApi/MyApi.csproj"

# ” Stage 2: Build ”——————————————————————————————————————————
FROM restore AS build
COPY . .
RUN dotnet build "MyApi/MyApi.csproj" -c Release --no-restore

# ” Stage 3: Publish ”————————————————————————————————————————
FROM build AS publish
RUN dotnet publish "MyApi/MyApi.csproj" \
    -c Release \
    --no-build \
    -o /app/publish

# ” Stage 4: Runtime (final, smallest image) ”————————————————
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final

# Security: non-root user
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
WORKDIR /app
COPY --from=publish /app/publish .

# Health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

USER appuser
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080 \
    DOTNET_RUNNING_IN_CONTAINER=true

ENTRYPOINT ["dotnet", "MyApi.dll"]
# .dockerignore — exclude unnecessary files
# .dockerignore
**/.git
**/.vs
**/bin
**/obj
**/*.user
**/node_modules
**/Dockerfile*
**/.gitignore
README.md
↥ back to top

Q. How do you deploy a .NET Core application using Docker?

# 1. Build and run locally
docker build -t myapi:latest .
docker run -d -p 8080:8080 --name myapi myapi:latest
curl http://localhost:8080/health

# 2. Push to Docker Hub
docker login
docker tag myapi:latest username/myapi:1.0
docker push username/myapi:1.0

# 3. Push to Azure Container Registry
az acr login --name myRegistry
docker tag myapi:latest myregistry.azurecr.io/myapi:1.0
docker push myregistry.azurecr.io/myapi:1.0

# 4. Deploy to Azure Container Apps
az containerapp create \
  --name myapi \
  --resource-group myRG \
  --environment myEnv \
  --image myregistry.azurecr.io/myapi:1.0 \
  --target-port 8080 \
  --ingress external \
  --min-replicas 1 \
  --max-replicas 10 \
  --cpu 0.5 --memory 1Gi

# 5. Deploy to Kubernetes (AKS)
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapi
spec:
  replicas: 3
  selector:
    matchLabels: { app: myapi }
  template:
    metadata:
      labels: { app: myapi }
    spec:
      containers:
      - name: myapi
        image: myregistry.azurecr.io/myapi:1.0
        ports:
        - containerPort: 8080
        resources:
          requests: { cpu: "100m", memory: "128Mi" }
          limits:   { cpu: "500m", memory: "512Mi" }
        livenessProbe:
          httpGet: { path: /health, port: 8080 }
          initialDelaySeconds: 10
EOF
↥ back to top

Q. What is Kubernetes and how is it used with .NET Core applications?

Kubernetes (K8s) is an open-source container orchestration platform that automates deployment, scaling, and management of containerised applications. For .NET apps it handles rolling updates, health monitoring, auto-scaling, service discovery, and secrets management.

# deployment.yaml — full .NET 10 Web API Kubernetes manifest
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapi
  labels: { app: myapi }
spec:
  replicas: 3
  selector:
    matchLabels: { app: myapi }
  template:
    metadata:
      labels: { app: myapi }
    spec:
      containers:
      - name: myapi
        image: myregistry.azurecr.io/myapi:1.0
        ports:
        - containerPort: 8080
        env:
        - name: ASPNETCORE_ENVIRONMENT
          value: Production
        - name: ConnectionStrings__Default
          valueFrom:
            secretKeyRef:
              name: myapi-secrets
              key: db-connection
        resources:
          requests: { cpu: "100m", memory: "128Mi" }
          limits:   { cpu: "500m", memory: "512Mi" }
        readinessProbe:
          httpGet: { path: /health/ready, port: 8080 }
          initialDelaySeconds: 5
        livenessProbe:
          httpGet: { path: /health/live, port: 8080 }
          initialDelaySeconds: 15
---
apiVersion: v1
kind: Service
metadata:
  name: myapi-svc
spec:
  selector: { app: myapi }
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer
# Apply manifests
kubectl apply -f deployment.yaml

# Rolling update (zero-downtime)
kubectl set image deployment/myapi myapi=myregistry.azurecr.io/myapi:1.1

# Scale
kubectl scale deployment myapi --replicas=5

# View logs
kubectl logs -l app=myapi --tail=100 -f
// Health check endpoints for Kubernetes probes
builder.Services.AddHealthChecks()
    .AddDbContextCheck<AppDbContext>();

app.MapHealthChecks("/health/live",  new() { Predicate = _ => false });   // liveness
app.MapHealthChecks("/health/ready", new() { Predicate = r => r.Tags.Contains("ready") }); // readiness
↥ back to top

Q. How do you use CI/CD pipelines to deploy .NET Core applications?

# GitHub Actions — CI/CD pipeline for .NET 10 API ’ Azure App Service
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  DOTNET_VERSION: '10.0.x'
  AZURE_WEBAPP_NAME: 'my-dotnet-api'

jobs:
  # ” CI: Build & Test ”————————————————————————————————————
  build-and-test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Setup .NET $
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: $

    - name: Restore
      run: dotnet restore

    - name: Build
      run: dotnet build -c Release --no-restore

    - name: Test
      run: dotnet test -c Release --no-build \
             --logger "trx;LogFileName=results.trx" \
             --collect:"XPlat Code Coverage"

    - name: Upload test results
      uses: actions/upload-artifact@v4
      with:
        name: test-results
        path: "**/*.trx"

    - name: Publish
      run: dotnet publish -c Release -o ./publish --no-build

    - name: Upload artifact
      uses: actions/upload-artifact@v4
      with:
        name: app
        path: ./publish

  # ” CD: Deploy (main branch only) ”——————————————————————
  deploy:
    needs: build-and-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: production

    steps:
    - name: Download artifact
      uses: actions/download-artifact@v4
      with:
        name: app
        path: ./publish

    - name: Deploy to Azure App Service
      uses: azure/webapps-deploy@v3
      with:
        app-name: $
        publish-profile: $
        package: ./publish
↥ back to top

Q. What is the Azure DevOps service and how is it used for deployment?

Azure DevOps is a Microsoft platform providing: Boards (work tracking), Repos (Git), Pipelines (CI/CD), Test Plans, and Artifacts (NuGet/npm feeds).

# azure-pipelines.yml — CI/CD for .NET 10 API
trigger:
  branches:
    include: [main]

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'
  dotnetVersion: '10.0.x'

stages:
# ” Stage 1: Build & Test ”————————————————————————————————
- stage: Build
  jobs:
  - job: BuildAndTest
    steps:
    - task: UseDotNet@2
      inputs:
        version: $(dotnetVersion)

    - script: dotnet restore
      displayName: Restore

    - script: dotnet build -c $(buildConfiguration) --no-restore
      displayName: Build

    - script: |
        dotnet test -c $(buildConfiguration) --no-build \
          --logger trx \
          --collect "XPlat Code Coverage"
      displayName: Test

    - task: PublishTestResults@2
      inputs:
        testResultsFormat: VSTest
        testResultsFiles: '**/*.trx'

    - script: dotnet publish -c $(buildConfiguration) -o $(Build.ArtifactStagingDirectory)/publish
      displayName: Publish

    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: $(Build.ArtifactStagingDirectory)/publish
        ArtifactName: drop

# ” Stage 2: Deploy to Staging ”——————————————————————————
- stage: DeployStaging
  dependsOn: Build
  jobs:
  - deployment: DeployToStaging
    environment: staging
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureWebApp@1
            inputs:
              azureSubscription: 'MyServiceConnection'
              appName: 'my-api-staging'
              package: $(Pipeline.Workspace)/drop

# ” Stage 3: Deploy to Production (with approval) ”———————
- stage: DeployProd
  dependsOn: DeployStaging
  jobs:
  - deployment: DeployToProduction
    environment: production   # configure approvals in Azure DevOps portal
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureWebApp@1
            inputs:
              azureSubscription: 'MyServiceConnection'
              appName: 'my-api-production'
              package: $(Pipeline.Workspace)/drop
↥ back to top

Q. How do you configure a build pipeline in Azure DevOps?

A build pipeline compiles code, runs tests, and produces deployable artifacts. It is defined in azure-pipelines.yml at the repo root.

# azure-pipelines.yml — Build pipeline for .NET 10
trigger:
  branches:
    include: [main, develop]
  paths:
    exclude: ['*.md', 'docs/**']

pr:
  branches:
    include: [main]

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

steps:
# 1. Use specified .NET SDK
- task: UseDotNet@2
  displayName: 'Install .NET 10 SDK'
  inputs:
    version: '10.0.x'
    includePreviewVersions: false

# 2. Restore
- script: dotnet restore --locked-mode
  displayName: 'dotnet restore'

# 3. Build
- script: dotnet build -c $(buildConfiguration) --no-restore
  displayName: 'dotnet build'

# 4. Test with coverage
- script: |
    dotnet test -c $(buildConfiguration) --no-build \
      --logger "trx;LogFileName=TestResults.trx" \
      --collect "XPlat Code Coverage" \
      -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura
  displayName: 'dotnet test'

- task: PublishTestResults@2
  inputs:
    testResultsFormat: VSTest
    testResultsFiles: '**/TestResults.trx'
    failTaskOnFailedTests: true

- task: PublishCodeCoverageResults@2
  inputs:
    summaryFileLocation: '**/coverage.cobertura.xml'

# 5. Publish
- script: dotnet publish src/MyApi -c $(buildConfiguration) --no-build -o $(Build.ArtifactStagingDirectory)
  displayName: 'dotnet publish'

# 6. Archive artifact
- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: $(Build.ArtifactStagingDirectory)
    ArtifactName: 'myapi-$(Build.BuildNumber)'
↥ back to top

Q. How do you configure a release pipeline in Azure DevOps?

A release pipeline (or deployment stage in YAML) takes a build artifact and deploys it to one or more environments, optionally with approval gates.

# Deployment stages added to azure-pipelines.yml (single YAML pipeline)
stages:
- stage: Build
  jobs:
  - job: Build
    steps:
    - script: dotnet publish -c Release -o $(Build.ArtifactStagingDirectory)
    - task: PublishBuildArtifacts@1
      inputs: { ArtifactName: drop }

- stage: DeployDev
  displayName: 'Deploy to Dev'
  dependsOn: Build
  condition: succeeded()
  variables:
    webAppName: 'myapi-dev'
  jobs:
  - deployment: Deploy
    environment: dev        # no approval needed
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureWebApp@1
            inputs:
              azureSubscription: 'MyServiceConnection'
              appName: $(webAppName)
              package: $(Pipeline.Workspace)/drop

          - task: AzureAppServiceSettings@1
            inputs:
              azureSubscription: 'MyServiceConnection'
              appName: $(webAppName)
              appSettings: |
                [
                  {"name": "ASPNETCORE_ENVIRONMENT", "value": "Development"}
                ]

- stage: DeployProd
  displayName: 'Deploy to Production'
  dependsOn: DeployDev
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
  variables:
    webAppName: 'myapi-prod'
  jobs:
  - deployment: Deploy
    environment: production   # configure approval in Environments ’ Approvals & Checks
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureWebApp@1
            displayName: 'Blue-Green slot swap'
            inputs:
              azureSubscription: 'MyServiceConnection'
              appName: $(webAppName)
              deployToSlotOrASE: true
              resourceGroupName: myRG
              slotName: staging        # deploy to staging slot first
              package: $(Pipeline.Workspace)/drop

          - task: AzureAppServiceManage@0
            displayName: 'Swap staging  production'
            inputs:
              azureSubscription: 'MyServiceConnection'
              Action: 'Swap Slots'
              WebAppName: $(webAppName)
              ResourceGroupName: myRG
              SourceSlot: staging
↥ back to top

Q. How do you use GitHub Actions for deploying .NET Core applications?

# .github/workflows/deploy.yml — Full CI/CD with GitHub Actions
name: Build, Test & Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact-path: $

    steps:
    - uses: actions/checkout@v4

    - uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '10.0.x'

    - run: dotnet restore
    - run: dotnet build -c Release --no-restore
    - run: dotnet test -c Release --no-build --logger "trx" --collect "XPlat Code Coverage"

    - uses: actions/upload-artifact@v4        # upload test results
      with:
        name: test-results
        path: "**/*.trx"

    - name: Publish
      id: publish
      run: dotnet publish -c Release -o ./publish --no-build

    - uses: actions/upload-artifact@v4
      with:
        name: app
        path: ./publish

  deploy-azure:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://my-dotnet-api.azurewebsites.net

    steps:
    - uses: actions/download-artifact@v4
      with: { name: app, path: ./publish }

    # Option A — Azure App Service
    - uses: azure/webapps-deploy@v3
      with:
        app-name: 'my-dotnet-api'
        publish-profile: $
        package: ./publish

  deploy-docker:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
    - uses: actions/checkout@v4

    # Option B — Build & push Docker image to GHCR
    - uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: $
        password: $

    - uses: docker/build-push-action@v6
      with:
        context: .
        push: true
        tags: ghcr.io/$:$
        cache-from: type=gha
        cache-to: type=gha,mode=max
↥ back to top

Q. What is the Octopus Deploy tool and how is it used?

Octopus Deploy is a release management and deployment automation tool that complements CI systems (Azure DevOps, GitHub Actions, Jenkins). It models environments, targets, deployment processes, and variable sets separately from build pipelines.

# GitHub Actions ’ Octopus Deploy integration
- name: Push package to Octopus
  uses: OctopusDeploy/push-package-action@v3
  with:
    api_key: $
    server: https://mycompany.octopus.app
    packages: myapi.1.0.$.zip

- name: Create release in Octopus
  uses: OctopusDeploy/create-release-action@v3
  with:
    api_key: $
    server: https://mycompany.octopus.app
    project: MyApi
    release_number: 1.0.$

- name: Deploy to Staging
  uses: OctopusDeploy/deploy-release-action@v3
  with:
    api_key: $
    server: https://mycompany.octopus.app
    project: MyApi
    release_number: 1.0.$
    environments: Staging

Key Octopus concepts: | Concept | Description | |———|————-| | Project | Deployment process definition (steps, variables) | | Environment | Dev / Staging / Production logical grouping | | Release | Snapshot of project + package versions | | Deployment | Running a release against an environment | | Runbook | Operational scripts (db backup, restart service) | | Variable Sets | Shared variables across projects |

↥ back to top

Q. What are the best practices for deploying .NET Core applications?

# 1. Always publish in Release configuration
dotnet publish -c Release -o ./publish

# 2. Use health checks for readiness/liveness probes
// Health checks
builder.Services.AddHealthChecks()
    .AddDbContextCheck<AppDbContext>(tags: ["ready"])
    .AddUrlGroup(new Uri("https://ext-service/health"), tags: ["ready"]);

app.MapHealthChecks("/health/live",  new() { Predicate = _ => false });
app.MapHealthChecks("/health/ready", new() { Predicate = r => r.Tags.Contains("ready") });
# 3. Secrets management — never commit secrets; use environment vars / Key Vault
az keyvault secret set --vault-name myVault --name db-password --value "s3cret"
# In app: builder.Configuration.AddAzureKeyVault(...)

# 4. Use structured logging (Serilog/OpenTelemetry)
# 5. Enable HTTPS everywhere
# 6. Multi-stage Docker builds (keep images small)
# 7. Pin runtime versions in Dockerfile FROM mcr.microsoft.com/dotnet/aspnet:10.0
# 8. Blue-green or canary deployments via deployment slots (App Service) or K8s
# 9. Database migrations — apply via startup or migration job, never in production manually
# 10. Enable AOT/ReadyToRun for faster cold starts
dotnet publish -c Release -r linux-x64 -p:PublishAot=true
// Graceful shutdown
builder.Services.Configure<HostOptions>(o =>
    o.ShutdownTimeout = TimeSpan.FromSeconds(30)); // allow in-flight requests to complete

// Environment-specific config
if (app.Environment.IsProduction())
{
    app.UseExceptionHandler("/error");
    app.UseHsts();
}
↥ back to top

Q. What are the different types of hosting models available in .NET Core?

Hosting model Description Use case
In-process (IIS) App runs inside IIS worker process (w3wp.exe) Windows IIS hosting, better performance
Out-of-process (IIS) IIS proxies to Kestrel running as separate process Isolation, Linux-compatible workflow
Kestrel (edge) Kestrel directly exposed to internet Linux, Docker, Cloud
Kestrel + reverse proxy Nginx/IIS/YARP in front of Kestrel Production recommended
Self-contained App includes .NET runtime — no SDK needed on host Offline, locked environments
Framework-dependent Uses installed .NET runtime Shared hosting, smaller package
Docker container App + runtime in container image K8s, Azure Container Apps
Native AOT Compiled to native binary, no JIT/runtime Ultra-fast startup, serverless, CLI tools
Background Service / Worker IHostedService without HTTP Message queues, scheduled jobs
// In-process IIS hosting — .csproj
// <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>

// Out-of-process IIS hosting
// <AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>

// Worker Service (no HTTP)
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<MyWorker>();
await builder.Build().RunAsync();

// Self-contained publish
// dotnet publish -r win-x64 --self-contained -o ./publish

// Native AOT
// dotnet publish -r linux-x64 -p:PublishAot=true -o ./publish

// Kestrel with Unix socket (Nginx upstream)
builder.WebHost.ConfigureKestrel(opts =>
    opts.ListenUnixSocket("/tmp/myapp.sock"));
↥ back to top

Q. How do you build a CI/CD pipeline with GitHub Actions for a .NET application?

A CI/CD pipeline automates build, test, and deployment on every push. GitHub Actions uses YAML workflow files in .github/workflows/.

# .github/workflows/ci-cd.yml
name: .NET CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  DOTNET_VERSION: '10.0.x'
  REGISTRY: ghcr.io
  IMAGE_NAME: $

jobs:
  # ” 1. BUILD & TEST ”————————————————————————————————————————————————
  build-and-test:
    name: Build and Test
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: testpassword
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports: ['5432:5432']

    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET $
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: $

      - name: Cache NuGet packages
        uses: actions/cache@v4
        with:
          path: ~/.nuget/packages
          key: $-nuget-$
          restore-keys: $-nuget-

      - name: Restore dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --no-restore -c Release

      - name: Run unit tests
        run: dotnet test --no-build -c Release \
          --filter "Category!=Integration" \
          --logger "trx;LogFileName=unit-results.trx" \
          --collect:"XPlat Code Coverage" \
          --results-directory ./test-results

      - name: Run integration tests
        env:
          ConnectionStrings__Default: "Host=localhost;Port=5432;Database=testdb;Username=postgres;Password=testpassword"
        run: dotnet test --no-build -c Release \
          --filter "Category=Integration" \
          --logger "trx;LogFileName=integration-results.trx" \
          --results-directory ./test-results

      - name: Publish test results
        uses: dorny/test-reporter@v1
        if: always()
        with:
          name: Test Results
          path: ./test-results/*.trx
          reporter: dotnet-trx

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          directory: ./test-results
          token: $

  # ” 2. CODE QUALITY ”————————————————————————————————————————————————
  code-quality:
    name: Code Analysis
    runs-on: ubuntu-latest
    needs: build-and-test

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # needed for SonarCloud

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: $

      - name: Run Roslyn analyzers
        run: dotnet build -c Release -p:TreatWarningsAsErrors=true

  # ” 3. BUILD DOCKER IMAGE ”——————————————————————————————————————————
  build-image:
    name: Build Docker Image
    runs-on: ubuntu-latest
    needs: build-and-test
    if: github.event_name != 'pull_request'

    outputs:
      image-digest: $

    steps:
      - uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: $
          username: $
          password: $

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: $/$
          tags: |
            type=ref,event=branch
            type=sha,prefix=sha-
            type=semver,pattern=

      - name: Build and push
        id: push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: $
          labels: $
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ” 4. DEPLOY TO STAGING ”———————————————————————————————————————————
  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: build-image
    environment:
      name: staging
      url: https://staging.myapp.com
    if: github.ref == 'refs/heads/develop'

    steps:
      - uses: actions/checkout@v4

      - name: Deploy to Azure Container Apps
        uses: azure/container-apps-deploy-action@v1
        with:
          appSourcePath: $
          acrName: myregistry
          containerAppName: myapp-staging
          resourceGroup: myapp-staging-rg
          imageToDeploy: $/$:sha-$

  # ” 5. DEPLOY TO PRODUCTION ”————————————————————————————————————————
  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: deploy-staging
    environment:
      name: production
      url: https://myapp.com
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy (blue-green via App Service slots)
        run: |
          az webapp deployment slot swap \
            --resource-group myapp-prod-rg \
            --name myapp \
            --slot staging \
            --target-slot production
        env:
          AZURE_CREDENTIALS: $
↥ back to top

Q. How do you create a Docker multi-stage build for a .NET application?

A multi-stage Dockerfile uses separate build and runtime images, keeping the final image small and free of SDK tools.

# ” Dockerfile ”——————————————————————————————————————————————————————
# Stage 1: Restore dependencies (cached unless .csproj changes)
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore
WORKDIR /src

# Copy only project files first — layer cached until .csproj changes
COPY ["src/Api/Api.csproj",         "src/Api/"]
COPY ["src/Core/Core.csproj",        "src/Core/"]
COPY ["src/Infrastructure/Infrastructure.csproj", "src/Infrastructure/"]
COPY ["Directory.Packages.props",    "."]
RUN dotnet restore "src/Api/Api.csproj"

# Stage 2: Build
FROM restore AS build
COPY . .
WORKDIR /src/src/Api
RUN dotnet build "Api.csproj" -c Release --no-restore -o /app/build

# Stage 3: Publish (optimized, trimmed binary)
FROM build AS publish
RUN dotnet publish "Api.csproj" \
    -c Release \
    --no-build \
    -o /app/publish \
    -p:PublishSingleFile=false \
    -p:PublishTrimmed=false

# Stage 4: Runtime image (no SDK — much smaller)
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final

# Security: run as non-root
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
WORKDIR /app
COPY --from=publish /app/publish .

# Security: drop all capabilities, run as non-root
USER appuser

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD wget --quiet --tries=1 --spider http://localhost:8080/health/live || exit 1

EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENV DOTNET_RUNNING_IN_CONTAINER=true

ENTRYPOINT ["dotnet", "Api.dll"]
# docker-compose.yml — local development
services:
  api:
    build:
      context: .
      target: final          # use 'build' stage for debugging
    ports:
      - "8080:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__Default=Host=db;Database=myapp;Username=postgres;Password=secret
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ~/.aspnet/https:/https:ro    # dev certs

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
# Build and run
docker build -t myapp:latest .
docker run -p 8080:8080 --env ASPNETCORE_ENVIRONMENT=Production myapp:latest

# Multi-platform build (for ARM64 / Apple Silicon)
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .

# Inspect image layers and size
docker history myapp:latest
docker images myapp:latest   # SDK: ~800MB ’ Runtime: ~220MB ’ Trimmed: ~80MB
↥ back to top

Q. How do you manage environment-specific configuration in .NET Core?

.NET Core uses a layered configuration system where later sources override earlier ones. Environment-specific overrides are applied automatically.

// ” Configuration loading order (last wins) ”—————————————————————————
// 1. appsettings.json                    (all environments)
// 2. appsettings.{Environment}.json      (env-specific)
// 3. User Secrets (Development only)
// 4. Environment variables
// 5. Command-line arguments

// WebApplication.CreateBuilder() sets this up automatically

// ” appsettings.json ”————————————————————————————————————————————————
{
  "Logging": { "LogLevel": { "Default": "Information" } },
  "ConnectionStrings": {
    "Default": "Host=localhost;Database=myapp;Username=postgres;Password=devpass"
  },
  "FeatureFlags": { "NewCheckout": false },
  "EmailSettings": { "SmtpHost": "localhost", "Port": 1025 }
}

// ” appsettings.Production.json ”—————————————————————————————————————
{
  "Logging": { "LogLevel": { "Default": "Warning" } },
  "FeatureFlags": { "NewCheckout": true }
  // ConnectionStrings come from environment variable, not file
}

// ” Binding configuration to strongly-typed classes ”—————————————————
public class EmailSettings
{
    public string SmtpHost { get; init; } = null!;
    public int    Port     { get; init; }
    public string? Username { get; init; }
    public string? Password { get; init; }
}

// Program.cs
builder.Services.Configure<EmailSettings>(
    builder.Configuration.GetSection("EmailSettings"));

// Or use Options pattern with validation
builder.Services.AddOptions<EmailSettings>()
    .Bind(builder.Configuration.GetSection("EmailSettings"))
    .ValidateDataAnnotations()
    .ValidateOnStart(); // validate at startup, not first use

// Usage (inject IOptions<T> or IOptionsSnapshot<T>)
public class EmailService(IOptions<EmailSettings> opts)
{
    private readonly EmailSettings _settings = opts.Value;

    public Task SendAsync(string to, string subject, string body)
    {
        Console.WriteLine($"SMTP: {_settings.SmtpHost}:{_settings.Port}");
        return Task.CompletedTask;
    }
}

// ” User Secrets (Development only — not committed to source control) ”
// dotnet user-secrets init
// dotnet user-secrets set "EmailSettings:Password" "mysecretpassword"
// Stored in: %APPDATA%\Microsoft\UserSecrets\{userSecretsId}\secrets.json

// ” Environment Variables — override any key ”————————————————————————
// Flat key: EMAILSETTINGS__PASSWORD=secret   (double underscore = section separator)
// Connection string: ConnectionStrings__Default=Host=prod-db;...

// ” Azure Key Vault (production secrets) ”————————————————————————————
if (builder.Environment.IsProduction())
{
    builder.Configuration.AddAzureKeyVault(
        new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
        new DefaultAzureCredential());
}

// Key Vault maps: "EmailSettings--SmtpHost" ’ EmailSettings:SmtpHost

// ” Feature flags ”———————————————————————————————————————————————————
builder.Services.AddFeatureManagement(builder.Configuration.GetSection("FeatureFlags"));

// Controller
public class CheckoutController(IFeatureManager features) : ControllerBase
{
    [HttpGet("checkout")]
    public async Task<IActionResult> Checkout()
    {
        if (await features.IsEnabledAsync("NewCheckout"))
            return Ok("New checkout flow");
        return Ok("Classic checkout");
    }
}

// ” IConfiguration direct access ”————————————————————————————————————
public class StartupInfo(IConfiguration config)
{
    public void Log()
    {
        string? connStr = config.GetConnectionString("Default");
        string? env     = config["ASPNETCORE_ENVIRONMENT"];
        bool    flag    = config.GetValue<bool>("FeatureFlags:NewCheckout");
    }
}
↥ back to top

# 17. .NET Core


Q. What is .NET Core?

.NET Core (now simply called .NET) is Microsoft's open-source, cross-platform successor to .NET Framework. Starting with .NET 5, the “Core” name was dropped and it became the single unified runtime. The current version is .NET 10 (2026).

Key characteristics:

# Check installed runtimes
dotnet --list-runtimes

# Check SDK versions
dotnet --list-sdks

# Current version
dotnet --version  # e.g. 10.0.100
// Minimal .NET 10 console app (top-level statements, no boilerplate)
using Microsoft.Extensions.Hosting;

var host = Host.CreateApplicationBuilder(args);
host.Services.AddHostedService<MyWorker>();
await host.Build().RunAsync();

class MyWorker : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            Console.WriteLine($"[{DateTime.UtcNow:u}] Worker running");
            await Task.Delay(1000, ct);
        }
    }
}
↥ back to top

Q. What are the main differences between .NET Framework and .NET Core?

Feature .NET Framework .NET (Core / 5+)
Platform Windows only Cross-platform
Open source Partially Fully (GitHub)
Deployment GAC / machine-wide Self-contained / side-by-side
Performance Good Significantly faster
Current status Maintenance mode (4.8.x) Active development (.NET 10)
ASP.NET System.Web (heavy) ASP.NET Core (lightweight, Kestrel)
WPF / WinForms … (Windows only)
Xamarin / MAUI  
AOT compilation   … (.NET 7+)
Containers Limited First-class Docker support
# .NET Framework — Windows only, targeting net48
<TargetFramework>net48</TargetFramework>

# .NET 10 — cross-platform
<TargetFramework>net10.0</TargetFramework>

# Multi-targeting both
<TargetFrameworks>net48;net10.0</TargetFrameworks>
// .NET 10 — self-contained publish (no runtime needed on target machine)
// dotnet publish -r linux-x64 --self-contained -p:PublishSingleFile=true

// .NET 10 — Native AOT (no JIT, instant startup)
// dotnet publish -r linux-x64 -p:PublishAot=true
↥ back to top

Q. What is the .NET Standard?

.NET Standard is a formal specification of .NET APIs that all .NET implementations must support. It was the bridge that allowed a single library to run on .NET Framework, .NET Core, Xamarin, and Unity simultaneously.

Status: With .NET 5+ unifying all platforms, .NET Standard is no longer evolving. New libraries should target net10.0 (or a specific TFM). .NET Standard 2.0 is still useful for libraries that must support legacy .NET Framework 4.6.1+.

.NET Standard .NET Framework .NET Core / .NET
2.0 4.6.1+ 2.0+
2.1 (never) 3.0+
(no 3.0) Use net10.0 TFM
<!-- Library targeting .NET Standard 2.0 — works on .NET Framework 4.6.1+ and .NET 10 -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

<!-- Modern library — no legacy support needed -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>
</Project>

<!-- Multi-target for maximum compatibility -->
<TargetFrameworks>netstandard2.0;net10.0</TargetFrameworks>
↥ back to top

Q. How do you handle configuration in a .NET Core application?

.NET uses a layered configuration system via Microsoft.Extensions.Configuration. Sources are stacked — later sources override earlier ones.

// appsettings.json
{
  "App": {
    "Name": "MyApi",
    "Timeout": 30
  },
  "ConnectionStrings": {
    "Default": "Server=localhost;Database=MyDb"
  }
}
// Program.cs (.NET 10 — WebApplication.CreateBuilder sets up config automatically)
var builder = WebApplication.CreateBuilder(args);

// Config sources (in priority order, lowest ’ highest):
// 1. appsettings.json
// 2. appsettings.{Environment}.json
// 3. Environment variables
// 4. Command-line args

// Bind to a strongly-typed options class
builder.Services.Configure<AppOptions>(
    builder.Configuration.GetSection("App"));

var app = builder.Build();

// Read raw value
string name = builder.Configuration["App:Name"]!;
string conn = builder.Configuration.GetConnectionString("Default")!;

// Inject IOptions<T> in a service
public class MyService(IOptions<AppOptions> opts)
{
    private readonly AppOptions _opts = opts.Value;
    public string AppName => _opts.Name;
}

public class AppOptions
{
    public string Name { get; set; } = "";
    public int Timeout { get; set; }
}

// Add custom JSON config source
builder.Configuration.AddJsonFile("custom.json", optional: true, reloadOnChange: true);

// Add environment variable with prefix
builder.Configuration.AddEnvironmentVariables(prefix: "MYAPP_");
// MYAPP_App__Name=Override  ’  Config["App:Name"] = "Override"
↥ back to top

Q. What is the .NET Core CLI and how is it used?

The .NET CLI (dotnet) is the cross-platform command-line interface for creating, building, running, testing, and publishing .NET applications.

# --- Project Management ---
dotnet new console -n MyApp          # create console app
dotnet new webapi -n MyApi           # create Web API
dotnet new classlib -n MyLib         # create class library
dotnet new sln -n MySolution         # create solution file
dotnet sln add MyApp/MyApp.csproj    # add project to solution

# --- Build & Run ---
dotnet build                         # compile
dotnet run                           # build + run
dotnet run --project MyApp           # specify project
dotnet watch run                     # hot reload on file changes

# --- Testing ---
dotnet test                          # run all tests
dotnet test --filter "Category=Unit" # filter tests
dotnet test --collect "Code Coverage"

# --- Package Management ---
dotnet add package Serilog            # install NuGet package
dotnet remove package Serilog         # remove package
dotnet list package --outdated        # show outdated packages
dotnet restore                        # restore dependencies

# --- Publish ---
dotnet publish -c Release -r linux-x64 --self-contained
dotnet publish -p:PublishSingleFile=true
dotnet publish -p:PublishAot=true     # Native AOT (.NET 7+)

# --- Tools ---
dotnet tool install -g dotnet-ef      # install global tool
dotnet ef migrations add InitialCreate
dotnet ef database update

# --- Info ---
dotnet --version
dotnet --list-sdks
dotnet --list-runtimes
dotnet nuget list source
↥ back to top

Q. How do you create a new .NET Core project?

# Create console app
dotnet new console -n HelloWorld
cd HelloWorld
dotnet run
# Output: Hello, World!

# Create ASP.NET Core Web API
dotnet new webapi -n MyApi --use-minimal-apis
cd MyApi
dotnet run
# Swagger at https://localhost:5001/swagger

# Create solution with multiple projects
mkdir MySolution && cd MySolution
dotnet new sln -n MySolution
dotnet new webapi -n MyApi
dotnet new classlib -n MyApi.Core
dotnet new xunit -n MyApi.Tests
dotnet sln add MyApi MyApi.Core MyApi.Tests
dotnet add MyApi/MyApi.csproj reference MyApi.Core/MyApi.Core.csproj
dotnet add MyApi.Tests/MyApi.Tests.csproj reference MyApi/MyApi.csproj

# Build and test everything
dotnet build
dotnet test

Minimal Web API (generated by dotnet new webapi --use-minimal-apis):

// Program.cs — .NET 10 minimal API
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();

var app = builder.Build();
app.MapOpenApi();

app.MapGet("/hello", () => new { Message = "Hello, .NET 10!" });

app.MapGet("/weather", () =>
{
    var forecasts = Enumerable.Range(1, 5).Select(i => new WeatherForecast(
        DateOnly.FromDateTime(DateTime.Now.AddDays(i)),
        Random.Shared.Next(-20, 55),
        "Sunny"));
    return forecasts;
});

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string Summary);
↥ back to top

Q. What is the purpose of the Program.cs and Startup.cs files in a .NET Core application?

In modern .NET (6+), Startup.cs was eliminated and its responsibilities merged into Program.cs using the minimal hosting model.

Before .NET 6 (two files):

// Program.cs — entry point, created the host
public class Program
{
    public static void Main(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(web => web.UseStartup<Startup>())
            .Build().Run();
}

// Startup.cs — service registration + middleware pipeline
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    }
    public void Configure(IApplicationBuilder app)
    {
        app.UseRouting();
        app.UseEndpoints(e => e.MapControllers());
    }
}

.NET 10 (single Program.cs):

// Program.cs — everything in one place
var builder = WebApplication.CreateBuilder(args);

// === ConfigureServices equivalent ===
builder.Services.AddControllers();
builder.Services.AddOpenApi();
builder.Services.AddScoped<IOrderService, OrderService>();

var app = builder.Build();

// === Configure equivalent (middleware pipeline) ===
if (app.Environment.IsDevelopment())
    app.MapOpenApi();

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

Program.cs roles in .NET 10:

↥ back to top

Q. How do you identify duplicated code in C#, and what techniques can be used to remove it?

Identifying duplicates:

Techniques to remove duplication:

//  Duplicated logic
public decimal CalculateUkTax(decimal amount) => amount * 0.20m;
public decimal CalculateUsTax(decimal amount) => amount * 0.10m;
// Same structure repeated for each region

// … 1. Extract Method / Helper
public decimal CalculateTax(decimal amount, decimal rate) => amount * rate;
// Usage:
decimal uk = CalculateTax(100m, 0.20m);
decimal us = CalculateTax(100m, 0.10m);

// … 2. Generic method
public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
    => value.CompareTo(min) < 0 ? min : value.CompareTo(max) > 0 ? max : value;

// … 3. Strategy pattern for varying behavior
public interface ITaxStrategy { decimal Calculate(decimal amount); }
public class UkTax : ITaxStrategy { public decimal Calculate(decimal a) => a * 0.20m; }
public class UsTax : ITaxStrategy { public decimal Calculate(decimal a) => a * 0.10m; }

// … 4. Extension methods for repeated operations on types
public static class StringExtensions
{
    public static bool IsNullOrEmpty(this string? s) => string.IsNullOrEmpty(s);
    public static string ToTitleCase(this string s) =>
        System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(s.ToLower());
}

// … 5. Base class / template method for duplicated class structures
public abstract class ReportBase
{
    public string Generate()   // template method
    {
        var data    = FetchData();
        var body    = FormatBody(data);
        return $"<report>{body}</report>";
    }
    protected abstract IEnumerable<object> FetchData();
    protected abstract string FormatBody(IEnumerable<object> data);
}

// … 6. Generic repository to remove per-entity CRUD duplication
public class Repository<T>(AppDbContext db) where T : class
{
    public Task<T?> GetByIdAsync(int id) => db.Set<T>().FindAsync(id).AsTask();
    public void Add(T entity) => db.Set<T>().Add(entity);
    public Task SaveAsync() => db.SaveChangesAsync();
}
↥ back to top

Q. What is Kestrel?

Kestrel is the cross-platform, high-performance HTTP server built into ASP.NET Core. It is the default web server and handles incoming HTTP connections directly without requiring IIS or Apache.

Key features:

// Program.cs — Kestrel is used by default
var builder = WebApplication.CreateBuilder(args);

// Configure Kestrel explicitly
builder.WebHost.ConfigureKestrel(options =>
{
    // HTTP on port 5000
    options.ListenLocalhost(5000);

    // HTTPS on port 5001 with certificate
    options.ListenLocalhost(5001, listenOptions =>
    {
        listenOptions.UseHttps("cert.pfx", "password");
        listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel
            .Core.HttpProtocols.Http1AndHttp2AndHttp3;
    });

    // Limits
    options.Limits.MaxConcurrentConnections = 1000;
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10 MB
    options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
});

// Or configure via appsettings.json
/*
"Kestrel": {
  "Endpoints": {
    "Http":  { "Url": "http://0.0.0.0:80" },
    "Https": { "Url": "https://0.0.0.0:443",
               "Certificate": { "Path": "cert.pfx", "Password": "pw" } }
  }
}
*/

var app = builder.Build();
app.MapGet("/", () => "Running on Kestrel!");
app.Run();

Kestrel vs IIS:

Feature Kestrel IIS
Platform Cross-platform Windows only
Performance Very high Good
Edge server
Process management Manual / systemd Built-in
Reverse proxy Recommended pairing Built-in
↥ back to top

Q. What is Dependency Injection in .NET Core?

Dependency Injection (DI) is a design pattern where a class receives its dependencies from an external source (the DI container) rather than creating them itself. ASP.NET Core has a built-in DI container via IServiceCollection.

// 1. Define abstraction and implementation
public interface IEmailService
{
    Task SendAsync(string to, string subject, string body);
}

public class SmtpEmailService(IConfiguration config) : IEmailService
{
    public async Task SendAsync(string to, string subject, string body)
    {
        // Use config["Smtp:Host"] etc.
        Console.WriteLine($"Sending email to {to}: {subject}");
        await Task.CompletedTask;
    }
}

// 2. Register in DI container (Program.cs)
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IEmailService, SmtpEmailService>();

// 3. Inject via constructor (C# 12 primary constructor)
public class OrderService(IEmailService emailService, ILogger<OrderService> logger)
{
    public async Task PlaceOrderAsync(Order order)
    {
        // ... business logic ...
        await emailService.SendAsync(order.CustomerEmail,
            "Order Confirmed", $"Order #{order.Id} confirmed.");
        logger.LogInformation("Order {OrderId} placed", order.Id);
    }
}

// 4. Inject into minimal API endpoints
app.MapPost("/orders", async (Order order, IOrderService svc) =>
{
    await svc.PlaceOrderAsync(order);
    return Results.Created($"/orders/{order.Id}", order);
});

// 5. Inject into controllers
[ApiController]
[Route("api/[controller]")]
public class OrdersController(IOrderService orderService) : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> Create(Order order)
    {
        await orderService.PlaceOrderAsync(order);
        return CreatedAtAction(nameof(GetById), new { id = order.Id }, order);
    }
}
↥ back to top

Q. How do you add services to the dependency injection container?

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;

// 1. Lifetime registrations
services.AddTransient<IOrderService, OrderService>();   // new instance every request
services.AddScoped<ICartService, CartService>();         // one per HTTP request
services.AddSingleton<ICacheService, MemoryCacheService>(); // one per app lifetime

// 2. Register with factory (for complex construction)
services.AddScoped<IDbConnection>(_ =>
    new SqlConnection(builder.Configuration.GetConnectionString("Default")));

// 3. Register multiple implementations
services.AddScoped<INotifier, EmailNotifier>();
services.AddScoped<INotifier, SmsNotifier>();
// Inject IEnumerable<INotifier> to get all

// 4. Named/keyed services (.NET 8+)
services.AddKeyedScoped<IPaymentGateway, StripeGateway>("stripe");
services.AddKeyedScoped<IPaymentGateway, PayPalGateway>("paypal");
// Inject: ([FromKeyedServices("stripe")] IPaymentGateway gateway)

// 5. Options pattern
services.Configure<SmtpOptions>(builder.Configuration.GetSection("Smtp"));
// Inject: IOptions<SmtpOptions>, IOptionsSnapshot<T>, IOptionsMonitor<T>

// 6. Extension methods for clean grouping
services.AddApplicationServices(builder.Configuration);

// Extension method
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddApplicationServices(
        this IServiceCollection services, IConfiguration config)
    {
        services.AddScoped<IOrderService, OrderService>();
        services.AddScoped<IProductRepository, ProductRepository>();
        services.Configure<AppSettings>(config.GetSection("App"));
        return services;
    }
}

// 7. Auto-registration with scrutor
// dotnet add package Scrutor
services.Scan(scan => scan
    .FromAssemblyOf<IOrderService>()
    .AddClasses(c => c.AssignableTo(typeof(IRepository<>)))
    .AsImplementedInterfaces()
    .WithScopedLifetime());
↥ back to top

Q. What is the difference between IServiceCollection and IServiceProvider?

  IServiceCollection IServiceProvider
Role Registration — add/configure services Resolution — retrieve service instances
When used Startup / Program.cs (before Build()) Runtime — after Build()
Key methods AddScoped, AddSingleton, AddTransient, Configure GetService<T>, GetRequiredService<T>, CreateScope
Mutability Mutable — add services Read-only — resolve services
var builder = WebApplication.CreateBuilder(args);

// IServiceCollection — registration phase
IServiceCollection services = builder.Services;
services.AddScoped<IOrderService, OrderService>();
services.AddSingleton<IClock, SystemClock>();

var app = builder.Build();

// IServiceProvider — resolution phase (after Build)
IServiceProvider provider = app.Services;

// Resolve a singleton directly (rare — prefer injection)
var clock = provider.GetRequiredService<IClock>();
Console.WriteLine(clock.UtcNow);

// Resolve scoped service correctly — create a scope
using var scope = provider.CreateScope();
var orderService = scope.ServiceProvider.GetRequiredService<IOrderService>();
await orderService.ProcessAsync();

// GetService<T> vs GetRequiredService<T>
var optional = provider.GetService<IOptionalService>(); // null if not registered
var required = provider.GetRequiredService<IOrderService>(); // throws if not registered

// Anti-pattern: Service Locator — avoid in application code
// … Prefer constructor injection over IServiceProvider in services
↥ back to top

Q. How do you configure logging in .NET Core?

.NET's built-in logging uses ILogger<T> / ILoggerFactory from Microsoft.Extensions.Logging. The default builder configures console, debug, and event source providers.

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Configure logging
builder.Logging
    .ClearProviders()                          // remove defaults
    .AddConsole()                              // console output
    .AddDebug()                                // VS Output window
    .AddEventSourceLogger()                    // ETW / dotnet-trace
    .SetMinimumLevel(LogLevel.Information);    // global minimum

// Per-category level from appsettings.json:
// "Logging": {
//   "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" }
// }

// Structured logging with Serilog (recommended for production)
// dotnet add package Serilog.AspNetCore
builder.Host.UseSerilog((ctx, cfg) =>
    cfg.ReadFrom.Configuration(ctx.Configuration)
       .WriteTo.Console(outputTemplate:
           "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext}: {Message}{NewLine}{Exception}")
       .WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day));

// Use ILogger<T> in services
public class OrderService(ILogger<OrderService> logger)
{
    public async Task PlaceOrderAsync(int orderId)
    {
        logger.LogInformation("Placing order {OrderId}", orderId);   // structured

        try
        {
            // ... work ...
            logger.LogInformation("Order {OrderId} placed successfully", orderId);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to place order {OrderId}", orderId);
            throw;
        }
    }
}

// Log levels (lowest ’ highest severity):
// Trace, Debug, Information, Warning, Error, Critical, None
↥ back to top

Q. How do you perform database migrations in EF Core?

EF Core migrations track schema changes as versioned C# files and apply them to the database.

# Install EF Core tools
dotnet tool install -g dotnet-ef

# Add EF Core packages to project
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
// DbContext
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
    public DbSet<Product> Products => Set<Product>();
    public DbSet<Order>   Orders   => Set<Order>();
}

public class Product
{
    public int Id { get; set; }
    public required string Name { get; set; }
    public decimal Price { get; set; }
}
# Create the initial migration
dotnet ef migrations add InitialCreate --output-dir Data/Migrations

# Apply migrations to the database
dotnet ef database update

# Add a new migration after model changes
dotnet ef migrations add AddOrderDate

# Roll back to a specific migration
dotnet ef database update InitialCreate

# Generate SQL script (for DBA review / production)
dotnet ef migrations script --output migration.sql --idempotent

# Remove last unapplied migration
dotnet ef migrations remove

# List all migrations and their status
dotnet ef migrations list
// Apply migrations programmatically at startup (dev/staging only)
var app = builder.Build();

using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    await db.Database.MigrateAsync(); // apply pending migrations
}

app.Run();
↥ back to top

Q. What is the difference between AddTransient, AddScoped, and AddSingleton?

Lifetime Instance created Shared within Use for
AddTransient Every injection Never shared Lightweight, stateless services
AddScoped Once per HTTP request (scope) Same request DB contexts, unit-of-work
AddSingleton Once per application lifetime Everyone Caches, config, expensive shared state
public interface ICounter { int Next(); }
public class Counter : ICounter
{
    private int _count;
    public int Next() => ++_count;
}

// Register all three lifetimes (for demo)
builder.Services.AddTransient<ICounter, Counter>();   // change to test others

// Controller demonstrating behavior
[ApiController, Route("api/[controller]")]
public class DemoController(
    ICounter counter1,
    ICounter counter2) : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok(new
        {
            Counter1 = counter1.Next(),
            Counter2 = counter2.Next(),
            // Transient: counter1  counter2 (different instances)
            // Scoped: counter1 == counter2 (same instance within request)
            // Singleton: counter1 == counter2, and increments across requests
        });
    }
}

// Scoped services must NOT be injected into Singletons (captive dependency)
//  Singleton capturing Scoped ’ Scoped outlives its intended scope
builder.Services.AddSingleton<IBadSingleton, BadSingleton>(); // has IScoped inside — BAD
// … Use IServiceScopeFactory inside a singleton to create scopes manually
public class SafeSingleton(IServiceScopeFactory scopeFactory)
{
    public async Task DoWorkAsync()
    {
        using var scope = scopeFactory.CreateScope();
        var scoped = scope.ServiceProvider.GetRequiredService<IScopedService>();
        await scoped.WorkAsync();
    }
}
↥ back to top

Q. How do you create a RESTful API in ASP.NET Core?

// Program.cs — .NET 10 minimal API (recommended for new APIs)
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(opt =>
    opt.UseInMemoryDatabase("Products"));
builder.Services.AddOpenApi();

var app = builder.Build();
app.MapOpenApi();

// Route group — /api/products
var products = app.MapGroup("/api/products").WithTags("Products");

products.MapGet("/", async (AppDbContext db) =>
    await db.Products.ToListAsync());

products.MapGet("/{id:int}", async (int id, AppDbContext db) =>
    await db.Products.FindAsync(id) is Product p
        ? Results.Ok(p)
        : Results.NotFound());

products.MapPost("/", async (Product product, AppDbContext db) =>
{
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Results.Created($"/api/products/{product.Id}", product);
});

products.MapPut("/{id:int}", async (int id, Product updated, AppDbContext db) =>
{
    var product = await db.Products.FindAsync(id);
    if (product is null) return Results.NotFound();
    product.Name  = updated.Name;
    product.Price = updated.Price;
    await db.SaveChangesAsync();
    return Results.NoContent();
});

products.MapDelete("/{id:int}", async (int id, AppDbContext db) =>
{
    var product = await db.Products.FindAsync(id);
    if (product is null) return Results.NotFound();
    db.Products.Remove(product);
    await db.SaveChangesAsync();
    return Results.NoContent();
});

app.Run();

public class Product
{
    public int Id { get; set; }
    public required string Name { get; set; }
    public decimal Price { get; set; }
}
↥ back to top

Q. How do you read configuration values from appsettings.json?

// appsettings.json
{
  "App": {
    "Name": "MyApi",
    "MaxPageSize": 100,
    "FeatureFlags": {
      "EnableNewUI": true
    }
  },
  "ConnectionStrings": {
    "Default": "Server=localhost;Database=MyDb;Trusted_Connection=true"
  }
}
// 1. Raw IConfiguration (simple, no type safety)
var name = builder.Configuration["App:Name"];
var conn = builder.Configuration.GetConnectionString("Default");
var maxPage = builder.Configuration.GetValue<int>("App:MaxPageSize", defaultValue: 50);

// 2. Strongly-typed options (recommended)
public class AppOptions
{
    public string Name { get; set; } = "";
    public int MaxPageSize { get; set; }
    public FeatureFlagsOptions FeatureFlags { get; set; } = new();
}
public class FeatureFlagsOptions { public bool EnableNewUI { get; set; } }

builder.Services.Configure<AppOptions>(builder.Configuration.GetSection("App"));

// Inject and use
public class MyService(IOptionsSnapshot<AppOptions> opts)
{
    // IOptions<T> — singleton, does not update on file change
    // IOptionsSnapshot<T> — scoped, updates per request if reloadOnChange: true
    // IOptionsMonitor<T> — singleton, updates in real-time via OnChange callback
    public string AppName => opts.Value.Name;
    public bool NewUI     => opts.Value.FeatureFlags.EnableNewUI;
}

// 3. Bind directly
var appOptions = builder.Configuration
    .GetSection("App")
    .Get<AppOptions>()!;

// 4. Validate options on startup (.NET 8+ AddOptionsWithValidateOnStart)
builder.Services
    .AddOptions<AppOptions>()
    .Bind(builder.Configuration.GetSection("App"))
    .ValidateDataAnnotations()   // use [Required], [Range] on the class
    .ValidateOnStart();          // fail fast if config is invalid

// 5. Environment variable overrides (: ’ __ in env vars)
// App__Name=Override  ’  Config["App:Name"] = "Override"
// ASPNETCORE_ENVIRONMENT=Production
↥ back to top

Q. How do you use middleware in ASP.NET Core?

Middleware is software assembled into the request pipeline that handles requests and responses. Each component can short-circuit or pass to the next middleware via next().

var app = builder.Build();

// Built-in middleware (order matters!)
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseOutputCache();

// Inline middleware with app.Use
app.Use(async (context, next) =>
{
    var watch = System.Diagnostics.Stopwatch.StartNew();
    await next(context);  // call next middleware
    watch.Stop();
    var path = context.Request.Path;
    Console.WriteLine($"{context.Response.StatusCode} {path}{watch.ElapsedMilliseconds}ms");
});

// Short-circuit middleware with app.Run (terminal — no next)
app.MapGet("/health", () => Results.Ok(new { Status = "Healthy" }));

// app.UseWhen — conditional branch
app.UseWhen(ctx => ctx.Request.Path.StartsWithSegments("/api"),
    branch => branch.Use(async (ctx, next) =>
    {
        ctx.Response.Headers["X-Api-Version"] = "v1";
        await next(ctx);
    }));
↥ back to top

Q. How do you create a custom middleware?

// 1. Class-based middleware (recommended — testable, DI-friendly)
public class RequestTimingMiddleware(RequestDelegate next,
    ILogger<RequestTimingMiddleware> logger)
{
    public async Task InvokeAsync(HttpContext context)
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();

        await next(context);  // call rest of pipeline

        sw.Stop();
        logger.LogInformation("{Method} {Path} ’ {Status} in {Ms}ms",
            context.Request.Method,
            context.Request.Path,
            context.Response.StatusCode,
            sw.ElapsedMilliseconds);
    }
}

// Extension method for clean registration
public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseRequestTiming(
        this IApplicationBuilder app) =>
        app.UseMiddleware<RequestTimingMiddleware>();
}

// Register in Program.cs
app.UseRequestTiming();

// 2. Middleware with scoped dependencies — use IMiddleware + factory pattern
public class AuditMiddleware(ILogger<AuditMiddleware> logger) : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        logger.LogInformation("Audit: {Path}", context.Request.Path);
        await next(context);
    }
}

// Register as scoped (IMiddleware uses DI per request)
builder.Services.AddScoped<AuditMiddleware>();
app.UseMiddleware<AuditMiddleware>();

// 3. Exception-handling middleware example
public class GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Unhandled exception");
            context.Response.StatusCode = 500;
            context.Response.ContentType = "application/json";
            await context.Response.WriteAsJsonAsync(new
            {
                Error = "An unexpected error occurred.",
                TraceId = context.TraceIdentifier
            });
        }
    }
}
↥ back to top

Q. How do you use dependency injection in controllers?

// Constructor injection (primary constructor syntax, C# 12 / .NET 8+)
[ApiController]
[Route("api/[controller]")]
public class OrdersController(
    IOrderService orderService,
    ILogger<OrdersController> logger) : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetAll()
    {
        var orders = await orderService.GetAllAsync();
        return Ok(orders);
    }

    [HttpGet("{id:int}")]
    public async Task<ActionResult<OrderDto>> GetById(int id)
    {
        var order = await orderService.GetByIdAsync(id);
        return order is null ? NotFound() : Ok(order);
    }

    [HttpPost]
    public async Task<IActionResult> Create(CreateOrderRequest request)
    {
        var order = await orderService.CreateAsync(request);
        logger.LogInformation("Order {OrderId} created", order.Id);
        return CreatedAtAction(nameof(GetById), new { id = order.Id }, order);
    }
}

// Inject from DI in action parameter — [FromServices]
[HttpGet("summary")]
public IActionResult GetSummary([FromServices] IReportService reportService)
    => Ok(reportService.GetSummary());

// Keyed services (.NET 8+)
[HttpPost("pay/stripe")]
public IActionResult PayWithStripe(
    [FromKeyedServices("stripe")] IPaymentGateway gateway,
    PaymentRequest request)
{
    gateway.Charge(request);
    return Ok();
}

// Register controller services
builder.Services.AddControllers();
builder.Services.AddScoped<IOrderService, OrderService>();
↥ back to top

Q. How do you return JSON from a controller action?

// 1. Minimal API — returns JSON automatically for objects
app.MapGet("/products", async (AppDbContext db) =>
    await db.Products.ToListAsync()); // serialized to JSON automatically

// 2. Controller — Ok() wraps object in 200 JSON response
[ApiController, Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult GetAll() =>
        Ok(new[] { new { Id = 1, Name = "Laptop", Price = 999m } });

    // 3. Typed ActionResult<T>
    [HttpGet("{id:int}")]
    public ActionResult<ProductDto> GetById(int id)
    {
        var product = new ProductDto(id, "Laptop", 999m);
        return Ok(product); // 200 with JSON body
    }

    // 4. Custom JSON options for an action
    [HttpGet("formatted")]
    public IActionResult GetFormatted()
    {
        var data = new { Message = "Hello", Date = DateTime.UtcNow };
        return new JsonResult(data, new System.Text.Json.JsonSerializerOptions
        {
            WriteIndented = true,
            PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
        });
    }
}

// 5. Configure global JSON options (.NET 10)
builder.Services.ConfigureHttpJsonOptions(opt =>
{
    opt.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    opt.SerializerOptions.WriteIndented = false;
    opt.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});

// 6. Results.Json for minimal APIs with custom options
app.MapGet("/custom", () => Results.Json(
    new { Status = "ok" },
    new JsonSerializerOptions { WriteIndented = true }));

record ProductDto(int Id, string Name, decimal Price);
↥ back to top

Q. What is attribute routing?

Attribute routing places route templates directly on controllers and actions using [Route], [HttpGet], [HttpPost], etc. It gives fine-grained control over URL patterns and is the standard approach in ASP.NET Core Web APIs.

// Conventional routing (MVC) — defined globally in Program.cs
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

// Attribute routing — defined on the controller/action (preferred for APIs)
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")] // token replacement
public class ProductsController : ControllerBase
{
    // GET api/v1/products
    [HttpGet]
    public IActionResult GetAll() => Ok();

    // GET api/v1/products/42
    [HttpGet("{id:int}")]
    public IActionResult GetById(int id) => Ok(id);

    // GET api/v1/products/sku/ABC-123
    [HttpGet("sku/{sku:regex(^[A-Z]3-\\d3$)}")]
    public IActionResult GetBySku(string sku) => Ok(sku);

    // POST api/v1/products
    [HttpPost]
    [ProducesResponseType<ProductDto>(StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public IActionResult Create(CreateProductRequest request) => Created();

    // PUT api/v1/products/42
    [HttpPut("{id:int}")]
    public IActionResult Update(int id, UpdateProductRequest request) => NoContent();

    // DELETE api/v1/products/42
    [HttpDelete("{id:int}")]
    public IActionResult Delete(int id) => NoContent();

    // Multiple routes on one action
    [HttpGet("search")]
    [HttpGet("find")]           // both routes map here
    public IActionResult Search([FromQuery] string q) => Ok(q);
}

// Route constraints
// {id:int}           — integer only
// {name:alpha}       — letters only
// {code:length(5)}   — exactly 5 chars
// {date:datetime}    — valid datetime
// {price:decimal}    — decimal number
// {id:min(1)}        — minimum value
// {id:guid}          — GUID format
↥ back to top

Q. How do you create a custom route?

// 1. Custom route constraint — restrict route parameter values
public class EvenNumberConstraint : IRouteConstraint
{
    public bool Match(HttpContext? context, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values.TryGetValue(routeKey, out var value)
            && int.TryParse(value?.ToString(), out int num))
            return num % 2 == 0;
        return false;
    }
}

// Register constraint
builder.Services.Configure<RouteOptions>(opt =>
    opt.ConstraintMap["even"] = typeof(EvenNumberConstraint));

// Use in route template
app.MapGet("/items/{id:even}", (int id) => $"Even item {id}");
// /items/2  ’ matches
// /items/3  ’ 404

// 2. Custom route in controller
[HttpGet("reports/{year:int:min(2000)}/{month:int:range(1,12)}")]
public IActionResult GetMonthlyReport(int year, int month) =>
    Ok(new { Year = year, Month = month });

// 3. Minimal API with complex route pattern
app.MapGet("/files/{**path}", (string path) => $"Requested: {path}");
// Catch-all: /files/docs/2026/report.pdf

// 4. Route groups with shared prefix and metadata
var v2 = app.MapGroup("/api/v2")
    .RequireAuthorization()
    .WithOpenApi()
    .AddEndpointFilter<ValidationFilter>();

v2.MapGet("/products", () => Results.Ok());
v2.MapPost("/products", (Product p) => Results.Created($"/api/v2/products/{p.Id}", p));

// 5. Custom route transformer (slug-case URLs)
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value) =>
        value?.ToString() is string s
            ? System.Text.RegularExpressions.Regex.Replace(s, "([a-z])([A-Z])", "$1-$2").ToLower()
            : null;
}

builder.Services.Configure<RouteOptions>(opt =>
    opt.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
↥ back to top

Q. How do you handle form submissions in ASP.NET Core?

// 1. Minimal API — [FromForm] with IFormCollection
app.MapPost("/contact", async (IFormCollection form) =>
{
    var name    = form["name"].ToString();
    var email   = form["email"].ToString();
    var message = form["message"].ToString();
    return Results.Ok(new { name, email });
}).DisableAntiforgery(); // disable for API endpoints; enable for MVC forms

// 2. Strongly-typed form model
app.MapPost("/signup", async ([FromForm] SignupRequest req) =>
{
    Console.WriteLine($"Signup: {req.Name}, {req.Email}");
    return Results.Redirect("/welcome");
}).DisableAntiforgery();

public record SignupRequest([FromForm] string Name, [FromForm] string Email);

// 3. File upload via IFormFile
app.MapPost("/upload", async (IFormFile file) =>
{
    if (file.Length > 10 * 1024 * 1024)
        return Results.BadRequest("File too large (max 10 MB)");

    var ext = Path.GetExtension(file.FileName).ToLower();
    if (ext is not ".jpg" and not ".png" and not ".pdf")
        return Results.BadRequest("Invalid file type");

    var path = Path.Combine("uploads", Guid.NewGuid() + ext);
    await using var stream = File.Create(path);
    await file.CopyToAsync(stream);
    return Results.Ok(new { FilePath = path });
}).DisableAntiforgery();

// 4. MVC controller form handling with anti-forgery
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([FromForm] ProductFormModel model)
{
    if (!ModelState.IsValid)
        return View(model);

    await _service.CreateAsync(model);
    return RedirectToAction(nameof(Index));
}

// 5. Razor form with anti-forgery token
// @using Microsoft.AspNetCore.Mvc.Rendering
// <form method="post" action="/contact">
//   @Html.AntiForgeryToken()
//   <input name="name" />
//   <button type="submit">Submit</button>
// </form>
↥ back to top

Q. What is model binding?

Model binding is the process by which ASP.NET Core automatically maps HTTP request data (route values, query strings, form fields, JSON body, headers) to action method parameters.

// Sources (in binding order by default):
// 1. [FromRoute]   — /products/42  ’ id = 42
// 2. [FromQuery]   — ?page=2       ’ page = 2
// 3. [FromBody]    — JSON body     ’ complex object
// 4. [FromForm]    — form data
// 5. [FromHeader]  — request header
// 6. [FromServices]— DI container

[ApiController, Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    // Route + query binding — automatic
    [HttpGet("{id:int}")]
    public IActionResult Get(
        int id,                           // [FromRoute] implied
        [FromQuery] string? currency,     // ?currency=GBP
        [FromHeader(Name = "X-Tenant")] string? tenant) // from header
    {
        return Ok(new { id, currency, tenant });
    }

    // Body binding — JSON deserialized automatically ([ApiController] implies [FromBody])
    [HttpPost]
    public IActionResult Create(CreateProductRequest request) => Ok(request);

    // Mixed binding
    [HttpPut("{id:int}")]
    public IActionResult Update(
        [FromRoute] int id,
        [FromBody]  UpdateProductRequest body,
        [FromQuery] bool notify = false) => NoContent();
}

// Custom model binder — e.g., CSV to List<int>
public class CsvIntListBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext context)
    {
        var raw = context.ValueProvider.GetValue(context.ModelName).FirstValue;
        if (string.IsNullOrEmpty(raw))
        {
            context.Result = ModelBindingResult.Success(new List<int>());
            return Task.CompletedTask;
        }
        var list = raw.Split(',')
            .Select(s => int.TryParse(s.Trim(), out int n) ? (int?)n : null)
            .Where(n => n.HasValue).Select(n => n!.Value).ToList();
        context.Result = ModelBindingResult.Success(list);
        return Task.CompletedTask;
    }
}

// Use custom binder
[HttpGet("batch")]
public IActionResult GetBatch(
    [ModelBinder(typeof(CsvIntListBinder))] List<int> ids)
    => Ok(ids); // GET /batch?ids=1,2,3,4

record CreateProductRequest(string Name, decimal Price);
record UpdateProductRequest(string Name, decimal Price);
↥ back to top

Q. How do you validate models in ASP.NET Core?

// 1. Data annotations on model (automatic with [ApiController])
public class CreateProductRequest
{
    [Required(ErrorMessage = "Name is required")]
    [StringLength(100, MinimumLength = 2)]
    public string Name { get; set; } = "";

    [Range(0.01, 999999.99, ErrorMessage = "Price must be between 0.01 and 999,999.99")]
    public decimal Price { get; set; }

    [RegularExpression(@"^[A-Z]{3}$", ErrorMessage = "Currency must be 3 uppercase letters")]
    public string Currency { get; set; } = "GBP";
}

// [ApiController] automatically returns 400 if ModelState is invalid
[HttpPost]
public IActionResult Create(CreateProductRequest request)
{
    // ModelState.IsValid is guaranteed true here (ApiController handles it)
    return Ok(request);
}

// 2. Manual ModelState check (MVC controllers without [ApiController])
[HttpPost]
public IActionResult CreateMvc(CreateProductRequest request)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);
    return Ok();
}

// 3. Custom validation attribute
public class FutureDateAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext ctx)
    {
        if (value is DateTime date && date > DateTime.UtcNow)
            return ValidationResult.Success;
        return new ValidationResult("Date must be in the future");
    }
}

// 4. IValidatableObject — cross-property validation
public class OrderRequest : IValidatableObject
{
    public DateOnly StartDate { get; set; }
    public DateOnly EndDate { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext context)
    {
        if (EndDate <= StartDate)
            yield return new ValidationResult(
                "End date must be after start date",
                [nameof(EndDate)]);
    }
}

// 5. FluentValidation (popular alternative, .NET 10)
// dotnet add package FluentValidation.AspNetCore
public class ProductValidator : AbstractValidator<CreateProductRequest>
{
    public ProductValidator()
    {
        RuleFor(x => x.Name).NotEmpty().Length(2, 100);
        RuleFor(x => x.Price).GreaterThan(0).LessThan(1_000_000);
        RuleFor(x => x.Currency).Matches(@"^[A-Z]{3}$");
    }
}

builder.Services.AddValidatorsFromAssemblyContaining<ProductValidator>();
↥ back to top

Q. What is the ModelState property?

ModelState is a ModelStateDictionary available on ControllerBase that contains the state of model binding and validation for the current request. It holds errors for each property and a boolean IsValid flag.

[HttpPost]
public IActionResult Create(CreateProductRequest request)
{
    // Check validity
    if (!ModelState.IsValid)
    {
        // Collect all errors
        var errors = ModelState
            .Where(e => e.Value?.Errors.Count > 0)
            .ToDictionary(
                e => e.Key,
                e => e.Value!.Errors.Select(err => err.ErrorMessage).ToArray()
            );
        return BadRequest(new { Errors = errors });
    }

    return Ok(request);
}

// Add errors manually
[HttpPost("custom")]
public IActionResult CustomValidation(CreateProductRequest request)
{
    if (request.Name == "forbidden")
        ModelState.AddModelError(nameof(request.Name), "This name is not allowed");

    if (!ModelState.IsValid)
        return ValidationProblem(ModelState); // RFC 7807 Problem Details format

    return Ok();
}

// ValidationProblem — returns standardised ProblemDetails (RFC 7807)
// {
//   "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
//   "title": "One or more validation errors occurred.",
//   "status": 400,
//   "errors": { "Name": ["Name is required"] }
// }

// [ApiController] automatically calls ValidationProblem when ModelState is invalid
// — you don\'t need to check it manually for [ApiController] controllers
↥ back to top

Q. How do you use data annotations for validation?

using System.ComponentModel.DataAnnotations;

public class UserRegistration
{
    [Required(ErrorMessage = "Username is required")]
    [StringLength(50, MinimumLength = 3, ErrorMessage = "Username must be 3–50 characters")]
    public string Username { get; set; } = "";

    [Required]
    [EmailAddress(ErrorMessage = "Invalid email address")]
    public string Email { get; set; } = "";

    [Required]
    [MinLength(8, ErrorMessage = "Password must be at least 8 characters")]
    [RegularExpression(@"^(?=.*[A-Z])(?=.*\d).+$",
        ErrorMessage = "Password must contain an uppercase letter and a digit")]
    public string Password { get; set; } = "";

    [Compare(nameof(Password), ErrorMessage = "Passwords do not match")]
    public string ConfirmPassword { get; set; } = "";

    [Range(18, 120, ErrorMessage = "Age must be between 18 and 120")]
    public int Age { get; set; }

    [Url(ErrorMessage = "Invalid URL")]
    public string? Website { get; set; }

    [Phone(ErrorMessage = "Invalid phone number")]
    public string? Phone { get; set; }

    [DataType(DataType.Date)]
    public DateTime BirthDate { get; set; }

    [CreditCard(ErrorMessage = "Invalid credit card number")]
    public string? CardNumber { get; set; }
}

// Manual validation (outside ASP.NET pipeline)
var user = new UserRegistration { Username = "a", Email = "bad", Age = 15 };
var context = new ValidationContext(user);
var results = new List<ValidationResult>();

bool isValid = Validator.TryValidateObject(user, context, results, validateAllProperties: true);

if (!isValid)
    foreach (var r in results)
        Console.WriteLine($"{string.Join(", ", r.MemberNames)}: {r.ErrorMessage}");

// All common annotations:
// [Required]           — not null/empty
// [StringLength(n)]    — max (and optional min) length
// [MinLength(n)]       — min collection/string length
// [MaxLength(n)]       — max collection/string length
// [Range(min, max)]    — numeric/date range
// [RegularExpression]  — regex pattern
// [EmailAddress]       — email format
// [Url]                — URL format
// [Phone]              — phone format
// [Compare("Prop")]    — must equal another property
// [CreditCard]         — credit card format
// [EnumDataType]       — valid enum value
↥ back to top

Q. What is the ViewResult class?

ViewResult is an ActionResult that renders a Razor view (.cshtml file) as the HTTP response. It is returned by MVC controller actions that produce HTML.

// Controller returning a view
public class ProductsController : Controller  // Controller, not ControllerBase
{
    // View() returns ViewResult — renders Views/Products/Index.cshtml
    public IActionResult Index()
    {
        var products = new List<ProductViewModel>
        {
            new(1, "Laptop",  999m),
            new(2, "Monitor", 450m),
        };
        return View(products);              // passes model to the view
    }

    // Specify view name explicitly
    public IActionResult Details(int id)
    {
        var product = new ProductViewModel(id, "Laptop", 999m);
        return View("ProductDetails", product); // renders ProductDetails.cshtml
    }

    // ViewResult properties
    public IActionResult WithViewData()
    {
        ViewData["PageTitle"] = "Products";    // dynamic view data
        ViewBag.Count = 5;                     // dynamic property syntax

        var result = new ViewResult
        {
            ViewName = "Index",
            ViewData = ViewData,              // includes Model + ViewData
            StatusCode = 200,
        };
        return result;
    }
}

// Views/Products/Index.cshtml
@model List<ProductViewModel>
@{
    ViewData["Title"] = "Products";
}
<h1>Products (@Model.Count)</h1>
<ul>
    @foreach (var p in Model)
    {
        <li>@p.Name  @p.Price.ToString("C")</li>
    }
</ul>

record ProductViewModel(int Id, string Name, decimal Price);

ViewResult vs other results:

Return Use
View() Render Razor .cshtml
PartialView() Render partial view
Json() Return JSON
Redirect() HTTP 302 redirect
File() Return file download
↥ back to top

Q. How do you return a view from a controller action?

public class HomeController : Controller
{
    // 1. Default — renders Views/Home/Index.cshtml
    public IActionResult Index() => View();

    // 2. With model
    public IActionResult Products()
    {
        var model = new List<string> { "Laptop", "Mouse", "Monitor" };
        return View(model);
    }

    // 3. Explicit view name
    public IActionResult About() => View("AboutUs"); // Views/Home/AboutUs.cshtml

    // 4. View in different folder
    public IActionResult Shared() => View("~/Views/Shared/Info.cshtml");

    // 5. With ViewData / ViewBag
    public IActionResult Dashboard()
    {
        ViewData["Title"] = "Dashboard";
        ViewBag.UserId = 42;
        return View(new DashboardModel());
    }

    // 6. Conditional view
    public IActionResult Profile(int id)
    {
        var user = GetUser(id);
        if (user is null) return NotFound();
        return user.IsAdmin
            ? View("AdminProfile", user)
            : View("UserProfile", user);
    }
}
@* Views/Home/Products.cshtml *@
@model List<string>
@{
    ViewData["Title"] = "Products";
    Layout = "_Layout";      // use shared layout
}

<h1>@ViewData["Title"]</h1>
<ul>
    @foreach (var item in Model)
    {
        <li>@item</li>
    }
</ul>
↥ back to top

Q. What is Razor syntax?

Razor is ASP.NET Core's server-side templating syntax that mixes C# and HTML using the @ symbol as the transition character. Razor files have the .cshtml extension.

@* This is a Razor comment *@

@* Declare model type *@
@model List<Product>

@* Code block *@
@{
    ViewData["Title"] = "Product List";
    var count = Model.Count;
    string cssClass = count > 10 ? "many" : "few";
}

@* Inline expression — renders value *@
<h1>Products (@count)</h1>
<p class="@cssClass">Showing @Model.Count items</p>

@* Control flow *@
@if (Model.Any())
{
    <ul>
        @foreach (var product in Model)
        {
            <li>
                <strong>@product.Name</strong> — @product.Price.ToString("C")
                @if (product.Price > 500)
                {
                    <span class="badge">Premium</span>
                }
            </li>
        }
    </ul>
}
else
{
    <p>No products found.</p>
}

@* Explicit expression (multi-token) — use parentheses *@
<p>Tax: @(Model.Sum(p => p.Price) * 0.20m)</p>

@* Render raw HTML (avoid unless trusted content) *@
@Html.Raw("<strong>bold</strong>")

@* Tag helpers (preferred over HTML helpers) *@
<a asp-controller="Products" asp-action="Details" asp-route-id="@product.Id">
    View Details
</a>

@* Partial view *@
@await Html.PartialAsync("_ProductCard", product)

@* Section — inject content into layout *@
@section Scripts {
    <script src="~/js/products.js"></script>
}
@* Views/Shared/_Layout.cshtml — master layout *@
<!DOCTYPE html>
<html>
<head><title>@ViewData["Title"]</title></head>
<body>
    <nav>@await Html.PartialAsync("_Nav")</nav>
    @RenderBody()
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>
↥ back to top

Q. How do you create a Razor view?

# 1. Create via dotnet CLI
dotnet new page -n Index -na MyApp.Pages      # Razor Page
# For MVC views — create .cshtml manually (no template)
@* Views/Products/Create.cshtml — MVC Razor view *@
@model CreateProductRequest
@{
    ViewData["Title"] = "Create Product";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Create Product</h2>

<form asp-action="Create" asp-controller="Products" method="post">
    @Html.AntiForgeryToken()

    <div asp-validation-summary="ModelOnly" class="text-danger"></div>

    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>

    <div class="form-group">
        <label asp-for="Price"></label>
        <input asp-for="Price" class="form-control" type="number" step="0.01" />
        <span asp-validation-for="Price" class="text-danger"></span>
    </div>

    <button type="submit" class="btn btn-primary">Create</button>
    <a asp-action="Index">Cancel</a>
</form>

@section Scripts {
    @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
// Controller action that returns this view
[HttpGet]
public IActionResult Create() => View();

[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CreateProductRequest model)
{
    if (!ModelState.IsValid) return View(model);
    await _service.CreateAsync(model);
    return RedirectToAction(nameof(Index));
}
↥ back to top

Q. How do you use partial views in ASP.NET Core?

Partial views are reusable .cshtml fragments rendered inside other views. They do not have a layout and are ideal for repeating UI components.

@* Views/Shared/_ProductCard.cshtml — partial view *@
@model Product

<div class="card">
    <div class="card-body">
        <h5 class="card-title">@Model.Name</h5>
        <p class="card-text">@Model.Price.ToString("C")</p>
        <a asp-action="Details" asp-route-id="@Model.Id"
           class="btn btn-primary">View</a>
    </div>
</div>
@* Parent view — render partial *@
@model List<Product>

@* 1. Tag helper (preferred, .NET Core) *@
@foreach (var product in Model)
{
    <partial name="_ProductCard" model="product" />
}

@* 2. await Html.PartialAsync (async, preferred in code) *@
@foreach (var product in Model)
{
    @await Html.PartialAsync("_ProductCard", product)
}

@* 3. Pass ViewData to partial *@
@await Html.PartialAsync("_ProductCard", product,
    new ViewDataDictionary(ViewData) { { "ShowBadge", true } })
// Return partial from controller (for AJAX requests)
[HttpGet("card/{id:int}")]
public async Task<IActionResult> GetCard(int id)
{
    var product = await _repo.GetByIdAsync(id);
    return PartialView("_ProductCard", product);
}

// Fetch partial via JavaScript (HTMX or fetch)
// fetch('/products/card/42').then(r => r.text()).then(html => div.innerHTML = html)
↥ back to top

Q. How do you create a view component?

A View Component is like a mini-controller + partial view, suitable for complex reusable UI sections (e.g., shopping cart, navigation menu, notification badge) that require business logic or dependency injection.

// 1. View Component class
using Microsoft.AspNetCore.Mvc;

public class CartSummaryViewComponent(ICartService cartService) : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync()
    {
        var userId = HttpContext.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
        var cart   = await cartService.GetCartAsync(userId ?? "guest");
        return View(cart); // renders Views/Shared/Components/CartSummary/Default.cshtml
    }
}

// ICartService
public interface ICartService
{
    Task<CartViewModel> GetCartAsync(string userId);
}
public record CartViewModel(int ItemCount, decimal Total);
@* Views/Shared/Components/CartSummary/Default.cshtml *@
@model CartViewModel

<div class="cart-badge">
    <span class="icon">’</span>
    <span class="count">@Model.ItemCount</span>
    <span class="total">@Model.Total.ToString("C")</span>
</div>
@* Use in any view *@
@* Tag helper syntax (recommended) *@
<vc:cart-summary></vc:cart-summary>

@* Method syntax *@
@await Component.InvokeAsync("CartSummary")

@* With parameters *@
<vc:recent-products count="5" category="Electronics"></vc:recent-products>
// View component with parameters
public class RecentProductsViewComponent(IProductRepository repo) : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(int count = 4, string? category = null)
    {
        var products = await repo.GetRecentAsync(count, category);
        return View(products);
    }
}

// Register services
builder.Services.AddScoped<ICartService, CartService>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
// View components are discovered automatically — no explicit registration needed
↥ back to top

Q. How do you create a custom tag helper?

Tag helpers are C# classes that target HTML elements and transform them server-side. They are the Razor replacement for HTML helpers.

// 1. Simple custom tag helper — <alert type="success">message</alert>
using Microsoft.AspNetCore.Razor.TagHelpers;

[HtmlTargetElement("alert")]
public class AlertTagHelper : TagHelper
{
    public string Type { get; set; } = "info"; // maps to type attribute

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        var content = await output.GetChildContentAsync();

        output.TagName = "div";
        output.Attributes.SetAttribute("class", $"alert alert-{Type} alert-dismissible");
        output.Attributes.SetAttribute("role", "alert");
        output.Content.SetHtmlContent($"""
            {content.GetContent()}
            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
            """);
    }
}

// Usage in Razor: <alert type="warning">Watch out!</alert>
// Renders: <div class="alert alert-warning alert-dismissible" role="alert">
//            Watch out!
//            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
//          </div>

// 2. Tag helper targeting existing element — format bytes
[HtmlTargetElement("span", Attributes = "file-size")]
public class FileSizeTagHelper : TagHelper
{
    [HtmlAttributeName("file-size")]
    public long FileSizeBytes { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.Attributes.RemoveAll("file-size");
        string formatted = FileSizeBytes switch
        {
            < 1024               => $"{FileSizeBytes} B",
            < 1024 * 1024        => $"{FileSizeBytes / 1024.0:F1} KB",
            < 1024 * 1024 * 1024 => $"{FileSizeBytes / (1024.0 * 1024):F1} MB",
            _                    => $"{FileSizeBytes / (1024.0 * 1024 * 1024):F1} GB",
        };
        output.Content.SetContent(formatted);
    }
}

// Usage: <span file-size="1536000"></span>  ’  <span>1.5 MB</span>

// 3. Register tag helpers in _ViewImports.cshtml
// @addTagHelper *, MyApp        — all tag helpers in MyApp assembly
// @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers  — built-in
↥ back to top

Q. How does the ASP.NET Core middleware pipeline work?

The middleware pipeline is a chain of components that process HTTP requests and responses in order. Each middleware can call next() to pass control to the next component or short-circuit the pipeline.

// ” Request flow ”————————————————————————————————————————————————————
// Request ’ Middleware1 ’ Middleware2 ’ Middleware3 ’ Endpoint
//                                                         “
// Response  Middleware1  Middleware2  Middleware3  (response built)

// ” Built-in middleware order matters ”——————————————————————————————
var app = builder.Build();

app.UseExceptionHandler("/error"); // 1. Catch unhandled exceptions first
app.UseHsts();                     // 2. HTTPS security header
app.UseHttpsRedirection();         // 3. Redirect HTTP to HTTPS
app.UseStaticFiles();              // 4. Serve wwwroot before routing
app.UseRouting();                  // 5. Match route to endpoint
app.UseCors("MyPolicy");           // 6. CORS after routing, before auth
app.UseAuthentication();           // 7. Identify the user
app.UseAuthorization();            // 8. Enforce permissions
app.UseOutputCache();              // 9. Cache responses
app.MapControllers();              // 10. Execute controller endpoints

// ” Writing custom middleware ”———————————————————————————————————————
// Option A: Middleware class (recommended for reusability)
public class RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
{
    public async Task InvokeAsync(HttpContext ctx)
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();

        ctx.Response.OnStarting(() =>
        {
            ctx.Response.Headers["X-Response-Time"] = $"{sw.ElapsedMilliseconds}ms";
            return Task.CompletedTask;
        });

        await next(ctx); // call the next middleware

        sw.Stop();
        logger.LogInformation("{Method} {Path} ’ {StatusCode} in {Ms}ms",
            ctx.Request.Method, ctx.Request.Path,
            ctx.Response.StatusCode, sw.ElapsedMilliseconds);
    }
}

// Option B: Inline middleware (good for simple, one-off logic)
app.Use(async (ctx, next) =>
{
    ctx.Response.Headers["X-Powered-By"] = "ASP.NET Core 10";
    await next(ctx);
});

// Option C: Terminal middleware — does NOT call next (short-circuits)
app.Run(async ctx =>
{
    ctx.Response.StatusCode  = 200;
    ctx.Response.ContentType = "text/plain";
    await ctx.Response.WriteAsync("Hello from terminal middleware!");
});

// Registration
app.UseMiddleware<RequestTimingMiddleware>();

// ” Conditional middleware ”———————————————————————————————————————————
// Map — branch by path prefix
app.Map("/admin", adminApp =>
{
    adminApp.UseMiddleware<AdminAuthMiddleware>();
    adminApp.MapControllerRoute("admin", "{controller}/{action}");
});

// MapWhen — branch by custom predicate
app.MapWhen(
    ctx => ctx.Request.Headers.ContainsKey("X-Webhook-Signature"),
    webhookApp => webhookApp.UseMiddleware<WebhookVerificationMiddleware>());

// UseWhen — branch and REJOIN the pipeline (unlike MapWhen)
app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/api"),
    apiApp => apiApp.UseMiddleware<ApiRateLimiterMiddleware>());

// ” Middleware with scoped dependencies ”—————————————————————————————
// Inject scoped services via InvokeAsync parameters (not constructor)
public class AuditMiddleware(RequestDelegate next)
{
    // IMyService is scoped — cannot inject in constructor (middleware is singleton)
    public async Task InvokeAsync(HttpContext ctx, IAuditService auditService)
    {
        await next(ctx);
        if (ctx.User.Identity?.IsAuthenticated == true)
            await auditService.LogRequestAsync(ctx.Request.Path, ctx.User.Identity.Name!);
    }
}
↥ back to top

Q. How does the built-in DI container work in .NET Core?

The built-in IoC container (IServiceCollection / IServiceProvider) supports three service lifetimes and provides constructor injection throughout the application.

// ” Service lifetimes ”———————————————————————————————————————————————
// Singleton  — one instance for the entire application lifetime
// Scoped     — one instance per HTTP request (or explicit scope)
// Transient  — new instance every time it is requested

builder.Services.AddSingleton<IMemoryCacheService, MemoryCacheService>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();

// ” Registering with factory / implementation instance ”——————————————
// Factory — called once (Singleton) or per request (Scoped/Transient)
builder.Services.AddScoped<IDbConnection>(sp =>
    new NpgsqlConnection(sp.GetRequiredService<IConfiguration>()
        .GetConnectionString("Default")));

// Pre-created instance (always Singleton)
builder.Services.AddSingleton<IConfiguration>(builder.Configuration);

// ” Registering multiple implementations ”———————————————————————————
builder.Services.AddScoped<INotificationHandler, EmailNotificationHandler>();
builder.Services.AddScoped<INotificationHandler, SmsNotificationHandler>();
builder.Services.AddScoped<INotificationHandler, PushNotificationHandler>();

// Inject all: IEnumerable<INotificationHandler>
public class NotificationService(IEnumerable<INotificationHandler> handlers)
{
    public async Task NotifyAllAsync(Notification n, CancellationToken ct)
    {
        var tasks = handlers.Select(h => h.HandleAsync(n, ct));
        await Task.WhenAll(tasks);
    }
}

// ” Keyed services (.NET 8+) ”————————————————————————————————————————
builder.Services.AddKeyedScoped<IPaymentGateway, StripeGateway>("stripe");
builder.Services.AddKeyedScoped<IPaymentGateway, PayPalGateway>("paypal");

// Resolve by key
public class CheckoutService(
    [FromKeyedServices("stripe")] IPaymentGateway stripe,
    [FromKeyedServices("paypal")] IPaymentGateway paypal)
{ }

// ” Options pattern with DI ”——————————————————————————————————————————
builder.Services.AddOptions<SmtpOptions>()
    .Bind(builder.Configuration.GetSection("Smtp"))
    .Validate(o => o.Port > 0 && o.Port < 65536, "Invalid SMTP port")
    .ValidateOnStart();

public class SmtpEmailSender(IOptions<SmtpOptions> opts)
{
    private readonly SmtpOptions _opts = opts.Value;
}

// ” Avoiding captive dependency anti-pattern ”————————————————————————
//  WRONG: Singleton captures Scoped service — Scoped lives too long
builder.Services.AddSingleton<OrderService>(sp =>
    new OrderService(sp.GetRequiredService<IOrderRepository>())); // scoped repo in singleton!

// … CORRECT: use IServiceScopeFactory to create explicit scopes in Singleton
public class BackgroundOrderProcessor(IServiceScopeFactory scopeFactory) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            using var scope = scopeFactory.CreateScope();
            var repo = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
            await repo.ProcessPendingAsync(ct);
            await Task.Delay(TimeSpan.FromSeconds(30), ct);
        }
    }
}

// ” Manual resolution (avoid — prefer constructor injection) ”————————
using var scope = app.Services.CreateScope();
var dbContext   = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await dbContext.Database.MigrateAsync();
↥ back to top

Q. How do you implement background services using IHostedService and BackgroundService?

IHostedService runs code when the host starts/stops. BackgroundService is a base class that simplifies long-running background work.

// ” Option 1: Simple IHostedService ”—————————————————————————————————
public class DatabaseMigrationService(IServiceScopeFactory scopeFactory)
    : IHostedService
{
    public async Task StartAsync(CancellationToken ct)
    {
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Database.MigrateAsync(ct);
        Console.WriteLine("Database migration complete.");
    }

    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}

// ” Option 2: BackgroundService — long-running loop ”—————————————————
public class OrderOutboxProcessor(
    IServiceScopeFactory scopeFactory,
    ILogger<OrderOutboxProcessor> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        logger.LogInformation("Outbox processor started.");

        while (!ct.IsCancellationRequested)
        {
            try
            {
                await ProcessBatchAsync(ct);
            }
            catch (OperationCanceledException)
            {
                break; // graceful shutdown
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Error processing outbox batch");
            }

            await Task.Delay(TimeSpan.FromSeconds(5), ct);
        }

        logger.LogInformation("Outbox processor stopped.");
    }

    private async Task ProcessBatchAsync(CancellationToken ct)
    {
        using var scope = scopeFactory.CreateScope();
        var db  = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var bus = scope.ServiceProvider.GetRequiredService<IPublishEndpoint>();

        var messages = await db.OutboxMessages
            .Where(m => m.ProcessedAt == null)
            .Take(20)
            .ToListAsync(ct);

        foreach (var msg in messages)
        {
            var type    = Type.GetType(msg.Type)!;
            var payload = System.Text.Json.JsonSerializer.Deserialize(msg.Payload, type)!;
            await bus.Publish(payload, type, ct);
            msg.ProcessedAt = DateTime.UtcNow;
        }

        await db.SaveChangesAsync(ct);
    }
}

// ” Option 3: Timed background service ”——————————————————————————————
public class CacheWarmupService(IServiceScopeFactory scopeFactory) : BackgroundService
{
    private readonly PeriodicTimer _timer = new(TimeSpan.FromMinutes(5));

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        // Run immediately on startup, then every 5 minutes
        await DoWorkAsync(ct);

        while (await _timer.WaitForNextTickAsync(ct))
            await DoWorkAsync(ct);
    }

    private async Task DoWorkAsync(CancellationToken ct)
    {
        using var scope = scopeFactory.CreateScope();
        var cache = scope.ServiceProvider.GetRequiredService<IProductCacheService>();
        await cache.WarmupAsync(ct);
    }

    public override void Dispose()
    {
        _timer.Dispose();
        base.Dispose();
    }
}

// ” Registration ”————————————————————————————————————————————————————
builder.Services.AddHostedService<DatabaseMigrationService>(); // runs at startup
builder.Services.AddHostedService<OrderOutboxProcessor>();
builder.Services.AddHostedService<CacheWarmupService>();

// ” Worker Service — standalone background process ”——————————————————
// dotnet new worker -n MyWorker
// Generates a minimal Host with BackgroundService — no HTTP stack
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<MyWorker>();
builder.Services.AddSingleton<IMessageBusClient, RabbitMqClient>();

// Configure graceful shutdown timeout
builder.Services.Configure<HostOptions>(opts =>
    opts.ShutdownTimeout = TimeSpan.FromSeconds(30));

await builder.Build().RunAsync();
↥ back to top

# 18. MISCELLANEOUS


Q. What is NuGet?

NuGet is the official package manager for .NET. It enables developers to create, share, and consume reusable libraries and tools. NuGet packages are ZIP files with the .nupkg extension containing compiled code (DLLs), related files, and a manifest that describes the package.

Key features:

# Install a package
dotnet add package Newtonsoft.Json

# Install a specific version
dotnet add package Microsoft.EntityFrameworkCore --version 9.0.0

# Remove a package
dotnet remove package Newtonsoft.Json

# Restore all packages
dotnet restore

# List outdated packages
dotnet list package --outdated

# Search NuGet
dotnet package search Serilog

In .csproj (central package management, .NET 10):

<!-- Directory.Packages.props — define versions once for the entire repo -->
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="Serilog" Version="4.2.0" />
    <PackageVersion Include="System.Text.Json" Version="10.0.0" />
  </ItemGroup>
</Project>
<!-- Individual .csproj — just reference, no version needed -->
<ItemGroup>
  <PackageReference Include="Serilog" />
</ItemGroup>
↥ back to top

Q. What is the difference between ToString() and Convert.ToString()?

Feature obj.ToString() Convert.ToString(obj)
Null handling Throws NullReferenceException Returns "" (empty string)
Defined on object System.Convert static class
Works on Any object Any base type + nullable
Overridable … Yes No (calls ToString internally)
string? s = null;

// ToString() — throws NullReferenceException on null
try
{
    string result = s!.ToString(); //  NullReferenceException
}
catch (NullReferenceException ex)
{
    Console.WriteLine($"Exception: {ex.Message}");
}

// Convert.ToString() — safe on null
string safe = Convert.ToString(s)!; // returns ""
Console.WriteLine($"Result: '{safe}'"); // Result: ''

// With value types — same result
int number = 42;
Console.WriteLine(number.ToString());          // "42"
Console.WriteLine(Convert.ToString(number));   // "42"

// Custom formatting — prefer ToString(format)
double pi = Math.PI;
Console.WriteLine(pi.ToString("F2"));          // "3.14"
Console.WriteLine(Convert.ToString(pi));       // "3.141592653589793"

Best practice: Use ToString() for non-nullable types. Use Convert.ToString() or null-conditional ?.ToString() ?? "" when the object may be null.

↥ back to top

Q. What is the difference between int.Parse() and Convert.ToInt32()?

Feature int.Parse(s) Convert.ToInt32(s)
Input type string only Any base type (string, double, bool, etc.)
Null input Throws ArgumentNullException Returns 0
Empty string Throws FormatException Throws FormatException
Invalid format Throws FormatException Throws FormatException
Performance Slightly faster (no boxing) Slightly slower
// int.Parse — string only
Console.WriteLine(int.Parse("42"));     // 42
Console.WriteLine(int.Parse("-10"));    // -10

try { int.Parse(null!); }               //  ArgumentNullException
catch (ArgumentNullException) { Console.WriteLine("null throws!"); }

// Convert.ToInt32 — handles null
Console.WriteLine(Convert.ToInt32(null)); // 0 (no exception)
Console.WriteLine(Convert.ToInt32(3.9));  // 4 (rounds!)
Console.WriteLine(Convert.ToInt32(true)); // 1
Console.WriteLine(Convert.ToInt32(false));// 0

// Best practice — TryParse for user input (no exceptions)
string input = "abc";
if (int.TryParse(input, out int value))
    Console.WriteLine($"Parsed: {value}");
else
    Console.WriteLine("Invalid input");

// .NET 7+ — TryParse with generic NumberStyles
if (int.TryParse("FF", System.Globalization.NumberStyles.HexNumber,
    null, out int hex))
    Console.WriteLine(hex); // 255

Rule: Use int.TryParse() for user input. Use int.Parse() only when you are certain the string is a valid integer. Use Convert.ToInt32() when the source may be non-string types.

↥ back to top

Q. What is the use of Code Snippets?

Code snippets are predefined reusable templates for commonly typed code patterns. In Visual Studio, typing a shortcut and pressing Tab twice expands the snippet.

Built-in C# snippets:

Shortcut Expands to
cw Console.WriteLine()
for for loop
foreach foreach loop
if if statement
ctor Constructor
prop Auto-property
propg Get-only property
try try/catch block
switch switch statement
class Class definition
interface Interface definition
// Typing 'prop' + Tab + Tab generates:
public int MyProperty { get; set; }

// Typing 'ctor' + Tab + Tab inside a class generates:
public ClassName()
{
}

// Typing 'foreach' + Tab + Tab generates:
foreach (var item in collection)
{
}

Custom snippet file (.snippet XML):

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Guard Clause</Title>
      <Shortcut>guard</Shortcut>
    </Header>
    <Snippet>
      <Code Language="CSharp">
        <![CDATA[ArgumentNullException.ThrowIfNull($param$);]]>
      </Code>
      <Declarations>
        <Literal><ID>param</ID><Default>value</Default></Literal>
      </Declarations>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

Import via Tools ’ Code Snippets Manager ’ Import.

↥ back to top

Q. Write a program to get the range of Byte Datatype?

// Byte (byte) — unsigned 8-bit integer
Console.WriteLine($"byte Min: {byte.MinValue}");   // 0
Console.WriteLine($"byte Max: {byte.MaxValue}");   // 255
Console.WriteLine($"byte Size: {sizeof(byte)} byte(s)");

// Signed byte (sbyte) — signed 8-bit integer
Console.WriteLine($"sbyte Min: {sbyte.MinValue}"); // -128
Console.WriteLine($"sbyte Max: {sbyte.MaxValue}"); // 127

// All numeric type ranges (.NET 10)
var types = new (string Name, long Min, ulong Max)[]
{
    ("byte",   byte.MinValue,   byte.MaxValue),
    ("sbyte",  sbyte.MinValue,  (ulong)sbyte.MaxValue),
    ("short",  short.MinValue,  (ulong)short.MaxValue),
    ("ushort", ushort.MinValue, ushort.MaxValue),
    ("int",    int.MinValue,    (ulong)int.MaxValue),
    ("uint",   uint.MinValue,   uint.MaxValue),
    ("long",   long.MinValue,   (ulong)long.MaxValue),
};

Console.WriteLine($"{"Type",-8} {"Min",22} {"Max",22}");
Console.WriteLine(new string('-', 55));
foreach (var (name, min, max) in types)
    Console.WriteLine($"{name,-8} {min,22} {max,22}");

// Overflow behavior
byte b = byte.MaxValue; // 255
b++;                    // overflow — wraps to 0
Console.WriteLine(b);   // 0

// Checked context — throws OverflowException
try
{
    byte c = checked((byte)(byte.MaxValue + 1));
}
catch (OverflowException)
{
    Console.WriteLine("Overflow caught!");
}
↥ back to top

Q. What are attributes in C# and how can they be used?

Attributes are metadata decorators applied to types, methods, properties, and assemblies using [AttributeName] syntax. They are inspected at runtime via reflection or at compile time by source generators/analyzers.

// 1. Built-in attributes
[Obsolete("Use NewMethod() instead", error: false)]
public void OldMethod() { }

[Serializable]
public class LegacyData { public int Value; }

// 2. Validation with [Required], [Range] (System.ComponentModel.DataAnnotations)
using System.ComponentModel.DataAnnotations;

public class Product
{
    [Required]
    [StringLength(100, MinimumLength = 2)]
    public string Name { get; set; } = "";

    [Range(0.01, 99999.99)]
    public decimal Price { get; set; }
}

// Validate manually
var p = new Product { Name = "", Price = -5 };
var context = new ValidationContext(p);
var results = new List<ValidationResult>();
bool valid = Validator.TryValidateObject(p, context, results, true);
foreach (var r in results)
    Console.WriteLine(r.ErrorMessage);

// 3. Custom attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
    AllowMultiple = false, Inherited = true)]
public sealed class AuditAttribute(string author) : Attribute
{
    public string Author { get; } = author;
    public DateTime CreatedAt { get; } = DateTime.UtcNow;
}

[Audit("Alice")]
public class OrderService
{
    [Audit("Bob")]
    public void ProcessOrder() { }
}

// 4. Read attributes via reflection
var attr = typeof(OrderService)
    .GetCustomAttribute<AuditAttribute>();
Console.WriteLine(attr?.Author); // Alice

// 5. Conditional compilation attribute
[System.Diagnostics.Conditional("DEBUG")]
public static void DebugLog(string msg) =>
    Console.WriteLine($"[DEBUG] {msg}");

// Called only in DEBUG builds; no-op in Release
DebugLog("Processing started");
↥ back to top

Q. Why can't you specify the accessibility modifier for methods inside the interface?

In C#, all interface members are implicitly public by default, because an interface defines a contract — a promise that any implementing type exposes those members publicly. Restricting access would make the contract meaningless.

public interface IAnimal
{
    void Speak();     // implicitly public
    // private void Speak(); //  Compile error — cannot be private
    // protected void Speak(); //  Compile error
}

public class Dog : IAnimal
{
    public void Speak() => Console.WriteLine("Woof!"); // must be public
}

Exception — C# 8+ Default Interface Methods (DIM):

Since C# 8, interfaces can have private members, but only as helpers for default implementations:

public interface ILogger
{
    void Log(string message);

    // private helper — only usable inside the interface body
    private static string Format(string msg) => $"[{DateTime.UtcNow:u}] {msg}";

    // default implementation uses the private helper
    void LogInfo(string message) => Log(Format(message));
}

public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine(message);
    // LogInfo is inherited from the interface default implementation
}

var logger = new ConsoleLogger();
logger.LogInfo("Application started");
// [2026-04-19 12:00:00Z] Application started

Summary: Interface members are public by design (the contract must be accessible). Only private and static members added in C# 8+ for default implementation support are allowed to restrict visibility — and they are internal to the interface body only.

↥ back to top

Q. How do you implement a custom IComparer for sorting in C#?

IComparer<T> provides a Compare(T x, T y) method that returns negative (x < y), zero (x == y), or positive (x > y). Implement it when the default ordering doesn't fit your requirements.

record Product(string Name, decimal Price, int Stock);

// 1. Implement IComparer<T> as a class
public class ProductByPriceDescending : IComparer<Product>
{
    public int Compare(Product? x, Product? y)
    {
        if (x is null && y is null) return 0;
        if (x is null) return 1;
        if (y is null) return -1;
        return y.Price.CompareTo(x.Price); // descending
    }
}

var products = new List<Product>
{
    new("Laptop",  999m, 10),
    new("Mouse",    25m, 200),
    new("Monitor", 450m, 50),
};

products.Sort(new ProductByPriceDescending());
foreach (var p in products)
    Console.WriteLine($"{p.Name}: {p.Price:C}");
// Laptop: £999.00, Monitor: £450.00, Mouse: £25.00

// 2. Comparer<T>.Create — inline lambda (preferred for simple cases)
var byStockAsc = Comparer<Product>.Create((a, b) => a.Stock.CompareTo(b.Stock));
products.Sort(byStockAsc);
foreach (var p in products)
    Console.WriteLine($"{p.Name}: {p.Stock} units");

// 3. Multi-key sort — name length then alphabetical
var multiKey = Comparer<Product>.Create((a, b) =>
{
    int byLen = a.Name.Length.CompareTo(b.Name.Length);
    return byLen != 0 ? byLen : string.Compare(a.Name, b.Name, StringComparison.Ordinal);
});
products.Sort(multiKey);

// 4. Use with SortedSet<T>
var sortedSet = new SortedSet<Product>(new ProductByPriceDescending())
{
    new("Keyboard", 75m, 150),
    new("Webcam",  120m, 80),
};
foreach (var p in sortedSet)
    Console.WriteLine(p.Name);

// 5. LINQ OrderBy with IComparer<T>
var orderedByName = products
    .OrderBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
    .ToList();
↥ back to top

Q. What is the use of conditional preprocessor directive in C#?

Conditional preprocessor directives instruct the compiler to include or exclude code blocks based on defined symbols. They are evaluated at compile time, so excluded code is never compiled.

// Define symbols in .csproj
// <DefineConstants>DEBUG;LOGGING</DefineConstants>
// Or from command line: dotnet build -p:DefineConstants=STAGING

// #if / #elif / #else / #endif
#if DEBUG
    Console.WriteLine("Debug build");
#elif STAGING
    Console.WriteLine("Staging build");
#else
    Console.WriteLine("Production build");
#endif

// #define and #undef (file-scope)
#define FEATURE_NEW_UI
#undef DEBUG

#if FEATURE_NEW_UI
    Console.WriteLine("New UI enabled");
#endif

// Practical: environment-specific configuration
public class AppConfig
{
    public static string ApiBaseUrl =>
#if DEBUG
        "https://localhost:5001";
#elif STAGING
        "https://staging.api.example.com";
#else
        "https://api.example.com";
#endif
}

// #warning and #error — compiler diagnostics
#if !NET10_0_OR_GREATER
#warning This code targets .NET 10+. Older runtimes may not work correctly.
#endif

// Preferred modern alternative — [Conditional] attribute
[System.Diagnostics.Conditional("DEBUG")]
static void Trace(string msg) => Console.WriteLine($"[TRACE] {msg}");

Trace("Starting..."); // compiled out in Release builds

// Target framework checks (built-in .NET symbols)
#if NET10_0_OR_GREATER
    Console.WriteLine("Running on .NET 10+");
#elif NET8_0
    Console.WriteLine("Running on .NET 8");
#endif
↥ back to top

Q. What are pointer types in C#?

Pointer types are variables that hold the memory address of another variable. They require the unsafe context and are primarily used for interoperability with unmanaged code or low-level performance optimizations.

// Enable unsafe code in .csproj
// <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

unsafe
{
    // Pointer declaration and usage
    int x = 42;
    int* p = &x;           // p holds address of x

    Console.WriteLine(*p); // dereference — prints 42
    *p = 100;              // modify via pointer
    Console.WriteLine(x);  // 100

    // Pointer arithmetic
    int[] arr = [10, 20, 30, 40, 50];
    fixed (int* start = arr) // pin array in memory (prevent GC moving it)
    {
        int* current = start;
        for (int i = 0; i < arr.Length; i++, current++)
            Console.Write($"{*current} "); // 10 20 30 40 50
    }

    // Struct pointer
    var point = new System.Drawing.Point(3, 4);
    System.Drawing.Point* pp = &point;
    Console.WriteLine($"X={pp->X}, Y={pp->Y}"); // X=3, Y=4 (-> dereference)
}

// Span<T> and Memory<T> — safe zero-copy alternatives (preferred in .NET 10)
int[] data = [1, 2, 3, 4, 5];
Span<int> slice = data.AsSpan(1, 3); // [2, 3, 4] — no pointer needed
slice[0] = 99;
Console.WriteLine(data[1]); // 99 — modified original

// P/Invoke with pointers for unmanaged interop
using System.Runtime.InteropServices;

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int memcmp(void* b1, void* b2, nint count);

unsafe
{
    byte[] a = [1, 2, 3];
    byte[] b = [1, 2, 3];
    fixed (byte* pa = a, pb = b)
        Console.WriteLine(memcmp(pa, pb, a.Length) == 0 ? "Equal" : "Different");
}

When to use: P/Invoke interop, embedded/systems programming, performance-critical buffer manipulation. For most scenarios, prefer Span<T>, Memory<T>, or Marshal class.

↥ back to top

Q. What is marshalling and why do we need it?

Marshalling is the process of transforming data types between managed (.NET) code and unmanaged (native/COM) code. The .NET runtime automatically marshals simple types, but complex types need explicit configuration.

Why we need it: Managed memory is controlled by the GC (objects can be moved/collected). Unmanaged code operates with raw memory pointers. Marshalling bridges this gap safely.

using System.Runtime.InteropServices;

// 1. Simple P/Invoke — primitives marshalled automatically
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint GetCurrentThreadId();

Console.WriteLine($"Thread ID: {GetCurrentThreadId()}");

// 2. String marshalling
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool GetComputerName(
    System.Text.StringBuilder lpBuffer,
    ref uint nSize);

var sb = new System.Text.StringBuilder(256);
uint size = 256u;
GetComputerName(sb, ref size);
Console.WriteLine($"Computer: {sb}");

// 3. Struct marshalling — layout must match native struct
[StructLayout(LayoutKind.Sequential)]
public struct SystemTime
{
    public ushort Year, Month, DayOfWeek, Day;
    public ushort Hour, Minute, Second, Milliseconds;
}

[DllImport("kernel32.dll")]
static extern void GetSystemTime(out SystemTime st);

GetSystemTime(out SystemTime time);
Console.WriteLine($"{time.Year}-{time.Month:D2}-{time.Day:D2} {time.Hour:D2}:{time.Minute:D2}");

// 4. Marshal class — manual marshalling
IntPtr ptr = Marshal.AllocHGlobal(100);     // allocate unmanaged memory
try
{
    Marshal.WriteInt32(ptr, 42);
    int value = Marshal.ReadInt32(ptr);
    Console.WriteLine(value);               // 42
}
finally
{
    Marshal.FreeHGlobal(ptr);               // always free unmanaged memory
}

// 5. Modern alternative — LibraryImport (.NET 7+, source-generated, AOT-compatible)
public partial class NativeMethods
{
    [LibraryImport("kernel32.dll")]
    public static partial uint GetCurrentProcessId();
}
Console.WriteLine($"Process ID: {NativeMethods.GetCurrentProcessId()}");
↥ back to top

Q. How to calculate the code execution time in C#?

using System.Diagnostics;

// 1. Stopwatch — most precise, recommended
var sw = Stopwatch.StartNew();

// Code to measure
long sum = 0;
for (int i = 0; i < 10_000_000; i++) sum += i;

sw.Stop();
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
Console.WriteLine($"Elapsed: {sw.Elapsed.TotalMicroseconds:F0} s");
Console.WriteLine($"Sum: {sum}");

// 2. Measure a specific block with a helper
static T Measure<T>(string label, Func<T> action)
{
    var sw = Stopwatch.StartNew();
    T result = action();
    sw.Stop();
    Console.WriteLine($"{label}: {sw.Elapsed.TotalMilliseconds:F3} ms");
    return result;
}

var result = Measure("Enumerable.Sum", () =>
    Enumerable.Range(0, 10_000_000).Sum(x => (long)x));

// 3. High-resolution timestamp (nanoseconds, .NET 7+)
long start = Stopwatch.GetTimestamp();
// ... work ...
long end = Stopwatch.GetTimestamp();
double nanoseconds = (end - start) * 1_000_000_000.0 / Stopwatch.Frequency;
Console.WriteLine($"Elapsed: {nanoseconds:F0} ns");

// 4. BenchmarkDotNet — production-grade micro-benchmarking
// Install: dotnet add package BenchmarkDotNet
/*
[MemoryDiagnoser]
public class MyBenchmarks
{
    [Benchmark]
    public long LinqSum() => Enumerable.Range(0, 10_000_000).Sum(x => (long)x);

    [Benchmark]
    public long LoopSum()
    {
        long s = 0;
        for (int i = 0; i < 10_000_000; i++) s += i;
        return s;
    }
}
BenchmarkRunner.Run<MyBenchmarks>();
*/

// 5. ActivitySource — distributed tracing (.NET 5+)
using var activity = new System.Diagnostics.ActivitySource("MyApp")
    .StartActivity("ComputeSum");
activity?.SetTag("iterations", 1_000_000);
// ... work ...
activity?.Stop();
↥ back to top

Q. What is the distinction between DirectCast and CType?

DirectCast and CType are VB.NET operators. In C# the equivalents are direct cast (T)obj and Convert / as / is expressions.

VB.NET C# Equivalent Behavior
DirectCast(obj, T) (T)obj Requires exact or inheritance relationship; throws InvalidCastException on failure
CType(obj, T) Convert.ToT(obj) or operator Performs data conversion (e.g., doubleint); wider compatibility
TryCast(obj, T) obj as T Returns null on failure; reference types only
// C# explicit cast ( DirectCast) — requires compatible types
object obj = "Hello";
string s = (string)obj;  // … succeeds
// int n = (int)obj;     //  InvalidCastException at runtime

// C# Convert ( CType) — performs data conversion
double d = 3.9;
int i = (int)d;               // truncates ’ 3
int j = Convert.ToInt32(d);   // rounds    ’ 4
string str = Convert.ToString(123); // int ’ string

// C# 'as' ( TryCast) — null on failure, reference types
object value = 42;
string? result = value as string; // null (not a string)
Console.WriteLine(result is null); // True

// C# 'is' pattern matching — recommended modern approach
object item = "World";
if (item is string text && text.Length > 3)
    Console.WriteLine(text.ToUpper()); // WORLD

// Switch expression with type patterns
object data = 3.14;
string description = data switch
{
    int n    => $"Integer: {n}",
    double d => $"Double: {d:F2}",
    string s => $"String: {s}",
    _        => "Unknown"
};
Console.WriteLine(description); // Double: 3.14
↥ back to top

Q. Explain how to implement a custom serializer and deserializer for a complex object in C#?

System.Text.Json (.NET 10) is the recommended serialization library. For complex or non-standard types, implement a custom JsonConverter<T>.

using System.Text.Json;
using System.Text.Json.Serialization;

// Complex type to serialize
public record Money(decimal Amount, string Currency)
{
    public override string ToString() => $"{Amount:F2} {Currency}";
}

public class Order
{
    public int Id { get; init; }
    public Money Total { get; init; } = new(0, "GBP");
    public DateOnly Date { get; init; }
}

// Custom converter for Money — serialize as "99.99 GBP"
public class MoneyConverter : JsonConverter<Money>
{
    public override Money Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        var raw = reader.GetString()
            ?? throw new JsonException("Expected a string for Money");

        var parts = raw.Split(' ', 2);
        if (parts.Length != 2 || !decimal.TryParse(parts[0], out decimal amount))
            throw new JsonException($"Invalid Money format: '{raw}'");

        return new Money(amount, parts[1]);
    }

    public override void Write(Utf8JsonWriter writer, Money value,
        JsonSerializerOptions options)
    {
        writer.WriteStringValue($"{value.Amount:F2} {value.Currency}");
    }
}

// Configure options
var options = new JsonSerializerOptions
{
    WriteIndented = true,
    Converters = { new MoneyConverter() },
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};

var order = new Order
{
    Id   = 1001,
    Total = new Money(249.99m, "GBP"),
    Date  = new DateOnly(2026, 4, 19),
};

// Serialize
string json = JsonSerializer.Serialize(order, options);
Console.WriteLine(json);
// {
//   "id": 1001,
//   "total": "249.99 GBP",
//   "date": "2026-04-19"
// }

// Deserialize
var restored = JsonSerializer.Deserialize<Order>(json, options);
Console.WriteLine(restored?.Total); // 249.99 GBP

// Source-generated serializer (.NET 6+ — AOT & trimming friendly)
[JsonSourceGenerationOptions(WriteIndented = true,
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(Order))]
public partial class AppJsonContext : JsonSerializerContext { }

string json2 = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
↥ back to top

Q. Explain the differences between Memory and Span in C#. When would you use one over the other?

Both provide zero-copy, allocation-free views over contiguous memory (arrays, strings, native memory), but they differ in where they can live.

Feature Span<T> Memory<T>
Allocation Stack only (ref struct) Stack or heap
Use in async methods Cannot cross await … Safe across await
Use as class field  
Use in lambdas/closures  
Convert to Span N/A .Span property
Performance Slightly faster Slight overhead
Best for Synchronous, local processing Async pipelines, fields
// Span<T> — synchronous, stack-only
void ProcessSync(Span<byte> buffer)
{
    for (int i = 0; i < buffer.Length; i++)
        buffer[i] ^= 0xFF; // in-place XOR — no allocation

    Console.WriteLine($"Processed {buffer.Length} bytes");
}

byte[] data = [0x01, 0x02, 0x03];
ProcessSync(data.AsSpan());         // array ’ Span
ProcessSync(stackalloc byte[4]);    // stack memory ’ Span
Console.WriteLine(string.Join(",", data)); // 254,253,252

// ReadOnlySpan<T> — zero-copy string slicing
ReadOnlySpan<char> greeting = "Hello, World!".AsSpan();
ReadOnlySpan<char> hello    = greeting[..5];  // "Hello" — no allocation
Console.WriteLine(hello.ToString());

// Memory<T> — safe across await
async Task ProcessAsync(Memory<byte> buffer)
{
    // Can hold Memory<T> across await (Span<T> cannot)
    await Task.Delay(10);
    buffer.Span[0] = 42; // access via .Span when needed synchronously
}

byte[] asyncData = new byte[10];
await ProcessAsync(asyncData.AsMemory());
Console.WriteLine(asyncData[0]); // 42

// Memory<T> as a class field — Span<T> cannot be a field
public class DataProcessor
{
    private readonly Memory<byte> _buffer; // … Memory<T> as field

    public DataProcessor(byte[] data) => _buffer = data.AsMemory();

    public async Task RunAsync()
    {
        await Task.Yield(); // simulate async work
        _buffer.Span.Fill(0xFF); // zero all bytes
    }
}

// MemoryPool<T> — reusable memory without GC pressure
using System.Buffers;
using IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(1024);
Memory<byte> rented = owner.Memory;
rented.Span.Fill(0);
Console.WriteLine($"Rented: {rented.Length} bytes");
// owner disposed ’ memory returned to pool

Decision: Use Span<T> for synchronous, in-method processing. Use Memory<T> when you need to store the view across async boundaries or as a field.

↥ back to top

Q. What is a DTO?

A DTO (Data Transfer Object) is a simple object used to transfer data between layers or across process/network boundaries. DTOs contain only data (properties) — no business logic, no methods, no validation.

Purpose:

// Domain model — rich, with business logic
public class Order
{
    public int Id { get; private set; }
    public string CustomerId { get; private set; } = "";
    public List<OrderLine> Lines { get; } = [];
    private decimal _discount;

    public decimal Total => Lines.Sum(l => l.Subtotal) * (1 - _discount);
    public void ApplyDiscount(decimal pct) { /* validation logic */ }
}

// DTO — thin, serialization-friendly
public record OrderSummaryDto(
    int Id,
    string CustomerId,
    decimal Total,
    int ItemCount,
    DateTimeOffset CreatedAt);

// Mapping — manually or via AutoMapper / Mapperly
public static class OrderMapper
{
    public static OrderSummaryDto ToDto(Order order) => new(
        order.Id,
        order.CustomerId,
        order.Total,
        order.Lines.Count,
        DateTimeOffset.UtcNow);
}

// ASP.NET Core minimal API — return DTO, not domain model
app.MapGet("/orders/{id}", (int id, IOrderRepository repo) =>
{
    var order = repo.GetById(id);
    return order is null
        ? Results.NotFound()
        : Results.Ok(OrderMapper.ToDto(order));
});

// Request DTO — controls what clients can send (anti-over-posting)
public record CreateOrderRequest(
    string CustomerId,
    IReadOnlyList<OrderLineRequest> Lines);

public record OrderLineRequest(string ProductId, int Quantity);
↥ back to top

Q. What does POCO mean?

POCO stands for Plain Old CLR Object (or Plain Old C# Object). A POCO is a simple class that:

// … POCO — no framework dependency
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public string Email { get; set; } = "";
    public DateTime CreatedAt { get; set; }
}

// … POCO record (C# 9+) — immutable POCO
public record ProductPoco(int Id, string Name, decimal Price);

//  Not a POCO — inherits from framework class
public class LegacyController : System.Web.Mvc.Controller { }

// POCOs work with EF Core without inheriting DbContext entities
// EF Core maps POCOs via convention or configuration
public class AppDbContext : DbContext
{
    public DbSet<Customer> Customers => Set<Customer>();
}

// POCOs work with System.Text.Json without attributes
var customer = new Customer
{
    Id = 1,
    Name = "Alice",
    Email = "alice@example.com",
    CreatedAt = DateTime.UtcNow,
};

string json = JsonSerializer.Serialize(customer);
Console.WriteLine(json);
// {"Id":1,"Name":"Alice","Email":"alice@example.com","CreatedAt":"..."}

var restored = JsonSerializer.Deserialize<Customer>(json);
Console.WriteLine(restored?.Name); // Alice

POCO vs DTO: A POCO is a design principle (framework-free class). A DTO is a pattern (data carrier between layers). A DTO is usually a POCO, but not all POCOs are DTOs.

↥ back to top

Q. Define Parsing? Explain how to Parse a DateTime String?

Parsing is the process of converting a string representation into a typed value. For DateTime/DateTimeOffset, .NET provides several methods with different trade-offs.

using System.Globalization;

// 1. DateTime.Parse — lenient, throws on failure
DateTime dt1 = DateTime.Parse("2026-04-19");
DateTime dt2 = DateTime.Parse("April 19, 2026");
Console.WriteLine(dt1); // 4/19/2026 12:00:00 AM

// 2. DateTime.TryParse — safe, no exception
if (DateTime.TryParse("2026-04-19 14:30:00", out DateTime result))
    Console.WriteLine(result); // 4/19/2026 2:30:00 PM

// 3. DateTime.ParseExact — strict format, throws on mismatch
DateTime exact = DateTime.ParseExact(
    "19/04/2026 14:30",
    "dd/MM/yyyy HH:mm",
    CultureInfo.InvariantCulture);
Console.WriteLine(exact);

// 4. DateTime.TryParseExact — strict + safe
bool ok = DateTime.TryParseExact(
    "19-04-2026",
    "dd-MM-yyyy",
    CultureInfo.InvariantCulture,
    DateTimeStyles.None,
    out DateTime strict);
Console.WriteLine($"Parsed: {ok}, Value: {strict:yyyy-MM-dd}");

// 5. Multiple format candidates
string[] formats = ["dd/MM/yyyy", "MM-dd-yyyy", "yyyy.MM.dd"];
if (DateTime.TryParseExact("2026.04.19", formats,
    CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime multi))
    Console.WriteLine(multi);

// 6. DateOnly / TimeOnly (.NET 6+) — preferred for date/time-only values
DateOnly date = DateOnly.Parse("2026-04-19");
TimeOnly time = TimeOnly.Parse("14:30:00");
Console.WriteLine($"{date}, {time}");

DateOnly.TryParseExact("19/04/2026", "dd/MM/yyyy",
    CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly d);
Console.WriteLine(d); // 4/19/2026

// 7. DateTimeOffset — timezone-aware (preferred for APIs)
DateTimeOffset dto = DateTimeOffset.Parse("2026-04-19T14:30:00+05:30");
Console.WriteLine(dto.ToUniversalTime()); // converted to UTC

// 8. ISO 8601 round-trip format ("O")
string iso = DateTime.UtcNow.ToString("O");
DateTime restored = DateTime.Parse(iso, null, DateTimeStyles.RoundtripKind);
Console.WriteLine(restored.Kind); // Utc
↥ back to top

Q. What is IL (Intermediate Language) Code?

IL (Intermediate Language), also called CIL (Common Intermediate Language) or historically MSIL (Microsoft Intermediate Language), is the CPU-independent bytecode that .NET compilers (C#, F#, VB.NET) produce. It is not machine code — the JIT compiler converts IL to native machine code at runtime.

Source Code (C#/F#/VB)
       “ compile
  IL (.dll / .exe)           platform-independent
       “ JIT / AOT
Native Machine Code          platform-specific (x64, ARM64, etc.)

Example — C# and its IL:

// C# source
public static int Add(int a, int b) => a + b;
// Corresponding IL (viewed in ILDASM or ILSpy)
.method public hidebysig static int32 Add(int32 a, int32 b) cil managed
{
    .maxstack 2
    ldarg.0      // push a
    ldarg.1      // push b
    add          // pop both, push sum
    ret          // return
}

View IL in practice:

# ILSpy CLI
dotnet tool install -g ilspycmd
ilspycmd MyApp.dll --il

# Built-in ILDASM (Windows .NET SDK)
ildasm MyApp.dll /output:MyApp.il

# dotnet-ildasm
dotnet tool install -g dotnet-ildasm
dotnet-ildasm MyApp.dll

Characteristics of IL:

↥ back to top

Q. What is the use of JIT (Just-in-Time compiler)?

The JIT (Just-in-Time) compiler is part of the .NET CLR. It converts IL (Intermediate Language) bytecode into native machine code on demand — the first time each method is called — and caches the result for subsequent calls.

JIT compilation pipeline:

IL bytecode ’ [JIT Compiler] ’ Native x64/ARM64 code ’ CPU execution
                  
         (first call only — cached after)
// Demonstrate JIT behavior with RuntimeHelpers
using System.Runtime.CompilerServices;

// Force JIT compilation of a method before timing it
RuntimeHelpers.PrepareMethod(typeof(Program)
    .GetMethod(nameof(HotPath))!.MethodHandle);

static long HotPath()
{
    long sum = 0;
    for (int i = 0; i < 10_000_000; i++) sum += i;
    return sum;
}

// [MethodImpl] hints to the JIT
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int FastAdd(int a, int b) => a + b; // JIT will inline this call

[MethodImpl(MethodImplOptions.AggressiveOptimization)]
static void HeavyLoop()
{
    // JIT uses Tier-2 optimizations immediately (skip warm-up)
    for (int i = 0; i < 100_000; i++) { /* work */ }
}

[MethodImpl(MethodImplOptions.NoInlining)]
static void AlwaysCallStack() { } // prevents inlining (useful for profiling)

JIT tiers (.NET 6+):

Tier Trigger Optimization
Tier 0 First call Minimal (fast compilation)
Tier 1 After ~30 calls Full optimizations (inlining, unrolling, vectorization)

AOT (Ahead-of-Time) — .NET 7+ alternative to JIT:

# Publish as native AOT — compiles IL to machine code at build time
dotnet publish -r win-x64 -p:PublishAot=true
# Result: single native .exe, no JIT at runtime, faster startup
↥ back to top

Q. Is it possible to view IL code?

Yes. Multiple tools can inspect IL from any .NET assembly (.dll/.exe).

# 1. ILSpy CLI — cross-platform, decompiles back to C# or raw IL
dotnet tool install -g ilspycmd
ilspycmd MyApp.dll --il               # raw IL output
ilspycmd MyApp.dll -l CSharp          # decompile to C#

# 2. dotnet-ildasm — .NET global tool
dotnet tool install -g dotnet-ildasm
dotnet-ildasm MyApp.dll

# 3. Built-in ILDASM (ships with .NET SDK on Windows)
# Run from Developer Command Prompt:
ildasm MyApp.dll

# 4. dotnet-dump / SOS — inspect IL at runtime
dotnet tool install -g dotnet-dump
dotnet-dump collect -p <PID>
dotnet-dump analyze <dump-file>
# Then: clrthreads, dumpil, etc.

In Visual Studio:

SharpLab.io — paste C# and instantly view IL, JIT ASM, or decompiled output online.

// C# source
public static int Square(int x) => x * x;

// IL (as seen in ILSpy):
// .method public hidebysig static int32 Square(int32 x) cil managed
// {
//     ldarg.0
//     ldarg.0
//     mul
//     ret
// }
↥ back to top

Q. What is the benefit of compiling into IL code?

Compiling to IL provides several advantages over compiling directly to native machine code:

Benefit Explanation
Platform independence Same IL runs on Windows, Linux, macOS, ARM — JIT produces native code per platform
Language interoperability C#, F#, VB.NET all compile to the same IL — can call each other's types seamlessly
Runtime optimizations JIT knows the exact CPU (AVX-512, etc.) and optimizes better than a cross-compiled binary
Security / verification CLR verifies IL for type safety before execution
Reflection IL preserves metadata — types, methods, attributes queryable at runtime
Dynamic code System.Reflection.Emit generates IL at runtime for dynamic proxies, expression trees
AOT-ready Same IL can be JIT-compiled, interpreted (Mono), or AOT-compiled (NativeAOT)
// Benefit: Language interoperability
// F# library (compiled to IL):
// module MathLib
// let square x = x * x

// C# consuming F# IL seamlessly:
// int result = MathLib.square(5); // 25

// Benefit: Runtime IL generation with Reflection.Emit
using System.Reflection.Emit;

var method = new DynamicMethod("Add", typeof(int),
    [typeof(int), typeof(int)]);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);

var add = (Func<int, int, int>)method.CreateDelegate(typeof(Func<int, int, int>));
Console.WriteLine(add(3, 4)); // 7

// Benefit: JIT knows the target CPU
// On a machine with AVX-512, JIT auto-vectorizes loops:
int[] arr = Enumerable.Range(0, 1024).ToArray();
int sum = 0;
foreach (var n in arr) sum += n; // JIT may emit VPADDD (SIMD) instruction
↥ back to top

Q. What is the importance of CTS (Common Type System)?

The CTS (Common Type System) is the specification that defines how types are declared, used, and managed in the .NET runtime. It ensures that types from different .NET languages (C#, F#, VB.NET) are compatible with each other.

Key responsibilities:

// CTS type mapping — all languages share the same underlying CLR types
// C#      VB.NET     F#        CTS (CLR) Type
// int   = Integer  = int    = System.Int32
// long  = Long     = int64  = System.Int64
// string= String   = string = System.String
// bool  = Boolean  = bool   = System.Boolean

// Proof: C# int IS System.Int32
int x = 42;
System.Int32 y = 42;
Console.WriteLine(x.GetType() == y.GetType()); // True
Console.WriteLine(x.GetType().FullName);        // System.Int32

// CTS value types vs reference types
int    vt = 10;          // value type — CTS ValueType
string rt = "hello";     // reference type — CTS object
Console.WriteLine(vt.GetType().IsValueType); // True
Console.WriteLine(rt.GetType().IsValueType); // False

// CTS type hierarchy — everything derives from System.Object
Console.WriteLine(typeof(int).BaseType?.Name);    // ValueType
Console.WriteLine(typeof(string).BaseType?.Name); // Object
Console.WriteLine(typeof(bool).IsSubclassOf(typeof(object))); // True (via ValueType)

// Cross-language interop enabled by CTS
// A C# class can be used by F# or VB.NET code without any adapter
public class Calculator
{
    public int Add(int a, int b) => a + b;    // System.Int32 — universal in .NET
}

CTS vs CLS: CTS defines all possible types. The CLS (Common Language Specification) is a subset that all languages must support for cross-language compatibility (e.g., avoid unsigned types in public APIs since VB.NET doesn't have uint).

↥ back to top

Q. How are initializers executed?

In C#, initializers run in a specific order before the constructor body executes. Understanding this order prevents subtle bugs.

Execution order:

  1. Static field initializers (once, before first use of the type)
  2. Static constructor (static MyClass())
  3. Instance field initializers (top to bottom, before constructor body)
  4. Base class constructor (base(...))
  5. Derived class constructor body
public class Base
{
    public int BaseField = Log("Base field init", 10);
    public int BaseProp { get; } = Log("Base prop init", 20);

    static int Log(string msg, int val)
    {
        Console.WriteLine(msg);
        return val;
    }

    public Base()  => Console.WriteLine("Base constructor");
}

public class Derived : Base
{
    public int DerivedField = Log("Derived field init", 30);

    public Derived() : base()
    {
        Console.WriteLine("Derived constructor");
    }

    static int Log(string msg, int val)
    {
        Console.WriteLine(msg);
        return val;
    }
}

var d = new Derived();
// Output:
// Derived field init     instance field initializer runs FIRST (before base ctor)
// Base field init        base field initializers run when base() is called
// Base prop init
// Base constructor       base constructor body
// Derived constructor    derived constructor body

// Object / collection initializers — syntactic sugar, run after constructor
var list = new List<int> { 1, 2, 3 }; // equivalent to: Add(1); Add(2); Add(3);

record Point(int X, int Y);
var p = new Point(1, 2) { X = 10 }; // with expression — creates new Point(10, 2)

// Required init (C# 11+)
public class Config
{
    public required string ConnectionString { get; init; }
    public int Timeout { get; init; } = 30; // field initializer with default
}

var cfg = new Config { ConnectionString = "Server=localhost" };
Console.WriteLine(cfg.Timeout); // 30
↥ back to top

Q. What is Shadowing?

Shadowing (method hiding) is the practice of declaring a member in a derived class with the same name as a member in a base class using the new keyword. Unlike override, shadowing does not participate in polymorphism — the choice of method depends on the compile-time type of the variable.

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

public class Dog : Animal
{
    // Shadowing — hides Animal.Speak (compiler warning without 'new')
    public new void Speak() => Console.WriteLine("Dog barks");

    // Overriding — participates in polymorphism
    public override void Move() => Console.WriteLine("Dog runs");
}

var dog = new Dog();
dog.Speak();  // Dog barks        Dog.Speak (compile-time type = Dog)
dog.Move();   // Dog runs         Dog.Move (override — polymorphic)

Animal animal = new Dog();
animal.Speak(); // Animal speaks  Animal.Speak (compile-time type = Animal — shadowing!)
animal.Move();  // Dog runs       Dog.Move (override — polymorphic)

// Shadowing in practice — useful when extending sealed/framework types
public class MyList<T> : List<T>
{
    // Shadow List<T>.Add to add logging without override
    public new void Add(T item)
    {
        Console.WriteLine($"Adding: {item}");
        base.Add(item);
    }
}

var myList = new MyList<int>();
myList.Add(42);      // Adding: 42 (shadowed version called)

List<int> asList = myList;
asList.Add(99);      // original List<T>.Add called (no log — shadowing, not override)

// Shadowing vs Overriding
Console.WriteLine("--- Key Difference ---");
// Shadow: method selected by compile-time type
// Override: method selected by runtime type (true polymorphism)

Best practice: Prefer override over shadowing. Use new (shadowing) only when you cannot or should not override (e.g., method is not virtual, or you intentionally want different behavior per reference type).

↥ back to top

Q. How does Reflection work in C# and when should you use it?

Reflection allows you to inspect and manipulate types, methods, properties, and fields at runtime. It is used for serializers, ORMs, DI containers, and plugin systems.

using System.Reflection;

// ” 1. Inspect a type ”———————————————————————————————————————————————
Type type = typeof(string);

Console.WriteLine(type.FullName);          // System.String
Console.WriteLine(type.IsClass);           // True
Console.WriteLine(type.IsValueType);       // False
Console.WriteLine(type.BaseType?.Name);    // Object

// All public methods
foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
    Console.WriteLine($"  {method.Name}({string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name))})");

// ” 2. Create instance and invoke method dynamically ”————————————————
public class Calculator
{
    public int Add(int a, int b) => a + b;
    private string _secret = "hidden";

    [Obsolete("Use Add instead")]
    public int Sum(int a, int b) => a + b;
}

// Create instance via reflection
Type calcType = typeof(Calculator);
object? calc = Activator.CreateInstance(calcType);

// Invoke public method
MethodInfo? addMethod = calcType.GetMethod("Add");
object? result = addMethod!.Invoke(calc, [3, 7]);
Console.WriteLine(result); // 10

// Access private field
FieldInfo? secretField = calcType.GetField("_secret",
    BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine(secretField?.GetValue(calc));   // hidden
secretField?.SetValue(calc, "modified");
Console.WriteLine(secretField?.GetValue(calc));   // modified

// ” 3. Read attributes via reflection ”———————————————————————————————
foreach (MethodInfo method in calcType.GetMethods())
{
    var obsolete = method.GetCustomAttribute<ObsoleteAttribute>();
    if (obsolete != null)
        Console.WriteLine($"{method.Name} is obsolete: {obsolete.Message}");
}

// ” 4. Generic reflection ”———————————————————————————————————————————
// Create List<int> dynamically
Type listType = typeof(List<>).MakeGenericType(typeof(int));
object list = Activator.CreateInstance(listType)!;
MethodInfo addItem = listType.GetMethod("Add")!;
addItem.Invoke(list, [42]);
addItem.Invoke(list, [99]);
Console.WriteLine(listType.GetProperty("Count")?.GetValue(list)); // 2

// ” 5. Property access and setting ”——————————————————————————————————
public class Person { public string Name { get; set; } = ""; public int Age { get; set; } }

var person = new Person();
Type personType = typeof(Person);

// Set properties by name (useful for generic mappers)
var values = new Dictionary<string, object> { ["Name"] = "Alice", ["Age"] = 30 };
foreach (var (key, value) in values)
{
    PropertyInfo? prop = personType.GetProperty(key);
    prop?.SetValue(person, Convert.ChangeType(value, prop.PropertyType));
}
Console.WriteLine($"{person.Name}, {person.Age}"); // Alice, 30

// ” 6. Cached reflection with compiled expressions (fast path) ”——————
// Raw reflection is ~100-300x slower than direct calls
// Cache with compiled delegates for hot paths
var compiled = CreateSetter<Person, string>(p => p.Name);
compiled(person, "Bob"); // fast — no reflection overhead

Func<T, TProp> CreateGetter<T, TProp>(System.Linq.Expressions.Expression<Func<T, TProp>> expr)
    => expr.Compile();

Action<T, TProp> CreateSetter<T, TProp>(System.Linq.Expressions.Expression<Func<T, TProp>> expr)
{
    var param  = System.Linq.Expressions.Expression.Parameter(typeof(T));
    var value  = System.Linq.Expressions.Expression.Parameter(typeof(TProp));
    var member = (System.Linq.Expressions.MemberExpression)expr.Body;
    var assign = System.Linq.Expressions.Expression.Assign(
        System.Linq.Expressions.Expression.Property(param, member.Member.Name), value);
    return System.Linq.Expressions.Expression.Lambda<Action<T, TProp>>(assign, param, value).Compile();
}

Performance note: Use reflection sparingly. Cache Type, MethodInfo, and PropertyInfo objects. For hot paths, compile to delegates or use source generators instead.

↥ back to top

Q. How do you create and use custom attributes in C#?

Custom attributes are metadata annotations that can be attached to types, methods, properties, parameters, etc., and read at runtime via reflection or at compile time via source generators / Roslyn analyzers.

using System;
using System.Reflection;

// ” 1. Define a custom attribute ”———————————————————————————————————
[AttributeUsage(
    AttributeTargets.Class | AttributeTargets.Method,  // where it can be applied
    AllowMultiple = false,                             // one per target
    Inherited     = true)]                             // derived classes inherit it
public class AuditAttribute : Attribute
{
    public string  Action   { get; }
    public string? Category { get; set; }
    public bool    LogArgs  { get; set; } = true;

    public AuditAttribute(string action) => Action = action;
}

// ” 2. Apply the attribute ”——————————————————————————————————————————
[Audit("Order", Category = "Commerce")]
public class OrderController
{
    [Audit("PlaceOrder", LogArgs = true)]
    public Task<Order> PlaceOrderAsync(PlaceOrderRequest req) => Task.FromResult(new Order());
}

// ” 3. Read attributes at runtime via reflection ”————————————————————
Type type = typeof(OrderController);

// Class-level attribute
var classAudit = type.GetCustomAttribute<AuditAttribute>();
Console.WriteLine($"Class action: {classAudit?.Action}"); // Order

// Method-level attribute
foreach (MethodInfo method in type.GetMethods())
{
    var methodAudit = method.GetCustomAttribute<AuditAttribute>();
    if (methodAudit != null)
        Console.WriteLine($"{method.Name}: {methodAudit.Action}, LogArgs={methodAudit.LogArgs}");
}

// ” 4. Validation attribute (like DataAnnotations) ”——————————————————
[AttributeUsage(AttributeTargets.Property)]
public class MustBePastAttribute : ValidationAttribute
{
    public MustBePastAttribute() : base("The date must be in the past.") { }

    public override bool IsValid(object? value)
        => value is DateTime dt && dt < DateTime.UtcNow;
}

public class CreateEventRequest
{
    [Required]
    public string Name { get; set; } = null!;

    [MustBePast]
    public DateTime StartedAt { get; set; }
}

// ASP.NET Core validates automatically via [ApiController]
// Manual validation:
var request = new CreateEventRequest { Name = "Conf", StartedAt = DateTime.UtcNow.AddDays(1) };
var ctx     = new ValidationContext(request);
var results = new List<ValidationResult>();
bool valid  = Validator.TryValidateObject(request, ctx, results, validateAllProperties: true);
Console.WriteLine(valid);        // False
Console.WriteLine(results[0].ErrorMessage); // The date must be in the past.

// ” 5. Parameter attribute ”——————————————————————————————————————————
[AttributeUsage(AttributeTargets.Parameter)]
public class NotEmptyAttribute : Attribute { }

public static class Guard
{
    public static string NotEmpty([NotEmpty] string value, [CallerArgumentExpression(nameof(value))] string? name = null)
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException($"'{name}' must not be empty.", name);
        return value;
    }
}

var name = Guard.NotEmpty(""); // throws: 'value' must not be empty.
↥ back to top

Q. What are source generators and how do you create one?

Source generators run during compilation and add new C# source files to the project. They eliminate runtime reflection overhead and enable compile-time code generation (serializers, mappers, DI wiring, etc.).

dotnet new classlib -n MySourceGenerator
dotnet add MySourceGenerator package Microsoft.CodeAnalysis.CSharp
dotnet add MySourceGenerator package Microsoft.CodeAnalysis.Analyzers
<!-- MySourceGenerator.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>  <!-- required for generators -->
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.*" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.*" PrivateAssets="all" />
  </ItemGroup>
</Project>
// ” 1. Incremental Source Generator (recommended — .NET 6+) ”—————————
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Text;

[Generator]
public class ToStringGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 1. Find classes marked with [GenerateToString]
        IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations =
            context.SyntaxProvider
                .CreateSyntaxProvider(
                    predicate: static (node, _) => node is ClassDeclarationSyntax cls
                        && cls.AttributeLists.Count > 0,
                    transform: static (ctx, _) => GetSemanticTarget(ctx))
                .Where(static m => m is not null)!;

        // 2. Combine with compilation and generate
        IncrementalValueProvider<(Compilation, ImmutableArray<ClassDeclarationSyntax>)> compilation =
            context.CompilationProvider.Combine(classDeclarations.Collect());

        context.RegisterSourceOutput(compilation,
            static (spc, source) => Execute(source.Item1, source.Item2, spc));
    }

    private static ClassDeclarationSyntax? GetSemanticTarget(GeneratorSyntaxContext ctx)
    {
        var classDecl = (ClassDeclarationSyntax)ctx.Node;
        var model     = ctx.SemanticModel;
        var symbol    = model.GetDeclaredSymbol(classDecl);

        return symbol?.GetAttributes()
            .Any(a => a.AttributeClass?.Name == "GenerateToStringAttribute") == true
            ? classDecl
            : null;
    }

    private static void Execute(
        Compilation compilation,
        ImmutableArray<ClassDeclarationSyntax> classes,
        SourceProductionContext ctx)
    {
        foreach (var classDecl in classes)
        {
            var model  = compilation.GetSemanticModel(classDecl.SyntaxTree);
            var symbol = model.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
            if (symbol is null) continue;

            var source = GenerateToString(symbol);
            ctx.AddSource($"{symbol.Name}.g.cs", source);
        }
    }

    private static string GenerateToString(INamedTypeSymbol symbol)
    {
        
        var ns = symbol.ContainingNamespace.IsGlobalNamespace 
            ? null 
            : symbol.ContainingNamespace.ToDisplayString();

        var props = symbol.GetMembers()
            .OfType<IPropertySymbol>()
            .Where(p => p.DeclaredAccessibility == Accessibility.Public);

        
        var accessibility = symbol.DeclaredAccessibility.ToString().ToLower();

        var propString = string.Join(", ", props.Select(p => $"{p.Name} = }"));

        var sb = new StringBuilder();
        sb.AppendLine("// <auto-generated/>");
        
        if (ns != null)
        {
            sb.AppendLine($"namespace {ns};");
            sb.AppendLine();
        }

        sb.Append($$"""
         partial class 
        {
            public override string ToString() => $"  }}";
        }
        """);

        return sb.ToString();
    }

}

// ” 2. Attribute trigger (add to generator project) ”—————————————————
[AttributeUsage(AttributeTargets.Class)]
public sealed class GenerateToStringAttribute : Attribute { }

// ” 3. Consumer project ”—————————————————————————————————————————————
// Add reference to generator:
// <ProjectReference Include="../MySourceGenerator/MySourceGenerator.csproj"
//                   OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

[GenerateToString]
public partial class Product    // must be partial
{
    public string Name  { get; set; } = null!;
    public decimal Price { get; set; }
    public int Stock { get; set; }
}

// At compile time, generator adds:
// public override string ToString() => $"Product { Name = {Name}, Price = {Price}, Stock = {Stock} }";

var p = new Product { Name = "Laptop", Price = 999m, Stock = 5 };
Console.WriteLine(p); // Product { Name = Laptop, Price = 999, Stock = 5 }

Real-world source generators in .NET:

↥ back to top

Q. How does the dynamic keyword work in C# and when should you use it?

The dynamic keyword bypasses compile-time type checking. Member resolution happens at runtime via the Dynamic Language Runtime (DLR).

using System.Dynamic;
using Microsoft.CSharp.RuntimeBinder;

// ” 1. Basic dynamic usage ”——————————————————————————————————————————
dynamic value = 42;
Console.WriteLine(value + 8);   // 50 — resolved as int addition at runtime

value = "Hello";
Console.WriteLine(value.Length); // 5 — string.Length resolved at runtime

value = new DateTime(2026, 1, 1);
Console.WriteLine(value.Year);   // 2026 — DateTime.Year at runtime

// ” 2. COM Interop (primary use case) ”———————————————————————————————
// Without dynamic (verbose)
var excel = (Microsoft.Office.Interop.Excel.Application)
    Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application")!);

// With dynamic (clean)
dynamic excelDyn = Activator.CreateInstance(
    Type.GetTypeFromProgID("Excel.Application")!)!;
excelDyn.Visible = true;
dynamic workbook = excelDyn.Workbooks.Add();
dynamic sheet = workbook.Worksheets[1];
sheet.Cells[1, 1] = "Hello from C#!";

// ” 3. Working with JSON / dictionary structures ”—————————————————————
// ExpandoObject — dynamic dictionary that works like an object
dynamic person = new ExpandoObject();
person.Name = "Alice";
person.Age  = 30;
person.Greet = (Func<string>)(() => $"Hi, I'm {person.Name}!");
Console.WriteLine(person.Greet()); // Hi, I'm Alice!

// ExpandoObject implements IDictionary<string, object>
var dict = (IDictionary<string, object?>)person;
dict["Email"] = "alice@example.com";
Console.WriteLine(dict.ContainsKey("Email")); // True

// ” 4. DynamicObject — custom dynamic behavior ”——————————————————————
public class DynamicConfig : DynamicObject
{
    private readonly Dictionary<string, object?> _data = new();

    public override bool TrySetMember(SetMemberBinder binder, object? value)
    {
        _data[binder.Name] = value;
        return true;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object? result)
        => _data.TryGetValue(binder.Name, out result);

    public override bool TryInvokeMember(InvokeMemberBinder binder,
        object?[]? args, out object? result)
    {
        result = $"Invoked: {binder.Name}({string.Join(", ", args ?? [])})";
        return true;
    }
}

dynamic config = new DynamicConfig();
config.ConnectionString = "Server=localhost;Database=mydb";
config.MaxRetries = 3;
Console.WriteLine(config.ConnectionString); // Server=localhost;Database=mydb
Console.WriteLine(config.DoSomething("a", "b")); // Invoked: DoSomething(a, b)

// ” 5. Calling private/internal members (advanced) ”——————————————————
// Use reflection with dynamic for cleaner syntax on legacy APIs
var internalObj = CreateInternalInstance();
dynamic d = internalObj;
// d.InternalMethod(); // Works if member exists — RuntimeBinderException if not

// ” 6. Pitfalls ”————————————————————————————————————————————————————
dynamic x = "hello";
try
{
    int num = x + 5; // RuntimeBinderException — cannot add string and int
}
catch (RuntimeBinderException ex)
{
    Console.WriteLine($"Runtime error: {ex.Message}");
}

// dynamic is ~10-100x slower than static dispatch — avoid in hot paths
// No IntelliSense, no compile-time errors for typos
// Prefer: pattern matching, generics, interfaces over dynamic

object CreateInternalInstance() => new object();

When to use dynamic:

Use case Recommended
COM interop (Office, legacy) … Yes
ExpandoObject for flexible data bags … Acceptable
Unknown JSON structure (prefer JsonNode/JsonElement) Prefer typed approach
Reflection replacement in hot paths No — use compiled delegates
Plugin systems Prefer interfaces
↥ back to top

# 19. ADVANCED C# FEATURES


Q. How does unsafe code and pointers work in C#?

Unsafe code enables direct memory manipulation using pointers — useful for performance-critical interop, image processing, and working with unmanaged APIs.

// ” 1. Enable unsafe code in .csproj ”———————————————————————————————
// <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

// ” 2. Pointer basics ”———————————————————————————————————————————————
unsafe
{
    int value = 42;
    int* ptr  = &value;           // take address
    Console.WriteLine(*ptr);      // dereference: 42
    *ptr = 100;
    Console.WriteLine(value);     // 100 — modified via pointer

    // Pointer arithmetic
    int[] arr = { 10, 20, 30, 40, 50 };
    fixed (int* p = arr)          // pin array so GC doesn\'t move it
    {
        for (int i = 0; i < arr.Length; i++)
            Console.Write(*(p + i) + " "); // 10 20 30 40 50
    }
}

// ” 3. stackalloc — allocate on stack (no GC) ”———————————————————————
unsafe
{
    // Stack-allocated buffer — no heap allocation, no GC pressure
    int* numbers = stackalloc int[8];
    for (int i = 0; i < 8; i++) numbers[i] = i * i;
    for (int i = 0; i < 8; i++) Console.Write(numbers[i] + " "); // 0 1 4 9 16 25 36 49
}

// Preferred: stackalloc with Span<T> (no unsafe keyword needed)
Span<int> safeStack = stackalloc int[8];
for (int i = 0; i < 8; i++) safeStack[i] = i * i;

// ” 4. Structs with fixed-size arrays ”———————————————————————————————
public unsafe struct NetworkHeader
{
    public fixed byte IpAddress[4];     // inline array — no pointer chasing
    public ushort Port;
    public uint Sequence;
}

unsafe
{
    NetworkHeader header = new();
    header.IpAddress[0] = 192;
    header.IpAddress[1] = 168;
    header.IpAddress[2] = 1;
    header.IpAddress[3] = 1;
    header.Port = 8080;
    Console.WriteLine($"IP: {header.IpAddress[0]}.{header.IpAddress[1]}.{header.IpAddress[2]}.{header.IpAddress[3]}:{header.Port}");
}

// ” 5. Interop with native libraries ”———————————————————————————————
[System.Runtime.InteropServices.DllImport("msvcrt.dll", CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
private static unsafe extern void* memcpy(void* dest, void* src, nint count);

// Modern interop: LibraryImport + Span<T> (avoids unsafe, .NET 7+)
[System.Runtime.InteropServices.LibraryImport("msvcrt.dll")]
private static partial void memset_s(nint dest, nint destSize, int value, nint count);

// ” 6. Performance: unsafe struct copy ”——————————————————————————————
public static unsafe void FastCopy(byte[] src, byte[] dst, int length)
{
    fixed (byte* pSrc = src, pDst = dst)
    {
        Buffer.MemoryCopy(pSrc, pDst, dst.Length, length); // hardware-accelerated
    }
}

// Modern alternative: Span<T> (preferred)
public static void SafeCopy(ReadOnlySpan<byte> src, Span<byte> dst)
    => src[..dst.Length].CopyTo(dst); // no unsafe, no pointers
↥ back to top

Q. What are advanced C# patterns — pattern matching, records, and primary constructors?

Modern C# (10–14) provides expressive patterns and type features that reduce boilerplate and improve code clarity.

// ” 1. Extended pattern matching (C# 8–12) ”——————————————————————————
public record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
public record Triangle(double Base, double Height) : Shape;

static string Describe(Shape shape) => shape switch
{
    Circle { Radius: 0 }                => "Degenerate circle",
    Circle { Radius: > 100 }            => "Huge circle",
    Circle c                            => $"Circle r={c.Radius:F1}",
    Rectangle { Width: var w, Height: var h } when w == h
                                        => $"Square {w}x{h}",
    Rectangle(var w, var h)             => $"Rect {w}x{h}",
    Triangle(var b, var h)              => $"Triangle b={b} h={h}",
    null                                => "null",
    _                                   => "unknown"
};

// List patterns (C# 11+)
static string DescribeList(int[] arr) => arr switch
{
    []          => "empty",
    [var x]     => $"one element: {x}",
    [var x, var y] => $"two elements: {x}, {y}",
    [1, 2, ..]  => "starts with 1, 2",
    [.., 99]    => "ends with 99",
    _           => $"{arr.Length} elements"
};

Console.WriteLine(DescribeList([]));         // empty
Console.WriteLine(DescribeList([42]));       // one element: 42
Console.WriteLine(DescribeList([1, 2, 5])); // starts with 1, 2

// ” 2. Records — immutable data with value semantics ”————————————————
public record OrderLine(string ProductId, int Quantity, decimal UnitPrice)
{
    public decimal Total => Quantity * UnitPrice;

    // Custom deconstruct
    public void Deconstruct(out string sku, out decimal total)
        => (sku, total) = (ProductId, Total);
}

var line = new OrderLine("SKU-001", 3, 9.99m);
Console.WriteLine(line);       // OrderLine { ProductId = SKU-001, Quantity = 3, UnitPrice = 9.99 }

var modified = line with { Quantity = 5 }; // non-destructive update
Console.WriteLine(modified.Total); // 49.95

var (sku, total) = line;       // custom deconstruct
Console.WriteLine($"{sku}: £{total:F2}");

// Record struct (C# 10+) — value type record
public record struct Point(double X, double Y)
{
    public double Distance => Math.Sqrt(X * X + Y * Y);
}

// ” 3. Primary constructors (C# 12) ”—————————————————————————————————
// For classes (not just records)
public class OrderService(
    IOrderRepository repository,
    IPublishEndpoint  publishEndpoint,
    ILogger<OrderService> logger)
{
    public async Task<Order> PlaceOrderAsync(PlaceOrderRequest req, CancellationToken ct)
    {
        // Parameters are captured as fields automatically
        logger.LogInformation("Placing order for {Customer}", req.CustomerId);
        var order = new Order(req.CustomerId, req.Items);
        await repository.AddAsync(order, ct);
        await publishEndpoint.Publish(new OrderPlaced(order.Id), ct);
        return order;
    }
}

// ” 4. Required members (C# 11) ”—————————————————————————————————————
public class ProductDto
{
    public required string Name  { get; init; }
    public required decimal Price { get; init; }
    public string? Description  { get; init; }
}

// Compile error if required members not set:
// var p = new ProductDto(); 
var p = new ProductDto { Name = "Laptop", Price = 999m }; // …

// ” 5. Generic math (C# 11+) ”———————————————————————————————————————
using System.Numerics;

static T Average<T>(IEnumerable<T> values) where T : INumber<T>
{
    T sum   = values.Aggregate(T.Zero, (acc, n) => acc + n);
    T count = T.CreateChecked(values.Count());
    return sum / count;
}

Console.WriteLine(Average([1, 2, 3, 4, 5]));           // 3
Console.WriteLine(Average([1.5, 2.5, 3.5]));            // 2.5
Console.WriteLine(Average(new decimal[] { 10m, 20m })); // 15

// ” 6. Interceptors (C# 12, preview) ”———————————————————————————————
// Allow source generators to intercept specific call sites
// Used by EF Core compiled models, System.Text.Json, ASP.NET Core Minimal APIs
↥ back to top

# 20. ARCHITECTURE AND DESIGN PATTERNS


Q. What is Clean Architecture and how do you implement it in .NET?

Clean Architecture (Robert C. Martin) organizes code into concentric layers where inner layers define abstractions and outer layers provide implementations. Dependencies always point inward.

””—————————————————————————————————————————————————
”  Infrastructure  (EF Core, HTTP, Serilog, etc.) ”
”  ””———————————————————————————————————————————  ”
”  ”  Application  (use cases, CQRS handlers)  ”  ”
”  ”  ””—————————————————————————————————————  ”  ”
”  ”  ”  Domain  (entities, value objects,  ”  ”  ”
”  ”  ”  domain events, business rules)     ”  ”  ”
”  ”  ”””—————————————————————————————————————  ”  ”
”  ”””———————————————————————————————————————————  ”
”  Presentation  (API Controllers / Minimal API)  ”
”””—————————————————————————————————————————————————
         Dependencies flow INWARD only ’
MyApp.sln
”” src/
”   ”” MyApp.Domain/           # No external dependencies
”   ”   ”” Entities/
”   ”   ”” ValueObjects/
”   ”   ”” Enums/
”   ”   ”” Events/
”   ”   ””” Exceptions/
”   ”” MyApp.Application/      # Depends only on Domain
”   ”   ”” Interfaces/         # IOrderRepository, IEmailService
”   ”   ”” Commands/
”   ”   ”” Queries/
”   ”   ”” DTOs/
”   ”   ””” Behaviors/          # MediatR pipeline behaviors
”   ”” MyApp.Infrastructure/   # Implements Application interfaces
”   ”   ”” Persistence/        # EF Core, repositories
”   ”   ”” Messaging/          # RabbitMQ, SendGrid
”   ”   ””” Identity/
”   ””” MyApp.Api/              # ASP.NET Core host
”       ”” Controllers/
”       ”” Middleware/
”       ””” Program.cs
””” tests/
    ”” MyApp.Domain.Tests/
    ”” MyApp.Application.Tests/
    ””” MyApp.Api.Tests/
// ” Domain Layer — pure business logic, no framework dependencies ”———
namespace MyApp.Domain.Entities;

public sealed class Order : AggregateRoot
{
    private readonly List<OrderLine> _lines = [];

    public Guid       Id         { get; private set; }
    public string     CustomerId { get; private set; } = null!;
    public OrderStatus Status    { get; private set; }
    public decimal    Total      => _lines.Sum(l => l.Total);
    public IReadOnlyList<OrderLine> Lines => _lines.AsReadOnly();

    private Order() { } // EF Core constructor

    public static Order Create(string customerId, IEnumerable<OrderLine> lines)
    {
        if (string.IsNullOrWhiteSpace(customerId))
            throw new DomainException("CustomerId is required.");

        var order = new Order
        {
            Id         = Guid.NewGuid(),
            CustomerId = customerId,
            Status     = OrderStatus.Pending
        };
        order._lines.AddRange(lines);
        order.AddDomainEvent(new OrderCreatedEvent(order.Id, customerId));
        return order;
    }

    public void Ship(string trackingNumber)
    {
        if (Status != OrderStatus.Paid)
            throw new DomainException("Order must be paid before shipping.");
        Status = OrderStatus.Shipped;
        AddDomainEvent(new OrderShippedEvent(Id, trackingNumber));
    }
}

// ” Application Layer — use case orchestration ”—————————————————————
namespace MyApp.Application.Interfaces;

public interface IOrderRepository
{
    Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default);
    Task AddAsync(Order order, CancellationToken ct = default);
    Task<IReadOnlyList<Order>> GetByCustomerAsync(string customerId, CancellationToken ct = default);
}

// ” Infrastructure Layer — concrete implementations ”—————————————————
namespace MyApp.Infrastructure.Persistence;

public class OrderRepository(AppDbContext db) : IOrderRepository
{
    public Task<Order?> GetByIdAsync(Guid id, CancellationToken ct)
        => db.Orders
             .Include(o => o.Lines)
             .FirstOrDefaultAsync(o => o.Id == id, ct);

    public async Task AddAsync(Order order, CancellationToken ct)
    {
        db.Orders.Add(order);
        await db.SaveChangesAsync(ct);
    }

    public Task<IReadOnlyList<Order>> GetByCustomerAsync(string customerId, CancellationToken ct)
        => db.Orders
             .Where(o => o.CustomerId == customerId)
             .ToListAsync(ct)
             .ContinueWith(t => (IReadOnlyList<Order>)t.Result, ct);
}

// ” Presentation Layer — thin controllers, delegate to application ”———
[ApiController, Route("api/orders")]
public class OrdersController(ISender mediator) : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> Create(
        [FromBody] CreateOrderCommand cmd, CancellationToken ct)
    {
        var orderId = await mediator.Send(cmd, ct);
        return CreatedAtAction(nameof(GetById), new { id = orderId }, new { Id = orderId });
    }

    [HttpGet("{id:guid}")]
    public async Task<IActionResult> GetById(Guid id, CancellationToken ct)
    {
        var order = await mediator.Send(new GetOrderQuery(id), ct);
        return order is null ? NotFound() : Ok(order);
    }
}
↥ back to top

Q. What is CQRS and how do you implement it with MediatR in .NET?

CQRS (Command Query Responsibility Segregation) separates read operations (queries) from write operations (commands). MediatR provides an in-process mediator for dispatching commands and queries.

dotnet add package MediatR
dotnet add package FluentValidation.DependencyInjectionExtensions
// ” 1. COMMANDS — change state, return minimal result ”———————————————
// Command DTO
public sealed record CreateOrderCommand(
    string CustomerId,
    IReadOnlyList<OrderLineDto> Lines) : IRequest<Guid>;

public sealed record OrderLineDto(string ProductId, int Quantity, decimal UnitPrice);

// Command Handler
public sealed class CreateOrderHandler(
    IOrderRepository repository,
    IPublishEndpoint  publishEndpoint,
    ILogger<CreateOrderHandler> logger) : IRequestHandler<CreateOrderCommand, Guid>
{
    public async Task<Guid> Handle(CreateOrderCommand cmd, CancellationToken ct)
    {
        logger.LogInformation("Creating order for customer {CustomerId}", cmd.CustomerId);

        var lines = cmd.Lines.Select(l =>
            OrderLine.Create(l.ProductId, l.Quantity, Money.Of(l.UnitPrice, "GBP")));

        var order = Order.Create(cmd.CustomerId, lines);
        await repository.AddAsync(order, ct);

        return order.Id;
    }
}

// ” 2. QUERIES — read state, never mutate ”———————————————————————————
// Query DTO
public sealed record GetOrderQuery(Guid OrderId) : IRequest<OrderDetailDto?>;

public sealed record OrderDetailDto(
    Guid     OrderId,
    string   CustomerId,
    string   Status,
    decimal  Total,
    IReadOnlyList<OrderLineDetailDto> Lines);

public sealed record OrderLineDetailDto(string ProductId, int Quantity, decimal UnitPrice, decimal Total);

// Query Handler — can use read-optimized data access (dapper, projections)
public sealed class GetOrderHandler(AppDbContext db) : IRequestHandler<GetOrderQuery, OrderDetailDto?>
{
    public async Task<OrderDetailDto?> Handle(GetOrderQuery query, CancellationToken ct)
    {
        return await db.Orders
            .AsNoTracking()
            .Where(o => o.Id == query.OrderId)
            .Select(o => new OrderDetailDto(
                o.Id,
                o.CustomerId,
                o.Status.ToString(),
                o.Lines.Sum(l => l.Quantity * l.UnitPrice),
                o.Lines.Select(l => new OrderLineDetailDto(
                    l.ProductId, l.Quantity, l.UnitPrice, l.Quantity * l.UnitPrice))
                .ToList()))
            .FirstOrDefaultAsync(ct);
    }
}

// ” 3. PIPELINE BEHAVIORS — cross-cutting concerns ”——————————————————
// Validation behavior — run FluentValidation before every command
public sealed class ValidationBehavior<TRequest, TResponse>(
    IEnumerable<IValidator<TRequest>> validators)
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : notnull
{
    public async Task<TResponse> Handle(
        TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        if (!validators.Any()) return await next();

        var context = new ValidationContext<TRequest>(request);
        var failures = validators
            .Select(v => v.Validate(context))
            .SelectMany(r => r.Errors)
            .Where(f => f is not null)
            .ToList();

        if (failures.Count != 0)
            throw new ValidationException(failures);

        return await next();
    }
}

// Logging behavior — log every request/response
public sealed class LoggingBehavior<TRequest, TResponse>(
    ILogger<LoggingBehavior<TRequest, TResponse>> logger)
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : notnull
{
    public async Task<TResponse> Handle(
        TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
    {
        var name = typeof(TRequest).Name;
        logger.LogInformation("Handling {Request}", name);
        var sw = System.Diagnostics.Stopwatch.StartNew();

        var response = await next();

        sw.Stop();
        logger.LogInformation("Handled {Request} in {Ms}ms", name, sw.ElapsedMilliseconds);
        return response;
    }
}

// FluentValidation for command
public sealed class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
    public CreateOrderCommandValidator()
    {
        RuleFor(c => c.CustomerId).NotEmpty().MaximumLength(100);
        RuleFor(c => c.Lines).NotEmpty().WithMessage("Order must have at least one line.");
        RuleForEach(c => c.Lines).ChildRules(line =>
        {
            line.RuleFor(l => l.ProductId).NotEmpty();
            line.RuleFor(l => l.Quantity).GreaterThan(0);
            line.RuleFor(l => l.UnitPrice).GreaterThanOrEqualTo(0);
        });
    }
}

// ” 4. REGISTRATION ”—————————————————————————————————————————————————
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssemblyContaining<CreateOrderHandler>();
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
});
builder.Services.AddValidatorsFromAssemblyContaining<CreateOrderCommandValidator>();
↥ back to top

Q. What is Domain-Driven Design (DDD) and what are its core building blocks?

DDD (Eric Evans) is a software design approach that focuses on modeling complex business domains. The code structure mirrors the business language (Ubiquitous Language).

// ” 1. VALUE OBJECT — defined by its attributes, immutable ”——————————
public sealed class Money : IEquatable<Money>
{
    public decimal Amount   { get; }
    public string  Currency { get; }

    private Money(decimal amount, string currency)
    {
        if (amount < 0)    throw new DomainException("Amount cannot be negative.");
        if (string.IsNullOrWhiteSpace(currency)) throw new DomainException("Currency required.");
        Amount   = amount;
        Currency = currency.ToUpperInvariant();
    }

    public static Money Of(decimal amount, string currency) => new(amount, currency);
    public static Money Zero(string currency) => new(0, currency);

    public Money Add(Money other)
    {
        if (Currency != other.Currency) throw new DomainException("Currency mismatch.");
        return new Money(Amount + other.Amount, Currency);
    }

    public bool Equals(Money? other) => other is not null
        && Amount == other.Amount && Currency == other.Currency;

    public override bool Equals(object? obj) => Equals(obj as Money);
    public override int GetHashCode() => HashCode.Combine(Amount, Currency);
    public override string ToString() => $"{Amount:F2} {Currency}";
}

// ” 2. ENTITY — defined by identity, mutable state ”——————————————————
public abstract class Entity
{
    public Guid Id { get; protected set; } = Guid.NewGuid();

    private readonly List<IDomainEvent> _domainEvents = [];
    public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
    public void AddDomainEvent(IDomainEvent evt) => _domainEvents.Add(evt);
    public void ClearDomainEvents() => _domainEvents.Clear();
}

// ” 3. AGGREGATE ROOT — consistency boundary, only accessible entry ”—
public sealed class Order : Entity
{
    private readonly List<OrderLine> _lines = [];

    public string      CustomerId { get; private set; } = null!;
    public OrderStatus Status     { get; private set; }
    public Money       Total      => _lines.Aggregate(
        Money.Zero("GBP"), (acc, l) => acc.Add(l.Total));

    private Order() { }

    public static Order Create(string customerId)
    {
        var order = new Order { CustomerId = customerId, Status = OrderStatus.Pending };
        order.AddDomainEvent(new OrderCreatedEvent(order.Id, customerId, DateTime.UtcNow));
        return order;
    }

    public OrderLine AddLine(string productId, int quantity, Money unitPrice)
    {
        if (Status != OrderStatus.Pending)
            throw new DomainException("Cannot modify order that is not pending.");
        var line = new OrderLine(Id, productId, quantity, unitPrice);
        _lines.Add(line);
        return line;
    }

    public void Submit()
    {
        if (!_lines.Any()) throw new DomainException("Cannot submit empty order.");
        Status = OrderStatus.Submitted;
        AddDomainEvent(new OrderSubmittedEvent(Id, Total.Amount, DateTime.UtcNow));
    }
}

// ” 4. DOMAIN EVENTS — something significant happened ”——————————————
public interface IDomainEvent { }
public sealed record OrderCreatedEvent(Guid OrderId, string CustomerId, DateTime OccurredAt) : IDomainEvent;
public sealed record OrderSubmittedEvent(Guid OrderId, decimal Total, DateTime OccurredAt) : IDomainEvent;

// Publish domain events after saving (via EF Core interceptor or unit of work)
public class DomainEventPublisher(IPublishEndpoint bus) : SaveChangesInterceptor
{
    public override async ValueTask<int> SavedChangesAsync(
        SaveChangesCompletedEventData data, int result, CancellationToken ct = default)
    {
        var aggregates = data.Context?.ChangeTracker.Entries<Entity>()
            .Select(e => e.Entity)
            .Where(e => e.DomainEvents.Any())
            .ToList() ?? [];

        foreach (var aggregate in aggregates)
        {
            foreach (var evt in aggregate.DomainEvents)
                await bus.Publish(evt, evt.GetType(), ct);
            aggregate.ClearDomainEvents();
        }

        return result;
    }
}

// ” 5. REPOSITORY — abstracts persistence for aggregates only ”———————
public interface IOrderRepository
{
    Task<Order?> FindAsync(Guid id, CancellationToken ct = default);
    Task SaveAsync(Order order, CancellationToken ct = default);
}

// ” 6. DOMAIN SERVICE — logic that doesn\'t belong to a single entity ”
public class PricingService(IProductRepository products)
{
    public async Task<Money> CalculateDiscountedPriceAsync(
        string productId, int quantity, string customerId, CancellationToken ct)
    {
        var product  = await products.FindAsync(productId, ct)
            ?? throw new DomainException("Product not found.");
        var basePrice = product.Price.Amount;
        decimal discount = quantity >= 10 ? 0.1m : quantity >= 5 ? 0.05m : 0m;
        return Money.Of(basePrice * quantity * (1 - discount), product.Price.Currency);
    }
}

// ” 7. BOUNDED CONTEXT MAP ”——————————————————————————————————————————
/*
 ””——————————————————         ””——————————————————
 ”  Order Context   ””ACL”—–”  Catalog Context  ”
 ”  (Order, Line)   ”         ”  (Product, Stock) ”
 ”””——————————————————         ”””——————————————————
        ” Domain Events
        –
 ””——————————————————
 ” Shipping Context ”
 ”””——————————————————

 ACL = Anti-Corruption Layer (translates between contexts)
*/
↥ back to top

Q. What are enterprise integration patterns and how do you implement them in .NET?

Enterprise Integration Patterns (EIP) (Hohpe & Woolf) provide a vocabulary for designing messaging systems. Key patterns: Message Channel, Message Router, Aggregator, Saga, and Dead Letter Queue.

// ” 1. MESSAGE ROUTER — route messages by content ”———————————————————
public class OrderPriorityRouter(
    IMessageChannel standardQueue,
    IMessageChannel priorityQueue) : IConsumer<OrderPlaced>
{
    public Task Consume(ConsumeContext<OrderPlaced> ctx)
    {
        // Route high-value orders to priority processing
        var channel = ctx.Message.Total > 1000m ? priorityQueue : standardQueue;
        return channel.SendAsync(ctx.Message);
    }
}

// ” 2. AGGREGATOR — collect related messages, emit combined result ”———
// Collect all items for an order, then process when complete
public class OrderAggregatorSaga : MassTransitStateMachine<OrderAggregatorState>
{
    public State Aggregating { get; private set; } = null!;
    public Event<OrderItemReceived> ItemReceived { get; private set; } = null!;
    public Schedule<OrderAggregatorState, AggregationTimeout> Timeout { get; private set; } = null!;

    public OrderAggregatorSaga()
    {
        InstanceState(x => x.CurrentState);
        Event(() => ItemReceived, e => e.CorrelateById(m => m.Message.OrderId));
        Schedule(() => Timeout, x => x.TimeoutToken, s =>
        {
            s.Delay  = TimeSpan.FromSeconds(30);
            s.Received = r => r.CorrelateById(m => m.Message.OrderId);
        });

        Initially(
            When(ItemReceived)
                .Then(ctx =>
                {
                    ctx.Saga.OrderId      = ctx.Message.OrderId;
                    ctx.Saga.ExpectedCount = ctx.Message.TotalItems;
                    ctx.Saga.Items.Add(ctx.Message.ItemId);
                })
                .Schedule(Timeout, ctx => new AggregationTimeout(ctx.Saga.CorrelationId))
                .TransitionTo(Aggregating));

        During(Aggregating,
            When(ItemReceived)
                .Then(ctx => ctx.Saga.Items.Add(ctx.Message.ItemId))
                .IfElse(
                    ctx => ctx.Saga.Items.Count >= ctx.Saga.ExpectedCount,
                    complete => complete
                        .Unschedule(Timeout)
                        .Publish(ctx => new AllOrderItemsReceived(ctx.Saga.OrderId, ctx.Saga.Items))
                        .Finalize(),
                    waiting => waiting.TransitionTo(Aggregating)),
            When(Timeout!.Received)
                .Publish(ctx => new OrderAggregationTimedOut(ctx.Saga.OrderId, ctx.Saga.Items))
                .Finalize());
    }
}

// ” 3. DEAD LETTER QUEUE — handle unprocessable messages ”————————————
public class FaultConsumer<T> : IConsumer<Fault<T>> where T : class
{
    private readonly IDeadLetterStore _store;
    private readonly ILogger<FaultConsumer<T>> _logger;

    public FaultConsumer(IDeadLetterStore store, ILogger<FaultConsumer<T>> logger)
        => (_store, _logger) = (store, logger);

    public async Task Consume(ConsumeContext<Fault<T>> context)
    {
        var fault = context.Message;
        _logger.LogError("Message {MessageId} of type {Type} failed after {Retries} retries. Exceptions: {Errors}",
            fault.FaultedMessageId,
            typeof(T).Name,
            fault.RetryCount,
            string.Join("; ", fault.Exceptions.Select(e => e.Message)));

        await _store.StoreAsync(new DeadLetterMessage
        {
            MessageId  = fault.FaultedMessageId?.ToString(),
            MessageType = typeof(T).Name,
            Payload    = System.Text.Json.JsonSerializer.Serialize(fault.Message),
            Errors     = fault.Exceptions.Select(e => e.Message).ToArray(),
            FailedAt   = DateTime.UtcNow
        });
    }
}

// Register fault consumers
x.AddConsumer(typeof(FaultConsumer<OrderPlaced>));
x.AddConsumer(typeof(FaultConsumer<ProcessPayment>));

// ” 4. REQUEST-REPLY — synchronous over async messaging ”—————————————
// Requester
public class InventoryCheckService(IRequestClient<CheckInventory> client)
{
    public async Task<bool> IsAvailableAsync(string productId, int quantity, CancellationToken ct)
    {
        var response = await client.GetResponse<InventoryCheckResult>(
            new CheckInventory(productId, quantity), ct,
            timeout: RequestTimeout.After(s: 5));

        return response.Message.Available;
    }
}

// Responder
public class InventoryConsumer(IInventoryRepository repo) : IConsumer<CheckInventory>
{
    public async Task Consume(ConsumeContext<CheckInventory> ctx)
    {
        var stock = await repo.GetStockAsync(ctx.Message.ProductId);
        await ctx.RespondAsync(
            new InventoryCheckResult(ctx.Message.ProductId, stock >= ctx.Message.Quantity));
    }
}

record CheckInventory(string ProductId, int Quantity);
record InventoryCheckResult(string ProductId, bool Available);
↥ back to top