All topics
Backend · Learning hub

C# notes for developers

Master C# with a curated set of 7 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Backend notes
C#

Language Fundamentals

C#: Language Fundamentals C# is a strongly-typed, object-oriented language developed by Microsoft. It runs on the .NET CLR and is the primary language for ASP.N

C#: Language Fundamentals

C# is a strongly-typed, object-oriented language developed by Microsoft. It runs on the .NET CLR and is the primary language for ASP.NET Core, Unity, and Xamarin/MAUI. Modern C# (10–13) continues to add expressive features like records, pattern matching, and primary constructors.

Types & Variables

// Value types (stored on stack, copied on assignment)
int count = 42;
double pi = 3.14159;
bool isActive = true;
char grade = 'A';
decimal price = 19.99m;   // decimal for money — exact, no float errors

// Reference types (stored on heap, shared reference)
string name = "Alice";
int[] scores = { 95, 82, 78 };
object anything = "can be any type";

// Type inference
var list = new List<string>();   // inferred as List<string>
var user = new User("Alice", 30);

// Constants and readonly
const double Pi = 3.14159;           // compile-time constant
static readonly int MaxSize = 100;   // runtime constant (can be computed)

// Nullable value types
int? nullableInt = null;
double? maybeDouble = GetValue();    // returns null if unavailable
int result = nullableInt ?? 0;       // null coalescing
int len = nullableInt?.ToString().Length ?? 0;  // null conditional

// String
string msg = $"Hello, {name}! You are {count} years old.";  // interpolation
string raw = @"C:\Users\Alice\Documents";  // verbatim — no escape needed
string multiline = """
    This is a raw string literal (C# 11)
    No escaping needed for "quotes" or \backslashes
    """;

Control Flow

// if / else
if (score >= 90)
    grade = 'A';
else if (score >= 80)
    grade = 'B';
else
    grade = 'C';

// Switch statement (classic)
switch (day)
{
    case DayOfWeek.Monday:
    case DayOfWeek.Tuesday:
        Console.WriteLine("Early week");
        break;
    default:
        Console.WriteLine("Other");
        break;
}

// Switch expression (C# 8+) — expression form, returns value
string label = status switch
{
    "active"   => "Active User",
    "inactive" => "Inactive User",
    null       => "Unknown",
    _          => $"Unknown status: {status}",
};

// For / foreach
for (int i = 0; i < 10; i++) { }
foreach (var item in collection) { }

// While / do-while
while (condition) { }
do { } while (condition);

// Pattern matching in if
if (obj is string s && s.Length > 0)
    Console.WriteLine(s.ToUpper());

// throw expressions (C# 7+)
var value = input ?? throw new ArgumentNullException(nameof(input));

Methods & Parameters

// Basic method
public static int Add(int a, int b) => a + b;  // expression body

// Optional parameters
public string Greet(string name, string greeting = "Hello") =>
    $"{greeting}, {name}!";

// Named arguments
Greet(greeting: "Hi", name: "Bob");

// Params — variable arguments
public int Sum(params int[] numbers) => numbers.Sum();
Sum(1, 2, 3, 4, 5);  // no array needed at call site

// Out parameters
bool success = int.TryParse("42", out int value);
if (success) Console.WriteLine(value);

// Ref and in parameters
void Increment(ref int count) => count++;
void Process(in LargeStruct data) { }  // in = readonly ref (no copy, no modify)

// Tuples
(string Name, int Age) GetPerson() => ("Alice", 30);
var (name, age) = GetPerson();  // deconstruct

// Local functions
int Factorial(int n)
{
    return n <= 1 ? 1 : n * Inner(n - 1);

    int Inner(int x) => x <= 1 ? 1 : x * Inner(x - 1);  // local function
}
C#

OOP & Type System

C#: OOP & Type System Classes & Inheritance // Class with properties and constructor public class Animal { public string Name { get; init; } // init-only — set

C#: OOP & Type System

Classes & Inheritance

// Class with properties and constructor
public class Animal
{
    public string Name { get; init; }       // init-only — set only in constructor/initializer
    public int Age { get; private set; }    // public read, private write
    protected string Species { get; set; }

    private static int _count;
    public static int Count => _count;

    public Animal(string name, int age, string species)
    {
        Name = name;
        Age = age;
        Species = species;
        _count++;
    }

