---
description: Modern C# Language Features
alwaysApply: false
---

# Modern C# Language Features

Use modern features deliberately for safety, clarity, and performance.

## Nullable Reference Types

- `string` = non-null, `string?` = nullable — respect the distinction
- `ArgumentNullException.ThrowIfNull()` at public API boundaries
- Never disable nullable warnings project-wide
- Avoid null-forgiving (`!`) — if you need it, the design may be wrong

## Records

```csharp
public record CreateUserRequest(string Name, string Email);
public readonly record struct Coordinate(double Lat, double Lon);
var updated = original with { Email = "new@example.com" };
```

## Pattern Matching

```csharp
public static decimal CalculateDiscount(Order order) => order switch {
    { Total: > 1000, Customer.IsPremium: true } => order.Total * 0.15m,
    { Total: > 500 } => order.Total * 0.10m,
    { Customer.IsPremium: true } => order.Total * 0.05m,
    _ => 0m
};
```

## LINQ

- Composable, declarative: `.Where().OrderBy().Select().ToList()`
- Never use LINQ for side effects — use `foreach` instead
- Avoid multiple enumerations of `IEnumerable` — materialize with `.ToList()` first
- `Any()` over `Count() > 0` (short-circuits)

## Spans and Memory

```csharp
public static bool StartsWithDigit(ReadOnlySpan<char> input)
    => !input.IsEmpty && char.IsDigit(input[0]);
// stackalloc for small buffers: Span<byte> buf = stackalloc byte[256];
```

## Collection Expressions (C# 12+)

```csharp
int[] numbers = [1, 2, 3, 4, 5];
int[] combined = [..firstHalf, ..secondHalf];
```

## Primary Constructors (C# 12+)

```csharp
public class OrderService(IOrderRepository repo, ILogger<OrderService> logger) {
    public async Task<Order> GetByIdAsync(int id) {
        logger.LogInformation("Fetching order {OrderId}", id);
        return await repo.GetByIdAsync(id)
            ?? throw new NotFoundException($"Order {id} not found");
    }
}
```

## Anti-Patterns

- **Stringly-typed code** — use enums, records, or discriminated unions
- **Exceptions for control flow** — use `FirstOrDefault` or `TryGetValue`
- **Mutable static state** — use `IMemoryCache` or proper caching abstraction
