Skip to content
cccskills
FreePublished on GitHub

dotnet-csharp-modern-patterns

Using records, pattern matching, primary constructors, collection expressions. C# 12-15 by TFM.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md12.8 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

dotnet-csharp-modern-patterns

Modern C# language feature guidance adapted to the project's target framework. Always run [skill:dotnet-version-detection] first to determine TFM and C# version.

Scope

  • Records, pattern matching, primary constructors, collection expressions
  • C# 12-15 feature usage mapped to TFM
  • Language feature adoption guidance

Out of scope

  • Naming and style conventions -- see [skill:dotnet-csharp-coding-standards]
  • Async/await patterns -- see [skill:dotnet-csharp-async-patterns]
  • Source generator usage (GeneratedRegex, LoggerMessage) -- see [skill:dotnet-csharp-source-generators]

Cross-references: [skill:dotnet-csharp-coding-standards] for naming/style conventions, [skill:dotnet-csharp-async-patterns] for async-specific patterns.


Quick Reference: TFM to C# Version

TFMC#Key Language Features
net8.012Primary constructors, collection expressions, alias any type
net9.013params collections, Lock type, partial properties
net10.014field keyword, extension blocks, nameof unbound generics
net11.015 (preview)Collection expression with() arguments

Records

Use records for immutable data transfer objects, value semantics, and domain modeling where equality is based on values rather than identity.

Record Classes (reference type)


// Positional record: concise, immutable, value equality
public record OrderSummary(int OrderId, decimal Total, DateOnly OrderDate);

// With additional members
public record Customer(string Name, string Email)
{
    public string DisplayName => $"{Name} <{Email}>";
}

```text

### Record Structs (value type, C# 10+)

```csharp

// Positional record struct: value type with value semantics
public readonly record struct Point(double X, double Y);

// Mutable record struct (rare -- prefer readonly)
public record struct MutablePoint(double X, double Y);

```text

### When to Use Records vs Classes

| Use Case                             | Prefer                   |
| ------------------------------------ | ------------------------ |
| DTOs, API responses                  | `record`                 |
| Domain value objects (Money, Email)  | `readonly record struct` |
| Entities with identity (User, Order) | `class`                  |
| High-throughput, small data          | `readonly record struct` |
| Inheritance needed                   | `record` (class-based)   |

### Non-destructive Mutation

```csharp

var updated = order with { Total = order.Total + tax };

```csharp

---

## Primary Constructors (C# 12+, net8.0+)

Capture constructor parameters directly in the class/struct body. Parameters become available throughout the type but
are **not** fields or properties -- they are captured state.

### For Services (DI injection)

```csharp

public class OrderService(IOrderRepository repo, ILogger<OrderService> logger)
{
    public async Task<Order> GetAsync(int id)
    {
        logger.LogInformation("Fetching order {OrderId}", id);
        return await repo.GetByIdAsync(id);
    }
}

```text

### Gotchas

- Primary constructor parameters are **mutable** captures, not `readonly` fields. If immutability matters, assign to a
  `readonly` field in the body.
- Do not use primary constructors when you need to validate parameters at construction time -- use a traditional
  constructor with guard clauses instead.
- For records, positional parameters become public properties automatically. For classes/structs, they remain private
  captures.

```csharp

// Explicit readonly field when immutability matters
public class Config(string connectionString)
{
    private readonly string _connectionString = connectionString
        ?? throw new ArgumentNullException(nameof(connectionString));
}

```text

---

## Collection Expressions (C# 12+, net8.0+)

Unified syntax for creating collections with `[...]`.

```csharp

// Array
int[] numbers = [1, 2, 3];

// List
List<string> names = ["Alice", "Bob"];

// Span
ReadOnlySpan<byte> bytes = [0x00, 0xFF];

// Spread operator
int[] combined = [..first, ..second, 99];

// Empty collection
List<int> empty = [];

```text

### Collection Expression with Arguments (C# 15 preview, net11.0+)

Specify capacity, comparers, or other constructor arguments:

```csharp

// Capacity hint
List<int> nums = [with(capacity: 1000), ..Generate()];