    // Virtual — can be overridden
    public virtual string Describe() => $"{Species} named {Name}";

    // Sealed — prevent inheritance of this method
    public sealed override string ToString() => Describe();

    // Static factory
    public static Animal Create(string name) => new("Unknown", 0, name);
}

// Inheritance
public class Dog : Animal
{
    public string Breed { get; }

    public Dog(string name, int age, string breed)
        : base(name, age, "Dog")    // call base constructor
    {
        Breed = breed;
    }

    public override string Describe() => $"{base.Describe()} ({Breed})";

    public void Fetch() => Console.WriteLine($"{Name} fetches!");
}

// Object initializer syntax (uses init/set properties)
var dog = new Dog("Rex", 3, "Labrador") { };

Interfaces & Abstract Classes

// Interface — defines contract
public interface IRepository<T, TKey>
{
    Task<T?> GetByIdAsync(TKey id);
    Task<IEnumerable<T>> GetAllAsync();
    Task<T> CreateAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(TKey id);
}

// Default interface methods (C# 8+)
public interface ILogger
{
    void Log(string message);

    void LogError(string message) => Log($"ERROR: {message}");  // default implementation
}

// Abstract class — partial implementation
public abstract class Shape
{
    public abstract double Area();   // must be overridden
    public abstract double Perimeter();

    public void Print() =>           // concrete — shared implementation
        Console.WriteLine($"Area: {Area()}, Perimeter: {Perimeter()}");
}

public class Circle : Shape
{
    public double Radius { get; }
    public Circle(double radius) => Radius = radius;

    public override double Area() => Math.PI * Radius * Radius;
    public override double Perimeter() => 2 * Math.PI * Radius;
}

// Explicit interface implementation (resolve name conflicts)
public class MultiLogger : ILogger, IDisposable
{
    void ILogger.Log(string message) => Console.WriteLine(message);
    public void Dispose() => Console.WriteLine("Disposed");
}

Generics

// Generic class
public class Stack<T>
{
    private readonly List<T> _items = new();

    public void Push(T item) => _items.Add(item);
    public T Pop()
    {
        var item = _items[^1];
        _items.RemoveAt(_items.Count - 1);
        return item;
    }
    public T Peek() => _items[^1];
    public int Count => _items.Count;
}

// Generic method with constraint
public T Max<T>(T a, T b) where T : IComparable<T>
    => a.CompareTo(b) >= 0 ? a : b;

// Multiple constraints
public class Repository<T> where T : class, IEntity, new()
{
    // T must be a class, implement IEntity, and have parameterless constructor
}

// Covariance (out) and contravariance (in)
IEnumerable<Dog> dogs = GetDogs();
IEnumerable<Animal> animals = dogs;  // IEnumerable<out T> is covariant — works!

Action<Animal> actOnAnimal = a => Console.WriteLine(a.Name);
Action<Dog> actOnDog = actOnAnimal;  // Action<in T> is contravariant — works!

Delegates, Events & Lambdas

// Delegate — type-safe function pointer
public delegate int Operation(int a, int b);
Operation add = (a, b) => a + b;
int result = add(3, 4);  // 7

// Built-in delegate types (prefer over custom delegates)
Func<int, int, int> multiply = (a, b) => a * b;  // returns value
Action<string> print = s => Console.WriteLine(s); // returns void
Predicate<int> isEven = n => n % 2 == 0;         // returns bool

// Events (publisher-subscriber pattern)
public class Button
{
    public event EventHandler<ClickEventArgs>? Clicked;

    protected virtual void OnClicked(ClickEventArgs e) =>
        Clicked?.Invoke(this, e);  // ?. — thread-safe null check

    public void Click() => OnClicked(new ClickEventArgs { X = 10, Y = 20 });
}

// Subscribe
var btn = new Button();
btn.Clicked += (sender, e) => Console.WriteLine($"Clicked at {e.X},{e.Y}");
btn.Click();

// Lambda expressions
var numbers = new[] { 1, 2, 3, 4, 5 };
var even = numbers.Where(n => n % 2 == 0);         // lambda
var squares = numbers.Select(n => n * n);           // projection
var sum = numbers.Aggregate(0, (acc, n) => acc + n);  // fold
C#

LINQ & Collections

