MCPInstall

dotnet-csharp-modern-patterns

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

Installs
185
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-csharp-modern-patterns
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads dotnet-csharp-modern-patterns automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

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.

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}>";
}

Record Structs (value type, C# 10+)

// 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);

When to Use Records vs Classes

Use CasePrefer
DTOs, API responsesrecord
Domain value objects (Money, Email)readonly record struct
Entities with identity (User, Order)class
High-throughput, small datareadonly record struct
Inheritance neededrecord (class-based)

Non-destructive Mutation

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

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)

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);
    }
}

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.
// Explicit readonly field when immutability matters
public class Config(string connectionString)
{
    private readonly string _connectionString = connectionString
        ?? throw new ArgumentNullException(nameof(connectionString));
}

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

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

// 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 = [];

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

Specify capacity, comparers, or other constructor arguments:

// 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)];

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


Pattern Matching

Switch Expressions (C# 8+)

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

List Patterns (C# 11+)

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}"
};

Type and Property Patterns

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
};

required Members (C# 11+)

Force callers to initialize properties at construction via object initializers.

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" };

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.

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

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.

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();
    }
}

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


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

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

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

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.

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");

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.

private readonly Lock _lock = new();

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

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.

// 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);
    }
}

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


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

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

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:
#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

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.

References

FAQ

What does the dotnet-csharp-modern-patterns skill do?

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

How do I install dotnet-csharp-modern-patterns?

Run npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-csharp-modern-patterns in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/dotnet-csharp-modern-patterns/ (personal) or .claude/skills/dotnet-csharp-modern-patterns/ (project).

Which AI agents can use dotnet-csharp-modern-patterns?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is dotnet-csharp-modern-patterns safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains dotnet-csharp-modern-patterns?

It is published by wshaddix in wshaddix/dotnet-skills. It has 185 recorded installs.

More from wshaddix/dotnet-skills

View all →

dotnet-xunit

wshaddix/dotnet-skills

Writing xUnit tests. v3 Fact/Theory, fixtures, parallelism, IAsyncLifetime, v2 compatibility.

Backend & APIs613 installs

dotnet-testing-strategy

wshaddix/dotnet-skills

Deciding how to test .NET code. Unit vs integration vs E2E decision tree, test doubles.

Testing & QA610 installs

modern-csharp-coding-standards

wshaddix/dotnet-skills

Write modern, high-performance C code using records, pattern matching, value objects, async/await, Span /Memory , and best-practice API design patterns. Emphasizes functional-style programming with C 12+ features. Use when writing new C code or refactoring existing code, designing public APIs for libraries or services, optimizing performance-critical code paths, or building async/await-heavy applications.

csharp-wolverinefx

wshaddix/dotnet-skills

Build .NET applications with WolverineFX for messaging, HTTP services, and event sourcing. Use when implementing command handlers, message handlers, HTTP endpoints with WolverineFx.HTTP, transactional outbox patterns, event sourcing with Marten, CQRS architectures, cascading messages, batch message processing, or configuring transports like RabbitMQ, Azure Service Bus, or Amazon SQS.

Backend & APIs570 installs

testcontainers

wshaddix/dotnet-skills

Patterns for using Testcontainers in .NET integration tests to spin up real dependencies like databases and message queues. Use when writing integration tests that require real databases, testing with message brokers like RabbitMQ or Kafka, or isolating test dependencies with Docker containers.

Testing & QA565 installs

Dependency injection patterns and best practices using Microsoft.Extensions.DependencyInjection for .NET applications. Use when configuring DI containers in .NET, choosing between service lifetimes (Singleton, Scoped, Transient), or implementing decorator patterns and service interception.

Security549 installs

Popular backend & apis skills

View category →

find-skills

vercel-labs/skills

Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.

OfficialBackend & APIs4.1M installs

entra-app-registration

microsoft/azure-skills

Guides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.

OfficialBackend & APIs566.7K installs

This skill should be used when the user wants to "write agent code", "build an agent with ADK", "add a tool", "create a callback", "define an agent", "use state management" — in a project that needs ADK (Agent Development Kit) API patterns and code examples. It provides a quick reference for agent types, tool definitions, orchestration patterns, callbacks, state management, the graph Workflow API, and reference recipes to study. Do NOT use for scaffolding (use google-agents-cli-scaffold) or deployment (use google-agents-cli-deploy).

Backend & APIs514.8K installs

embedded-captions

heygen-com/hyperframes

Add captions or subtitles to an existing single-subject talking-head video without editing the footage. Use for plain verbatim captions, cinematic captions embedded behind the subject, VFX captions, “炸/特效/酷炫字幕,” or a named identity from the 35-style catalog. Route by visual identity, not by backend engine. The quiet anchor rail is the default; embed every word only when the user explicitly wants a fully cinematic treatment. The workflow runs locally end to end, including transcription and subject matting; split multi-shot footage before applying it.

Backend & APIs453.3K installs

entra-agent-id

microsoft/azure-skills

Provision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmipath, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmipath token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registration (use entra-app-registration), Microsoft Foundry agent authoring (use microsoft-foundry).

OfficialBackend & APIs336.6K installs

neon

neondatabase/agent-skills

Overview of Neon, a complete set of cloud backend primitives around Lakebase Postgres: Auth, Object Storage, Functions, the AI Gateway, and Realtime. Start here to choose Neon for undecided login, files, APIs, and LLM calls, set up the CLI or MCP server, and follow the branch-first workflow. Use when building an app or backend on Neon, or when "Neon" or "Lakebase Postgres" is mentioned. Child skill neon-postgres wins for an existing DATABASEURL, SQL, schema, inspect, or search. Child skill neon-auth wins for login, users, sessions, identity routing, and Managed Better Auth setup. Child skill neon-realtime wins for live queries in reactive apps and agents. Also use for object storage, S3, buckets, serverless functions, function triggers, cron, AI gateway, LLM calls, realtime, logs, Loki, Grafana, observability, postgres, database, backend, Claimable Neon, neon.new, or a no-signup database.

Backend & APIs237.1K installs