// Custom comparer
HashSet<string> set = [with(comparer: StringComparer.OrdinalIgnoreCase), "Alice", "bob"];

// Dictionary with comparer
Dictionary<string, int> map = [with(comparer: StringComparer.OrdinalIgnoreCase),
    new("key1", 1), new("key2", 2)];

```text

> **net11.0+ only.** Requires `<LangVersion>preview</LangVersion>`. Do not use on earlier TFMs.

---

## Pattern Matching

### Switch Expressions (C# 8+)

```csharp

string GetDiscount(Customer customer) => customer switch
{
    { Tier: "Gold", YearsActive: > 5 } => "30%",
    { Tier: "Gold" } => "20%",
    { Tier: "Silver" } => "10%",
    _ => "0%"
};

```text

### List Patterns (C# 11+)

```csharp

bool IsValid(int[] data) => data is [> 0, .., > 0]; // first and last positive

string Describe(int[] values) => values switch
{
    [] => "empty",
    [var single] => $"single: {single}",
    [var first, .., var last] => $"range: {first}..{last}"
};

```text

### Type and Property Patterns

```csharp

decimal CalculateShipping(object package) => package switch
{
    Letter { Weight: < 50 } => 0.50m,
    Parcel { Weight: var w } when w < 1000 => 5.00m + w * 0.01m,
    Parcel { IsOversized: true } => 25.00m,
    _ => 10.00m
};

```text

---

## `required` Members (C# 11+)

Force callers to initialize properties at construction via object initializers.

```csharp

public class UserDto
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public string? Phone { get; init; }
}

// Compiler enforces Name and Email
var user = new UserDto { Name = "Alice", Email = "alice@example.com" };

```text

Useful for DTOs that need to be deserialized (System.Text.Json honors `required` in .NET 8+).

---

## `field` Keyword (C# 14, net10.0+)

Access the compiler-generated backing field directly in property accessors.

```csharp

public class TemperatureSensor
{
    public double Reading
    {
        get => field;
        set => field = value >= -273.15
            ? value
            : throw new ArgumentOutOfRangeException(nameof(value));
    }
}

```text

Replaces the manual pattern of declaring a private field plus a property with custom logic. Use when you need validation
or transformation in a setter without a separate backing field.

> **net10.0+ only.** On earlier TFMs, use a traditional private field.

---

## Extension Blocks (C# 14, net10.0+)

Group extension members for a type in a single block.

```csharp

public static class EnumerableExtensions
{
    extension<T>(IEnumerable<T> source) where T : class
    {
        public IEnumerable<T> WhereNotNull()
            => source.Where(x => x is not null);

        public bool IsEmpty()
            => !source.Any();
    }
}

```text

> **net10.0+ only.** On earlier TFMs, use traditional `static` extension methods.

---

## Alias Any Type (`using`, C# 12+, net8.0+)

```csharp

using Point = (double X, double Y);
using UserId = System.Guid;

Point origin = (0, 0);
UserId id = UserId.NewGuid();

```text

Useful for tuple aliases and domain type aliases without creating a full type.

---

## `params` Collections (C# 13, net9.0+)

`params` now supports additional collection types beyond arrays, including `Span<T>`, `ReadOnlySpan<T>`, and types
implementing certain collection interfaces.

```csharp

public void Log(params ReadOnlySpan<string> messages)
{
    foreach (var msg in messages)
        Console.WriteLine(msg);
}

// Callers: compiler may avoid heap allocation with span-based params
Log("hello", "world");

```text

> **net9.0+ only.** On net8.0, `params` only supports arrays.

---

## `Lock` Type (C# 13, net9.0+)

Use `System.Threading.Lock` instead of `object` for locking.

```csharp

private readonly Lock _lock = new();

public void DoWork()
{
    lock (_lock)
    {
        // thread-safe operation
    }
}

```text

`Lock` provides a `Scope`-based API for advanced scenarios and is more expressive than `lock (object)`.

> **net9.0+ only.** On net8.0, use `private readonly object _gate = new();` and `lock (_gate)`.

---

## Partial Properties (C# 13, net9.0+)

Partial properties enable source generators to define property signatures that users implement, or vice versa.

```csharp