C#: LINQ & Collections LINQ (Language Integrated Query) provides a unified syntax for querying arrays, lists, XML, databases, and any IEnumerable<T> source. It

C#: LINQ & Collections

LINQ (Language Integrated Query) provides a unified syntax for querying arrays, lists, XML, databases, and any IEnumerable<T> source. It uses deferred execution — queries run when iterated, not when defined.

LINQ Query Syntax

var users = new List<User> { /* ... */ };

// Query syntax (SQL-like)
var result = from u in users
             where u.Age >= 18
             orderby u.Name
             select new { u.Id, u.Name };

// Method syntax (more composable — prefer for most cases)
var result2 = users
    .Where(u => u.Age >= 18)
    .OrderBy(u => u.Name)
    .Select(u => new { u.Id, u.Name });

// Common LINQ operators
users.Where(u => u.IsActive)            // filter
users.Select(u => u.Name)              // project
users.SelectMany(u => u.Orders)        // flatten nested collections
users.OrderBy(u => u.Name)             // sort ascending
users.OrderByDescending(u => u.Age)    // sort descending
users.GroupBy(u => u.Department)       // group
users.Join(orders,                     // inner join
    u => u.Id,
    o => o.UserId,
    (u, o) => new { u.Name, o.Total })
users.Distinct()                       // remove duplicates
users.Take(10)                         // first N
users.Skip(20).Take(10)               // pagination
users.FirstOrDefault(u => u.Id == 5)  // first match or null
users.SingleOrDefault(u => u.Id == 5) // exactly one match or null
users.Any(u => u.IsAdmin)             // exists
users.All(u => u.IsActive)            // all match
users.Count(u => u.IsActive)         // count matching
users.Sum(u => u.Orders.Count)        // aggregate
users.Min(u => u.Age)
users.Max(u => u.Age)
users.Average(u => u.Score)
users.ToList()                         // materialize — execute query

Grouping & Joins

// GroupBy
var byDepartment = users
    .GroupBy(u => u.Department)
    .Select(g => new {
        Department = g.Key,
        Count = g.Count(),
        AvgAge = g.Average(u => u.Age),
        Names = g.Select(u => u.Name).ToList()
    });

// Left join with GroupJoin
var usersWithOrders = users
    .GroupJoin(orders,
        u => u.Id,
        o => o.UserId,
        (user, userOrders) => new {
            User = user,
            Orders = userOrders.ToList(),
            OrderCount = userOrders.Count()
        });

// Zip — combine two sequences element-by-element
var names = new[] { "Alice", "Bob" };
var scores = new[] { 95, 82 };
var combined = names.Zip(scores, (name, score) => $"{name}: {score}");

// Deferred execution — query is not executed here
var query = users.Where(u => u.IsActive);  // IEnumerable<User> — not yet run
// Executed here:
foreach (var u in query) { }  // or: query.ToList(), query.Count(), etc.

Collections

// List<T> — resizable array
var list = new List<string> { "a", "b", "c" };
list.Add("d");
list.Insert(0, "z");
list.Remove("b");
list.RemoveAt(0);
list.Sort();
list.BinarySearch("c");  // fast search on sorted list

// Dictionary<TKey, TValue>
var dict = new Dictionary<string, int>
{
    ["one"] = 1,
    ["two"] = 2,
};
dict["three"] = 3;
dict.TryGetValue("one", out int val);  // safe get
dict.ContainsKey("four");              // check existence
foreach (var (key, value) in dict) { }  // deconstruct KeyValuePair

// HashSet<T> — unique values, O(1) lookup
var set = new HashSet<int> { 1, 2, 3 };
set.Add(4);
set.Contains(2);  // O(1)
set.UnionWith(new[] { 3, 4, 5 });      // union
set.IntersectWith(new[] { 2, 3 });     // intersection
set.ExceptWith(new[] { 1 });           // difference

// Queue<T> and Stack<T>
var queue = new Queue<string>();
queue.Enqueue("first");
var next = queue.Dequeue();  // FIFO

var stack = new Stack<int>();
stack.Push(1);
var top = stack.Pop();  // LIFO

// ImmutableList, ImmutableDictionary (System.Collections.Immutable)
var immutable = ImmutableList.Create(1, 2, 3);
var added = immutable.Add(4);  // returns new list, original unchanged

