Skill v1.0.1
currentLLM-judged scan90/100+3 new
version: "1.0.1" name: modern-csharp description: > Modern C# language features for .NET 10 and C# 14. Covers primary constructors, collection expressions, the field keyword, extension members, records, pattern matching, spans, and raw string literals. Load this skill when writing any new C# code, reviewing existing code for modernization, using "modern C#", "C# 14", "primary constructor", "collection expression", "records", "pattern matching", "span", "field keyword", or "extension members". Always loaded as the baseline for all agents.
Modern C# (C# 14 / .NET 10)
Core Principles
- Use the newest stable features — C# 14 is the target. Prefer language-level constructs over library workarounds.
- Readability over cleverness — Pattern matching and expression-bodied members improve readability when used appropriately; deeply nested patterns do not.
- Value types where possible — Prefer
record struct,Span<T>, and stack allocation to reduce GC pressure. - Immutability by default — Use
record,readonly,init, andrequiredto make illegal states unrepresentable.
Patterns
Well-Known Features Quick Reference
| Feature | Usage | Example | |
|---|---|---|---|
| Primary constructors | DI injection, eliminate field assignments | public class OrderService(IOrderRepo repo, TimeProvider clock) { } | |
| Collection expressions | [] for all collection types + spread | List<string> names = ["Alice", "Bob"]; / int[] all = [..a, ..b, 99]; | |
| Records | DTOs, value objects, immutable data | public record CreateOrderRequest(string CustomerId, List<OrderItem> Items); | |
readonly record struct | Small stack-allocated value types | public readonly record struct Money(decimal Amount, string Currency); | |
| Pattern matching | Switch expressions, list/property patterns | order switch { { Total: > 1000 } => "Premium", _ => "Standard" }; | |
| List patterns | Deconstruct arrays/lists | items switch { [] => "Empty", [var x] => $"One: {x}", [var f, .., var l] => $"{f}..{l}" }; | |
Span<T> | Zero-allocation slicing | ReadOnlySpan<char> trimmed = input.Trim(); int.TryParse(trimmed[4..], out id); | |
| Raw string literals | Multi-line SQL, JSON, XML | var sql = """ SELECT ... """; / interpolated: $$""" {"id": "{{id}}"} """; | |
required members | Enforce initialization | public required string ConnectionString { get; init; } | |
is pattern + extraction | Null/type/property check | if (result is { IsSuccess: true, Value: var order }) { ... } |
The field Keyword (C# 14)
Access the auto-generated backing field in property accessors without declaring it manually.
// GOOD — field keyword for validation in auto-propertypublic class Product{public string Name{get => field;set => field = value?.Trim() ?? throw new ArgumentNullException(nameof(value));}public decimal Price{get => field;set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));}}
Lazy Initialization with field
public class ProductCatalog{// Lazy-load on first access — no manual Lazy<T> or backing fieldpublic IReadOnlyList<Product> Products{get => field ??= LoadProducts();}private static List<Product> LoadProducts() => /* expensive load */;}
Change Notification with field
// INotifyPropertyChanged without manual backing fieldspublic class OrderViewModel : INotifyPropertyChanged{public event PropertyChangedEventHandler? PropertyChanged;public string CustomerName{get => field;set{if (field == value) return;field = value;PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CustomerName)));}} = "";public decimal Total{get => field;set{if (field == value) return;field = value;PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Total)));}}}
Extension Members (C# 14)
C# 14 adds extension blocks inside static classes. Unlike classic extension methods, they support extension properties and static extension members — the receiver is declared once for the whole block.
// GOOD — extension block (shipped C# 14 syntax)public static class OrderExtensions{extension(Order order){public decimal TotalWithTax => order.Total * 1.2m;public bool IsHighValue => order.Total > 1000m;public string ToSummary() =>$"Order #{order.Id}: {order.Total:C} ({order.Items.Count} items)";}// Static extension members use the type (no receiver instance)extension(Order){public static Order Empty => Order.Create("none", [], DateTimeOffset.MinValue);}}// Callers see them as if declared on Orderif (order.IsHighValue) { /* ... */ }
Classic this-parameter extension methods still work and coexist — use extension blocks when you need properties or several members on the same receiver.
Anti-patterns
Don't Use Obsolete Patterns When Modern Alternatives Exist
// BAD — manual backing field when field keyword worksprivate string _name;public string Name{get => _name;set => _name = value ?? throw new ArgumentNullException();}// BAD — old-style collection initializationvar list = new List<int>() { 1, 2, 3 };// BAD — Tuple instead of record for domain types(string Name, decimal Price) product = ("Widget", 9.99m);// GOOD — recordpublic record Product(string Name, decimal Price);
Don't Over-pattern-match
// BAD — deeply nested pattern that's hard to readif (order is { Customer: { Address: { Country: { Code: "US" } } } })// GOOD — extract to a clear method or use sequential checksif (order.Customer.Address.Country.Code == "US")
Don't Use var When the Type Is Not Obvious
// BAD — what type is this?var result = Process(order);// GOOD — explicit type when not obviousResult<Order> result = Process(order);// Also GOOD — var is fine when type is apparentvar orders = new List<Order>();
Decision Guide
| Scenario | Recommendation | |
|---|---|---|
| DTO / API contract | record (reference type) | |
| Small value object (2-3 fields) | readonly record struct | |
| Service with DI | Primary constructor | |
| Collection creation | Collection expression [] | |
| Property with validation | field keyword | |
| Multi-line string (SQL, JSON) | Raw string literal """ | |
| Slicing strings/arrays | Span<T> | |
| Type checking + extraction | Pattern matching with is / switch | |
| Enforced initialization | required modifier | |
| Adding methods to external types | Extension members |