All skills
Skillintermediate

Modern C# Patterns

```csharp namespace MyApp.Domain;

Claude Code Knowledge Pack7/10/2026

Overview

Modern C# Patterns

File-Scoped Namespaces and Primary Constructors

namespace MyApp.Domain;

// Primary constructor (C# 12)
public class ProductService(IProductRepository repository, ILogger logger)
{
    public async Task<Product?> GetByIdAsync(int id, CancellationToken ct = default)
    {
        logger.LogInformation("Fetching product {ProductId}", id);
        return await repository.GetByIdAsync(id, ct);
    }
}

// Record with primary constructor
public record Product(int Id, string Name, decimal Price)
{
    public bool IsExpensive => Price > 100m;
}

Record Types and Pattern Matching

// Immutable record
public record Customer(int Id, string Name, string Email);

// Record with validation
public record OrderRequest(int ProductId, int Quantity)
{
    public OrderRequest : this(ProductId, Quantity)
    {
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(Quantity);
    }
}

// Pattern matching with records
public decimal CalculateDiscount(Customer customer, Order order) => customer switch
{
    { Id: > 1000 } => order.Total * 0.2m,          // Premium customer
    { Name: "VIP" } => order.Total * 0.3m,          // VIP
    _ when order.Total > 500 => order.Total * 0.1m, // Large order
    _ => 0m
};

// List patterns (C# 11+)
public string DescribeItems(int[] items) => items switch
{
    [] => "Empty",
    [var single] => $"One item: {single}",
    [var first, .., var last] => $"Multiple items from {first} to {last}",
    _ => "Unknown"
};

Nullable Reference Types

#nullable enable

public class UserService
{
    // Non-nullable parameter and return type
    public User CreateUser(string email, string name)
    {
        ArgumentNullException.ThrowIfNull(email);
        ArgumentNullException.ThrowIfNull(name);

        return new User { Email = email, Name = name };
    }

    // Nullable return type
    public User? FindUserByEmail(string? email)
    {
        if (string.IsNullOrWhiteSpace(email))
            return null;

        return _repository.Find(email);
    }

    // Required modifier (C# 11)
    public class User
    {
        public required string Email { get; init; }
        public required string Name { get; init; }
        public string? PhoneNumber { get; init; } // Optional
    }
}

// Null-forgiving operator (use sparingly)
var user = FindUserById(id)!; // Only if you're certain

// Null-coalescing assignment
_cache ??= new Dictionary<string, object>();

Modern Collection Patterns

// Collection expressions (C# 12)
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Alice", "Bob", "Charlie"];

// Spread operator
int[] moreNumbers = [..numbers, 6, 7, 8];
string[] allNames = [..names, "David"];

// ReadOnly collections
public IReadOnlyList Products { get; } = [product1, product2];

// Frozen collections for performance
using System.Collections.Frozen;

private static readonly FrozenDictionary<string, int> StatusCodes =
    new Dictionary<string, int>
    {
        ["Active"] = 1,
        ["Inactive"] = 2,
        ["Pending"] = 3
    }.ToFrozenDictionary();

Expression-Bodied Members

public class Product
{
    private decimal _price;

    // Expression-bodied property
    public decimal Price
    {
        get => _price;
        init => _price = value > 0 ? value : throw new ArgumentException();
    }

    // Expression-bodied method
    public decimal GetPriceWithTax(decimal taxRate) => _price * (1 + taxRate);

    // Expression-bodied constructor (with validation)
    public Product(string name) => Name = !string.IsNullOrWhiteSpace(name)
        ? name
        : throw new ArgumentException(nameof(name));

    public required string Name { get; init; }
}

String Interpolation and Raw Strings

// Raw string literals (C# 11)
var json = """
    {
        "name": "Product",
        "price": 99.99,
        "available": true
    }
    """;

// Interpolated raw strings
var productJson = $$"""
    {
        "id": {{product.Id}},
        "name": "{{product.Name}}",
        "price": {{product.Price}}
    }
    """;

// UTF-8 string literals
ReadOnlySpan<byte> utf8 = "Hello"u8;

Global Using Directives

// GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.DependencyInjection;

Source Generators (Preparation)

// Use partial classes for source generators
public partial class UserRepository
{
    // Generator will add methods here
}

// Example: JsonSerializer source generation
using System.Text.Json.Serialization;

[JsonSerializable(typeof(Product))]
[JsonSerializable(typeof(List))]
internal partial class AppJsonContext : JsonSerializerContext
{
}

// Usage
var json = JsonSerializer.Serialize(product, AppJsonContext.Default.Product);

Discriminated Unions with Records

// Base record for result pattern
public abstract record Result
{
    public record Success(T Value) : Result;
    public record Failure(string Error) : Result;
}

// Usage
public Result GetUser(int id) =>
    _repository.Find(id) is User user
        ? new Result.Success(user)
        : new Result.Failure("User not found");

// Pattern matching on result
var message = GetUser(id) switch
{
    Result.Success(var user) => $"Found: {user.Name}",
    Result.Failure(var error) => $"Error: {error}",
    _ => "Unknown"
};

Quick Reference

FeatureC# VersionExample
File-scoped namespaceC# 10namespace MyApp;
Primary constructorsC# 12class Service(ILogger logger)
Required membersC# 11public required string Name { get; init; }
Raw string literalsC# 11var s = """ multi-line """;
List patternsC# 11[1, 2, .., var last]
Collection expressionsC# 12int[] x = [1, 2, 3];
Init-only propertiesC# 9public string Name { get; init; }
Record typesC# 9record Person(string Name);