// Span<T> — stack-allocated slice (no heap allocation)
Span<int> slice = numbers.AsSpan(2, 3);  // elements 2, 3, 4
C#

async/await & Tasks

C#: async/await & Tasks C# async/await is built on the Task Parallel Library (TPL). async/await transforms methods into state machines that resume on completion

C#: async/await & Tasks

C# async/await is built on the Task Parallel Library (TPL). async/await transforms methods into state machines that resume on completion — no thread is blocked while awaiting.

async/await Basics

// Async method — always returns Task, Task<T>, or ValueTask<T>
public async Task<string> FetchDataAsync(string url)
{
    using var client = new HttpClient();
    var response = await client.GetAsync(url);          // await — no blocking
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
}

// Async void — only for event handlers (errors are unobservable)
private async void Button_Click(object sender, EventArgs e)
{
    var data = await FetchDataAsync("https://api.example.com");
    label.Text = data;
}

// ConfigureAwait(false) — don't capture sync context (use in library code)
var result = await SomeMethodAsync().ConfigureAwait(false);

// CancellationToken — allow callers to cancel
public async Task<List<User>> GetUsersAsync(CancellationToken ct = default)
{
    await Task.Delay(100, ct);   // throws OperationCanceledException if cancelled
    return await _db.Users.ToListAsync(ct);
}

// Call with cancellation
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var users = await GetUsersAsync(cts.Token);

Task Parallelism

// WhenAll — run multiple tasks concurrently, wait for all
var task1 = FetchUsersAsync();
var task2 = FetchOrdersAsync();
var task3 = FetchStatsAsync();

await Task.WhenAll(task1, task2, task3);
var users = await task1;  // already completed — no extra wait

// Shorthand
var (users, orders, stats) = await (task1, task2, task3);  // with tuple await

// WhenAny — return first completed task (timeout pattern)
var dataTask = FetchDataAsync();
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));

var completed = await Task.WhenAny(dataTask, timeoutTask);
if (completed == timeoutTask)
    throw new TimeoutException("Request timed out");
var data = await dataTask;

// Parallel.ForEachAsync (C# 6+ / .NET 6)
await Parallel.ForEachAsync(ids, new ParallelOptions { MaxDegreeOfParallelism = 10 }, async (id, ct) => {
    await ProcessAsync(id, ct);
});

// Channel — producer/consumer async queue
var channel = Channel.CreateBounded<WorkItem>(capacity: 100);
var writer = channel.Writer;
var reader = channel.Reader;

// Producer
await writer.WriteAsync(new WorkItem());

// Consumer
await foreach (var item in reader.ReadAllAsync()) {
    await ProcessItem(item);
}

Exception Handling in Async Code

// Standard try/catch works with await
public async Task ProcessAsync()
{
    try {
        var result = await FetchDataAsync();
        await SaveAsync(result);
    }
    catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound) {
        _logger.LogWarning("Resource not found: {Message}", ex.Message);
    }
    catch (OperationCanceledException) {
        _logger.LogInformation("Operation was cancelled");
        throw;  // re-throw cancellation
    }
    catch (Exception ex) {
        _logger.LogError(ex, "Unexpected error");
        throw;
    }
    finally {
        Cleanup();  // always runs
    }
}

// Task.WhenAll throws AggregateException with all errors
try {
    await Task.WhenAll(task1, task2, task3);
}
catch (Exception) {
    // Check all task exceptions
    var exceptions = new[] { task1, task2, task3 }
        .Where(t => t.IsFaulted)
        .Select(t => t.Exception?.InnerException)
        .ToList();
}

// ValueTask<T> — avoid allocation when result is often synchronous
public ValueTask<int> GetCachedValueAsync(string key)
{
    if (_cache.TryGetValue(key, out var cached))
        return ValueTask.FromResult(cached);  // no allocation

    return new ValueTask<int>(FetchAndCacheAsync(key));  // async path
}
C#

Modern C# Features & Interview Questions

C#: Modern Features & Interview Questions Records (C# 9+) // Record — immutable reference type with value equality public record User(int Id, string Name, strin

C#: Modern Features & Interview Questions