// In generated file
public partial class ViewModel
{
    public partial string Name { get; set; }
}

// In user file
public partial class ViewModel
{
    private string _name = "";
    public partial string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

```text

> **net9.0+ only.** See [skill:dotnet-csharp-source-generators] for generator patterns.

---

## `nameof` for Unbound Generic Types (C# 14, net10.0+)

```csharp

string name = nameof(List<>);      // "List"
string name2 = nameof(Dictionary<,>); // "Dictionary"

```csharp

Useful in logging, diagnostics, and reflection scenarios.

> **net10.0+ only.**

---

## Polyfill Guidance for Multi-Targeting

When targeting multiple TFMs, newer language features may not compile on older targets. Use these approaches:

1. **PolySharp** -- Polyfills compiler-required types (`IsExternalInit`, `RequiredMemberAttribute`, etc.) so language
   features like `init`, `required`, and `record` work on older TFMs.
2. **Polyfill** -- Polyfills runtime APIs (e.g., `string.Contains(char)` for netstandard2.0).
3. **Conditional compilation** -- Use `#if` for features that cannot be polyfilled:

```csharp

#if NET10_0_OR_GREATER
    // Use field keyword
    public double Value { get => field; set => field = Math.Max(0, value); }
#else
    private double _value;
    public double Value { get => _value; set => _value = Math.Max(0, value); }
#endif

```text

See [skill:dotnet-multi-targeting] for comprehensive polyfill guidance.

---

## Knowledge Sources

Feature guidance in this skill is grounded in publicly available language design rationale from:

- **C# Language Design Notes (Mads Torgersen et al.)** -- Design decisions behind each C# version's features. Key
  rationale relevant to this skill: primary constructors (reducing boilerplate for DI-heavy services), collection
  expressions (unifying collection initialization syntax), `field` keyword (eliminating backing field ceremony), and
  extension blocks (grouping extensions by target type). Each feature balances expressiveness with safety -- e.g.,
  primary constructor parameters are intentionally mutable captures (not readonly) to keep the feature simple; use
  explicit readonly fields when immutability is needed. Source: https://github.com/dotnet/csharplang/tree/main/meetings
- **C# Language Proposals Repository** -- Detailed specifications and design rationale for accepted and proposed
  features. Source: https://github.com/dotnet/csharplang/tree/main/proposals

> **Note:** This skill applies publicly documented design rationale. It does not represent or speak for the named
> sources.



## Code Navigation (Serena MCP)

**Primary approach:** Use Serena symbol operations for efficient code navigation:

1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` for file organization
3. **Track references**: `serena_find_referencing_symbols` for impact analysis
4. **Precise edits**: `serena_replace_symbol_body` for clean modifications

**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine

**Example workflow:**
```text
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"

# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"
```
## References

- [C# Language Reference](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/)
- [What's new in C# 12](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12)
- [What's new in C# 13](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13)
- [What's new in C# 14](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14)
- [C# Language Design Notes](https://github.com/dotnet/csharplang/tree/main/meetings)
- [.NET Framework Design Guidelines](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/)

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Patterns and techniques for evaluating and improving AI agent outputs.

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

Comprehensive AI prompt engineering safety review and improvement prompt. Analyzes prompts for safety, bias, security vulnerabilities, and effectiveness while providing detailed improvement recommendations.

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

Use when user requests research requiring multiple sources, comprehensive analysis, or synthesis across topics - technical research, domain knowledge gathering, market analysis, or learning about complex subjects

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

AI-powered wiki generation for code repositories with commands, agents, and skills

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

Use when building .NET 10 or C# 14 applications; when using minimal APIs, modular monolith patterns, or feature folders; when implementing HTTP resilience, Options pattern, Channels, or validation; when seeing outdated patterns like old extension method syntax

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

Implements accessible .NET UI. SemanticProperties, ARIA, AutomationPeer, testing per platform.

The English summary is being prepared. Showing the original description.

rudironsoni/Synaxis2Updated Mar 17, 2026

See all skills by rudironsoni

Report a problem with this skill