CommunityCodierung & Entwicklunggithub.com

Moderne C#-Codierungsstandards

Schreibe modernen, leistungsstarken C#-Code unter Verwendung von Records, Pattern Matching, Value Objects, async/await, Span<T>/Memory<T> und bewährten API-Entwurfsmustern. Setzt auf funktionale Programmierung mit C# 12+-Features.

Was ist Moderne C#-Codierungsstandards?

Diese Fähigkeit (Skill) definiert einen umfassenden Satz von Codierungsstandards für das Schreiben von modernem, leistungsstarkem und wartbarem C#-Code, der auf C# 12+ ausgerichtet ist. Sie ist für Entwickler gedacht, die neue C#-Projekte erstellen, bestehenden Code umgestalten, öffentliche APIs entwerfen oder leistungskritische Systeme optimieren. Der Skill betont die Verwendung von Sprachfeatures wie `record`-Typen für unveränderliche Daten, `readonly record struct` für Value Objects, umfangreiches Pattern Matching, nullable Referenztypen und Best Practices für async/await mit ordnungsgemäßem `CancellationToken`-Support. Für die Leistung werden Null-Allocation-Muster mit `Span<T>`/`Memory<T>` vorgeschrieben, und es wird die Verwendung von `Result<T, TError>` für erwartete Fehler anstelle von Ausnahmen empfohlen. Er rät von reflektionsbasierten Bibliotheken wie AutoMapper ab und befürwortet explizite Mapping-Erweiterungsmethoden. Referenzierte Begleitdateien (`value-objects-and-patterns.md`, `performance-and-api-design.md`, `composition-and-error-handling.md`, `anti-patterns-and-reflection.md`) enthalten vollständige Codebeispiele, die diese Prinzipien demonstrieren. Der Skill ist plattformunabhängig und kann in beliebigen Entwicklungsumgebungen oder KI-Agenten (z. B. Claude Code, Cursor, Codex) verwendet werden, die C#-Entwicklung unterstützen.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/csharp-coding-standards

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was macht Moderne C#-Codierungsstandards?

Wann dieser Skill verwendet werden sollte

Verwende diesen Skill, wenn:

  • Du neuen C#-Code schreibst oder bestehenden Code umgestaltest
  • Du öffentliche APIs für Bibliotheken oder Dienste entwirfst
  • Du leistungskritische Codepfade optimierst
  • Du Domänenmodelle mit starker Typisierung implementierst
  • Du stark async/await-lastige Anwendungen entwickelst
  • Du mit Binärdaten, Puffern oder Szenarien mit hohem Durchsatz arbeitest

Referenzdateien

Kernprinzipien

  1. Unveränderlichkeit als Standard – Verwende record-Typen und init-only-Eigenschaften
  2. Typsicherheit – Nutze nullable Referenztypen und Value Objects
  3. Modernes Pattern Matching – Verwende switch-Ausdrücke und Patterns umfassend
  4. Async überall – Bevorzuge asynchrone APIs mit ordnungsgemäßer Unterstützung für Abbruch
  5. Null-Allocation-Muster – Verwende Span<T> und Memory<T> für leistungskritischen Code
  6. API-Entwurf – Akzeptiere Abstraktionen, gib geeignet spezifische Typen zurück
  7. Komposition vor Vererbung – Vermeide abstrakte Basisklassen, bevorzuge Komposition
  8. Value Objects als Structs – Verwende readonly record struct für Value Objects

Sprachmuster