Records (C# 9+)

// Record — immutable reference type with value equality
public record User(int Id, string Name, string Email);

var alice = new User(1, "Alice", "alice@example.com");
var alice2 = new User(1, "Alice", "alice@example.com");
alice == alice2     // true — value equality (not reference)
alice.Equals(alice2)  // true

// With expression — create copy with modifications
var alice3 = alice with { Email = "newalice@example.com" };

// Record struct (value type, stack-allocated)
public record struct Point(double X, double Y);

// Positional records with custom members
public record Order(int Id, List<Item> Items)
{
    public decimal Total => Items.Sum(i => i.Price);
    public bool IsEmpty => !Items.Any();
}

// Deconstruction
var (id, name, email) = alice;

// Primary constructors (C# 12) — for classes too
public class Service(ILogger<Service> logger, IRepository repo)
{
    public async Task Process() => await repo.DoWork();  // logger, repo captured
}

Pattern Matching (C# 7–11)

// Type pattern
if (shape is Circle c)
    Console.WriteLine($"Circle: r={c.Radius}");

// Property pattern
if (user is { IsActive: true, Role: "admin" })
    GrantAccess();

// List pattern (C# 11)
if (numbers is [1, 2, ..var rest])
    Console.WriteLine($"Starts with 1, 2. Rest: {rest.Length} items");

// Switch expression with patterns
decimal Discount(Order order) => order switch
{
    { Total: > 1000, Customer.IsPremium: true } => 0.20m,
    { Total: > 500 }                            => 0.10m,
    { Items.Count: 0 }                          => 0m,
    _                                            => 0.05m,
};

// Relational patterns (C# 9)
string Grade(int score) => score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    < 0   => throw new ArgumentException("Negative score"),
    _     => "F",
};

// Logical patterns
bool IsWorkday(DayOfWeek day) =>
    day is not (DayOfWeek.Saturday or DayOfWeek.Sunday);

Nullable Reference Types & Span

// Enable in .csproj: <Nullable>enable</Nullable>
// Now all reference types are non-nullable by default
string name = "Alice";    // cannot be null — compiler warns
string? nickname = null;  // explicitly nullable

// Null-forgiving operator (tell compiler "trust me, not null")
string guaranteed = GetValue()!;  // suppresses warning

// Required members (C# 11)
public class Config
{
    public required string ConnectionString { get; init; }  // must be set
    public required int MaxConnections { get; init; }
}

var config = new Config
{
    ConnectionString = "Server=...",
    MaxConnections = 100
};  // compiler error if required properties missing

// Span<T> and Memory<T> — zero-copy slicing
void ProcessLine(ReadOnlySpan<char> line)
{
    // Span — no heap allocation, can't be stored
    var trimmed = line.Trim();
    var parts = trimmed.IndexOf(':');
}

// stackalloc — stack allocation for small buffers
Span<int> buffer = stackalloc int[10];
for (int i = 0; i < buffer.Length; i++)
    buffer[i] = i * 2;

Interview Questions

  • What is the difference between class and struct? Class is a reference type (heap), struct is a value type (stack). Structs are copied on assignment. Use structs for small, immutable data (Point, Color). Classes for identity-based objects.

  • Explain boxing and unboxing. Boxing wraps a value type in an object (heap allocation). Unboxing extracts it. Avoid in hot paths — use generics (List<int> not ArrayList) to prevent boxing.

  • What is the difference between == and .Equals()? By default == compares references for classes; .Equals() can be overridden for value equality. Records override both for value equality. Always override both together.

  • What is IDisposable and the using statement? IDisposable.Dispose() releases unmanaged resources. using ensures Dispose() is called even if an exception occurs. using declaration (C# 8+): `using var conn = new SqlConnection(...)` — disposed at end of scope.

  • What is a delegate vs event? Delegate is a type-safe function pointer. Event wraps a delegate with access restrictions — outside classes can only += or -= not = (preventing replacement of all handlers).

  • Explain the difference between IEnumerable and IQueryable. IEnumerable pulls all data into memory then filters. IQueryable translates LINQ to SQL — filtering happens in the DB. Use IQueryable for EF Core to avoid loading unnecessary data.

  • What are expression trees? Data structures representing code as data. EF Core uses them to translate LINQ expressions to SQL. Created when assigning a lambda to Expression<Func<T, TResult>> instead of Func<T, TResult>.

  • What is the Task Parallel Library (TPL)? The foundation of async in .NET. Task represents an async operation. Task.Run() offloads work to the thread pool. async/await is syntactic sugar over TPL.

  • Difference between async Task and async void? async Task allows callers to await and catch exceptions. async void is fire-and-forget — exceptions go to the SynchronizationContext and can crash the app. Only use async void for event handlers.

  • What are extension methods? Static methods that appear as instance methods on a type. Defined in static classes with `this` as first parameter. LINQ is built entirely on extension methods on IEnumerable<T>.

C#

Reflection, Attributes & Source Generators

C#: Reflection, Attributes & Source Generators Custom Attributes // Define a custom attribute [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,

C#: Reflection, Attributes & Source Generators

Custom Attributes

// Define a custom attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
    AllowMultiple = false, Inherited = true)]
