Dotnet Csharp Modern Patterns logo

Dotnet Csharp Modern Patterns

Community
wshaddix
dotnet-csharp-modern-patterns

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

Overview

Publisherwshaddix
Repositorydotnet-skills
Skill namedotnet-csharp-modern-patterns
Stars
79
Forks
13
Bundled files
Instructions only
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by wshaddix on GitHub. Read the source before you install it.

Installation

Install the Dotnet Csharp Modern Patterns AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/wshaddix/dotnet-skills.git /tmp/dotnet-skills
mkdir -p .claude/skills
cp -r /tmp/dotnet-skills/skills/dotnet-csharp-modern-patterns .claude/skills/dotnet-csharp-modern-patterns
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Dotnet Csharp Modern Patterns in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Dotnet Csharp Modern Patterns on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Dotnet Csharp Modern Patterns is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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)

csharp
// 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+)

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

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

csharp
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)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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"

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

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

Frequently asked questions

What does the Dotnet Csharp Modern Patterns AI skill do?

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

Why use Dotnet Csharp Modern Patterns on TypingMind?

Because you install it once and use it with any model. Dotnet Csharp Modern Patterns is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Dotnet Csharp Modern Patterns in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/wshaddix/dotnet-skills/tree/master/skills/dotnet-csharp-modern-patterns. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Dotnet Csharp Modern Patterns?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Dotnet Csharp Modern Patterns?

As many as you like. As long as a model supports skills, you can use Dotnet Csharp Modern Patterns with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Dotnet Csharp Modern Patterns AI skill free?

It is published on GitHub by wshaddix. Check the repository for licensing terms. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