Records für unveränderliche Daten (C# 9+)

Verwende record-Typen für DTOs, Nachrichten, Ereignisse und Domänenentitäten.

// Einfaches unveränderliches DTO
public record CustomerDto(string Id, string Name, string Email);

// Record mit Validierung im Konstruktor
public record EmailAddress
{
    public string Value { get; init; }

    public EmailAddress(string value)
    {
        if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
            throw new ArgumentException("Ungültige E-Mail-Adresse", nameof(value));

        Value = value;
    }
}

// Records mit Sammlungen – verwende IReadOnlyList
public record ShoppingCart(
    string CartId,
    string CustomerId,
    IReadOnlyList<CartItem> Items
)
{
    public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}

Wann record class vs. record struct verwenden:

  • record class (Standard): Referenztypen, verwende für Entitäten, Aggregate, DTOs mit mehreren Eigenschaften
  • record struct: Werttypen, verwende für Value Objects (siehe nächster Abschnitt)

Value Objects als readonly record struct

Value Objects sollten immer readonly record struct sein, für Leistung und Wertesemantik. Verwende explizite Konvertierungen, niemals implizite Operatoren.

public readonly record struct OrderId(string Value)
{
    public OrderId(string value) : this(
        !string.IsNullOrWhiteSpace(value)
            ? value
            : throw new ArgumentException("OrderId darf nicht leer sein", nameof(value)))
    { }
    public override string ToString() => Value;
}

public readonly record struct Money(decimal Amount, string Currency);
public readonly record struct CustomerId(Guid Value)
{
    public static CustomerId New() => new(Guid.NewGuid());
}

Siehe value-objects-and-patterns.md für vollständige Beispiele, darunter Multi-Value-Objects, Factory-Muster und die Regel der fehlenden impliziten Konvertierung.

Pattern Matching (C# 8-12)

Verwende switch-Ausdrücke, Eigenschaftsmuster, relationale Muster und Listenmuster für saubereren Code.

public decimal CalculateDiscount(Order order) => order switch
{
    { Total: > 1000m } => order.Total * 0.15m,
    { Total: > 500m } => order.Total * 0.10m,
    { Total: > 100m } => order.Total * 0.05m,
    _ => 0m
};

Siehe value-objects-and-patterns.md für vollständige Pattern-Matching-Beispiele.


Nullable Referenztypen (C# 8+)

Aktiviere nullable Referenztypen in deinem Projekt und behandle Nullwerte explizit.

// In .csproj
<PropertyGroup>
    <Nullable>enable</Nullable>
</PropertyGroup>

// Explizite Nullable-Behandlung
public string? FindUserName(string userId)
{
    var user = _repository.Find(userId);
    return user?.Name;
}

// Pattern Matching mit Null-Prüfungen
public decimal GetDiscount(Customer? customer) => customer switch
{
    null => 0m,
    { IsVip: true } => 0.20m,
    { OrderCount: > 10 } => 0.10m,
    _ => 0.05m
};

// Guard-Klauseln mit ArgumentNullException.ThrowIfNull (C# 11+)
public void ProcessOrder(Order? order)
{
    ArgumentNullException.ThrowIfNull(order);
    // order ist jetzt in diesem Gültigkeitsbereich nicht mehr nullable
    Console.WriteLine(order.Id);
}

Komposition vor Vererbung

Vermeide abstrakte Basisklassen. Verwende Schnittstellen + Komposition. Verwende statische Helfer für gemeinsame Logik. Verwende Records mit Factory-Methoden für Varianten.

Siehe composition-and-error-handling.md für vollständige Beispiele.


Leistungsmuster

Async/Await Best Practices

// Async bis zum Ende – akzeptiere immer CancellationToken
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
    var order = await _repository.GetAsync(orderId, cancellationToken);
    return order;
}

// ValueTask für häufig aufgerufene, oft synchrone Methoden
public ValueTask<Order?> GetCachedOrderAsync(string orderId, CancellationToken cancellationToken)
{
    if (_cache.TryGetValue(orderId, out var order))
        return ValueTask.FromResult<Order?>(order);
    return GetFromDatabaseAsync(orderId, cancellationToken);
}

// IAsyncEnumerable für Streaming
public async IAsyncEnumerable<Order> StreamOrdersAsync(
    string customerId,
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    await foreach (var order in _repository.StreamAllAsync(cancellationToken))
    {
        if (order.CustomerId == customerId)
            yield return order;
    }
}

Wichtige Regeln:

  • Akzeptiere immer CancellationToken mit = default
  • Verwende ConfigureAwait(false) in Bibliothekscode
  • Blockiere niemals asynchronen Code (kein .Result oder .Wait())
  • Verwende verknüpfte CancellationTokenSource für Zeitüberschreitungen

Span und Memory

Verwende Span<T> für synchrone Null-Allocation-Operationen, Memory<T> für asynchrone Operationen und ArrayPool<T> für große temporäre Puffer.

Siehe performance-and-api-design.md für vollständige Span/Memory-Beispiele und den API-Entwurfsabschnitt.


Fehlerbehandlung: Result-Typ

Verwende für erwartete Fehler Result<T, TError> anstelle von Ausnahmen. Verwende Ausnahmen nur für unerwartete/Systemfehler.

Siehe composition-and-error-handling.md für die vollständige Implementierung des Result-Typs und Nutzungsbeispiele.


Vermeide reflektionsbasierte Metaprogrammierung

Verboten: AutoMapper, Mapster, ExpressMapper. Verwende stattdessen explizite Mapping-Erweiterungsmethoden. Verwende UnsafeAccessorAttribute (.NET 8+), wenn du wirklich Zugriff auf private Member benötigst.

Siehe anti-patterns-and-reflection.md für vollständige Anleitungen.


Code-Organisation

// Datei: Domain/Orders/Order.cs

namespace MyApp.Domain.Orders;

// 1. Primärer Domänentyp
public record Order(
    OrderId Id,
    CustomerId CustomerId,
    Money Total,
    OrderStatus Status,
    IReadOnlyList<OrderItem> Items
)
{
    public bool IsCompleted => Status is OrderStatus.Completed;

    public Result<Order, OrderError> AddItem(OrderItem item)
    {
        if (Status is not OrderStatus.Draft)
            return Result<Order, OrderError>.Failure(
                new OrderError("ORDER_NOT_DRAFT", "Elemente können nur zu Entwurfsbestellungen hinzugefügt werden"));

        var newItems = Items.Append(item).ToList();
        var newTotal = new Money(
            Items.Sum(i => i.Total.Amount) + item.Total.Amount,
            Total.Currency);

        return Result<Order, OrderError>.Success(
            this with { Items = newItems, Total = newTotal });
    }
}

// 2. Enums für Zustände
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }

// 3. Verwandte Typen
public record OrderItem(ProductId ProductId, Quantity Quantity, Money UnitPrice)
{
    public Money Total => new(UnitPrice.Amount * Quantity.Value, UnitPrice.Currency);
}

// 4. Value Objects
public readonly record struct OrderId(Guid Value)
{
    public static OrderId New() => new(Guid.NewGuid());
}

// 5. Fehler
public readonly record struct OrderError(string Code, string Message);

Zusammenfassung der Best Practices

TUN

  • Verwende record für DTOs, Nachrichten und Domänenentitäten
  • Verwende readonly record struct für Value Objects
  • Nutze Pattern Matching mit switch-Ausdrücken
  • Aktiviere und respektiere nullable Referenztypen
  • Verwende async/await für alle I/O-Operationen
  • Akzeptiere CancellationToken in allen asynchronen Methoden
  • Verwende Span<T> und Memory<T> für leistungskritische Szenarien
  • Akzeptiere Abstraktionen (IEnumerable<T>, IReadOnlyList<T>)
  • Verwende Result<T, TError> für erwartete Fehler
  • Puffere mit ArrayPool<T> für große Allokationen
  • Bevorzuge Komposition vor Vererbung

NICHT TUN

  • Verwende keine veränderlichen Klassen, wenn Records funktionieren
  • Verwende keine Klassen für Value Objects (verwende readonly record struct)
  • Erstelle keine tiefen Vererbungshierarchien
  • Ignoriere keine Warnungen zu nullable Referenztypen
  • Blockiere nicht auf asynchronem Code (.Result, .Wait())
  • Verwende nicht byte[], wenn Span<byte> ausreicht
  • Vergiss keine CancellationToken-Parameter
  • Gib keine veränderlichen Sammlungen von APIs zurück
  • Wirf keine Ausnahmen für erwartete Geschäftsfehler
  • Alloziere nicht wiederholt große Arrays (verwende ArrayPool)

Siehe anti-patterns-and-reflection.md für detaillierte Anti-Pattern-Beispiele.


Zusätzliche Ressourcen

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

Verwandte Skills