public class AuditAttribute : Attribute
{
    public string Action { get; }
    public bool LogParameters { get; set; } = true;

    public AuditAttribute(string action)
    {
        Action = action;
    }
}

// Apply it
[Audit("UserService")]
public class UserService
{
    [Audit("CreateUser", LogParameters = false)]
    public async Task<User> CreateAsync(CreateUserDto dto) { }
}

// Built-in attributes you use daily
[Obsolete("Use NewMethod instead", error: false)]
[Serializable]
[JsonPropertyName("user_id")]
[Required, MaxLength(100)]
[HttpGet, Route("api/users")]
[Authorize(Roles = "admin")]

Reflection

using System.Reflection;

// Inspect types at runtime
Type type = typeof(UserService);
Type runtimeType = someObject.GetType();
Type byName = Type.GetType("MyApp.Services.UserService, MyApp");

// Properties
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
    var value = prop.GetValue(someObject);
    prop.SetValue(someObject, newValue);
}

// Methods
var method = type.GetMethod("CreateAsync");
var result = method!.Invoke(instance, new object[] { dto });

// Attributes
var audit = type.GetCustomAttribute<AuditAttribute>();
if (audit is not null)
    Console.WriteLine(audit.Action);

// Check all methods with [Audit]
var auditedMethods = type.GetMethods()
    .Where(m => m.IsDefined(typeof(AuditAttribute), inherit: true))
    .ToList();

// Create instance dynamically
var instance = Activator.CreateInstance(type, arg1, arg2);
var generic = Activator.CreateInstance(typeof(Repository<>).MakeGenericType(entityType));

// Performance: cache reflected members — reflection is slow on first call
// Use cached delegates or compiled expressions for hot paths:

Expression Trees

// Expression<Func<T,R>> — code as data (inspectable + translatable)
// Used by EF Core to translate LINQ to SQL

Expression<Func<User, bool>> filter = u => u.IsActive && u.Age > 18;

// Inspect the tree
var body = (BinaryExpression)filter.Body;  // &&
var left = (MemberExpression)body.Left;    // u.IsActive
Console.WriteLine(left.Member.Name);       // "IsActive"

// Compile to delegate when needed
var func = filter.Compile();
bool result = func(someUser);

// Build expressions programmatically (for dynamic queries)
var param = Expression.Parameter(typeof(User), "u");
var prop = Expression.Property(param, "Name");
var value = Expression.Constant("Alice");
var eq = Expression.Equal(prop, value);
var lambda = Expression.Lambda<Func<User, bool>>(eq, param);
// u => u.Name == "Alice"

// Use in EF Core
var users = await _db.Users.Where(lambda).ToListAsync();

Source Generators

Source Generators (Roslyn) run at compile time and generate additional C# code. They eliminate runtime reflection overhead. Used in: System.Text.Json (JsonSerializerContext), EF Core compiled models, Mapster, AutoMapper.

// Using source-generated JSON serialization (zero reflection at runtime)
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(List<User>))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    WriteIndented = false)]
public partial class AppJsonContext : JsonSerializerContext { }

// Register in ASP.NET Core
builder.Services.ConfigureHttpJsonOptions(opts =>
    opts.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));

// Serialize/deserialize with context (no reflection)
var json = JsonSerializer.Serialize(user, AppJsonContext.Default.User);
var user = JsonSerializer.Deserialize(json, AppJsonContext.Default.User);

// Regex source generators (C# 7 / .NET 7+)
[GeneratedRegex(@"^[^@]+@[^@]+\.[^@]+$", RegexOptions.IgnoreCase)]
private static partial Regex EmailRegex();

bool isValid = EmailRegex().IsMatch(email);  // compiled at build time, not runtime
C#

Testing & Benchmarking

C#: Testing & Benchmarking xUnit Patterns // Shared context — expensive setup once per class public class DatabaseFixture : IDisposable { public AppDbContext Db

C#: Testing & Benchmarking

xUnit Patterns

// Shared context — expensive setup once per class
public class DatabaseFixture : IDisposable
{
    public AppDbContext Db { get; }

    public DatabaseFixture()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase("SharedTestDb")
            .Options;
        Db = new AppDbContext(options);
        Db.Database.EnsureCreated();
        SeedTestData(Db);
    }

    public void Dispose() => Db.Dispose();
}

// IClassFixture — one instance per test class
public class UserRepositoryTests : IClassFixture<DatabaseFixture>
{
    private readonly AppDbContext _db;
    public UserRepositoryTests(DatabaseFixture fixture) => _db = fixture.Db;

    [Fact]
    public async Task GetByIdAsync_ExistingUser_ReturnsUser()
    {
        var repo = new UserRepository(_db);
        var user = await repo.GetByIdAsync(1);
        Assert.NotNull(user);
    }
}

// ICollectionFixture — shared across multiple test classes
[CollectionDefinition("Database")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture> { }

[Collection("Database")]
public class PostRepositoryTests { }

Test Builder Pattern

// Builder for complex test objects — avoids fragile constructors in tests
public class UserBuilder
{
    private string _name = "Test User";
    private string _email = "test@example.com";
    private string _role = "user";
    private bool _isActive = true;

    public UserBuilder WithName(string name) { _name = name; return this; }
    public UserBuilder WithEmail(string email) { _email = email; return this; }
    public UserBuilder AsAdmin() { _role = "admin"; return this; }
    public UserBuilder Inactive() { _isActive = false; return this; }

    public User Build() => new() {
        Id = Random.Shared.Next(1, 10000),
        Name = _name,
        Email = _email,
        Role = _role,
        IsActive = _isActive,
    };
}

// Bogus — fake data generation (more powerful)
// dotnet add package Bogus
var faker = new Faker<User>()
    .RuleFor(u => u.Id, f => f.IndexFaker + 1)
    .RuleFor(u => u.Name, f => f.Name.FullName())
    .RuleFor(u => u.Email, (f, u) => f.Internet.Email(u.Name))
    .RuleFor(u => u.CreatedAt, f => f.Date.Past());

var user = faker.Generate();
var users = faker.Generate(100);

BenchmarkDotNet

// dotnet add package BenchmarkDotNet
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]      // reports allocations
[SimpleJob(RuntimeMoniker.Net90)]
public class StringBenchmarks
{
    private const int N = 1000;
    private readonly string[] _words = Enumerable.Range(0, N)
        .Select(i => $"word{i}").ToArray();

    [Benchmark]
    public string Concatenation()
    {
        var result = string.Empty;
        foreach (var word in _words) result += word + " ";
        return result;
    }

    [Benchmark]
    public string StringBuilder()
    {
        var sb = new System.Text.StringBuilder();
        foreach (var word in _words) sb.Append(word).Append(' ');
        return sb.ToString();
    }

    [Benchmark]
    public string StringJoin() => string.Join(' ', _words);
}

// Run benchmarks (must be Release mode)
// dotnet run -c Release
BenchmarkRunner.Run<StringBenchmarks>();

// Output includes: Mean, StdDev, Gen0/1/2 GC, Allocated bytes

Code Coverage & Best Practices

  • Coverage: dotnet test --collect:"XPlat Code Coverage" — generates coverage.cobertura.xml. Use ReportGenerator to view HTML.

  • AAA pattern: every test has Arrange (setup), Act (execute), Assert (verify) — never mix.

  • One assert per test: one logical concept per test — easier to diagnose failures.

  • Test names: MethodName_Scenario_ExpectedResult — e.g., GetById_UserNotFound_ReturnsNull.

  • Avoid testing implementation details — test behavior (outputs/side effects), not internals.

  • Mutation testing: Stryker.NET — mutates your code and checks if tests catch it; reveals weak test coverage.

  • Test in all three layers: unit (fast, isolated), integration (real DB/HTTP), E2E (Playwright for UI).

  • Mock only what you own: don't mock third-party libraries directly. Wrap them behind an interface.

Keep your C# knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever