Skip to main content

csharp-best-practices

C# coding conventions, idiomatic patterns, performance, and async best practices for both .NET 8 (C# 12) and .NET 10 (C# 14). Use when writing, reviewing, refactoring, or designing C# code — including async code that uses Task, generic Task results, ValueTask, CancellationToken, Task.WhenAll/WhenAny, Task.Run, ConfigureAwait, async void, or fire-and-forget. Trigger on `.Result`, `.Wait()`, deadlocks, cancellation propagation, ASP.NET Core background work, UI responsiveness, exception flow, and performance-sensitive async API design.

Zur Installation springen

Quellinformationen

Repository
MCCTeam/UMPK
Letzte Quellaktivität
12. September 2026 um 15:34
Erkannte Sprache von SKILL.md
Englisch
Sterne
2
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
5 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
csharp-best-practices
description
C# coding conventions, idiomatic patterns, performance, and async best practices for both .NET 8 (C# 12) and .NET 10 (C# 14). Use when writing, reviewing, refactoring, or designing C# code — including async code that uses Task, generic Task results, ValueTask, CancellationToken, Task.WhenAll/WhenAny, Task.Run, ConfigureAwait, async void, or fire-and-forget. Trigger on `.Result`, `.Wait()`, deadlocks, cancellation propagation, ASP.NET Core background work, UI responsiveness, exception flow, and performance-sensitive async API design.
metadata
{"category":"technique","platform":".NET 8 (C# 12) and .NET 10 (C# 14)","triggers":["c#","csharp",".net",".net 8",".net 10","async","task","valuetask","cancellationtoken","configureawait",".result",".wait()","async void","fire-and-forget","task.run","whenall","whenany","asp.net core","deadlock"]}
# C# Best Practices — .NET 8 + .NET 10 Target this repository's **.NET 10 / C# 14** projects with nullable reference types enabled. ## Step 0 — Detect the target framework Before emitting code, inspect `Directory.Build.props` and the target project. The detected target framework decides which examples below apply: - `net8.0` → emit the **.NET 8 / C# 12** code block in every side-by-side pair; **never** use the C# 14-only syntax (`field`, `extension(...)`, `?.` assignment, partial constructors) or .NET 10-only APIs (`HybridCache`, `AddValidation`, EF Core named filters, first-party `Microsoft.AspNetCore.OpenApi`, Identity passkeys). - `net10.0` → prefer the **.NET 10 / C# 14** code block. - Multi-target (`<TargetFrameworks>net8.0;net10.0</TargetFrameworks>`) → emit the .NET 8 version, or wrap .NET 10-only code in `#if NET10_0_OR_GREATER`. - Unknown → ask the user. Sources: [MS C# Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions) · [.NET Runtime Style](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md) · [C# language versioning](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-versioning) · [C# 14 What's New](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14) · [C# 12 What's New](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12) ## Naming | Element | Style | Example | |---|---|---| | Type, method, property, const, enum member | PascalCase | `PacketHandler`, `MaxRetries`, `GameMode.Survival` | | Interface | `I` + PascalCase | `IChatBot` | | Private instance field | `_camelCase` | `_handler` | | Private static field | `s_camelCase` | `s_defaultTimeout` | | Thread-static field | `t_camelCase` | `t_cachedBuffer` | | Local, parameter | camelCase | `packetId` | | Type parameter | `T` + PascalCase | `TResult` | | Namespace | PascalCase | `SomeNamespace.SomeClasses` | | Async methods | Suffix `Async` | `ConnectAsync()`, `ReadPacketAsync()` | ```csharp // CORRECT: naming conventions private readonly Dictionary<int, Entity> _entities = new(); private static readonly TimeSpan s_reconnectDelay = TimeSpan.FromSeconds(5); public int PacketCount { get; private set; } public async Task<bool> ConnectAsync(CancellationToken ct) { } ``` ```csharp // WRONG: naming violations private Dictionary<int, Entity> entities = new(); // missing _ private static TimeSpan reconnectDelay; // missing s_ public int packet_count { get; set; } // snake_case public async Task<bool> Connect(CancellationToken ct) { } // missing Async suffix ``` ## C# 14 Features (.NET 10 only — with .NET 8 / C# 12 fallbacks) Every feature in this section requires `<TargetFramework>net10.0</TargetFramework>`. On `net8.0` use the fallback shown alongside. Authoritative reference: [C# 14 what's new](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14). ### Extension Members Declare extension methods, properties, and operators inside `extension(...)` blocks. ```csharp // .NET 10 / C# 14 — extension property + method public static class EntityExtensions { extension(Entity entity) { public bool IsAlive => entity.Health > 0; public void Heal(int amount) => entity.Health = Math.Min(entity.Health + amount, 20); } extension<T>(IEnumerable<T> items) { public bool IsEmpty => !items.GetEnumerator().MoveNext(); } } ``` ```csharp // .NET 8 / C# 12 — classic static extension class (only option) public static class EntityExtensions { public static bool IsAlive(this Entity entity) => entity.Health > 0; public static void Heal(this Entity entity, int amount) => entity.Health = Math.Min(entity.Health + amount, 20); public static bool IsEmpty<T>(this IEnumerable<T> items) => !items.GetEnumerator().MoveNext(); } // Extension properties do not exist in C# 12 — expose them as methods or compute inline. ``` ### `field` Keyword in Properties Access the auto-generated backing field without declaring it. ```csharp // .NET 10 / C# 14 — field keyword public string DisplayName => field ??= ComputeDisplayName(); public bool IsConnected { get; set { if (field == value) return; field = value; OnPropertyChanged(); } } ``` ```csharp // .NET 8 / C# 12 — manual backing field (only option) private string? _displayName; public string DisplayName => _displayName ??= ComputeDisplayName(); private bool _isConnected; public bool IsConnected { get => _isConnected; set { if (_isConnected == value) return; _isConnected = value; OnPropertyChanged(); } } ``` ### Null-Conditional Assignment ```csharp // .NET 10 / C# 14 — assign / compound-assign through ?. player?.Health = 20; connection?.OnDisconnect += HandleDisconnect; inventory?[slot] = newItem; ``` ```csharp // .NET 8 / C# 12 — manual null check if (player is not null) player.Health = 20; if (connection is not null) connection.OnDisconnect += HandleDisconnect; if (inventory is not null) inventory[slot] = newItem; ``` ### Simple Lambda Parameters with Modifiers ```csharp // .NET 10 / C# 14 — modifiers without explicit types TryParse<int> parse = (text, out result) => int.TryParse(text, out result); ProcessSpan((scoped span) => span.Length); ``` ```csharp // .NET 8 / C# 12 — full parameter types required when modifiers are present TryParse<int> parse = (string text, out int result) => int.TryParse(text, out result); ProcessSpan((scoped ReadOnlySpan<int> span) => span.Length); ``` ### First-Class Span Types ```csharp // .NET 10 / C# 14 — implicit T[] → ReadOnlySpan<T> int[] data = [1, 2, 3]; bool found = data.StartsWith(1); // ReadOnlySpan<int> extension auto-resolves ReadOnlySpan<byte> span = stackalloc byte[4]; ``` ```csharp // .NET 8 / C# 12 — call .AsSpan() explicitly at the boundary int[] data = [1, 2, 3]; bool found = data.AsSpan().StartsWith(stackalloc int[] { 1 }); // explicit conversion ReadOnlySpan<byte> span = stackalloc byte[4]; // stackalloc → ReadOnlySpan already works ``` ### Unbound Generics in `nameof` ```csharp // .NET 10 / C# 14 string name = nameof(Dictionary<,>); // "Dictionary" string prop = nameof(List<>.Count); // "Count" ``` ```csharp // .NET 8 / C# 12 — pick any closed type argument string name = nameof(Dictionary<object, object>); // "Dictionary" string prop = nameof(List<int>.Count); // "Count" ``` ### Partial Events and Constructors ```csharp // .NET 10 / C# 14 — partial constructor for source-gen interop partial class ServerConnection { partial ServerConnection(string host, int port); } partial class ServerConnection { partial ServerConnection(string host, int port) { /* generated body */ } } ``` ```csharp // .NET 8 / C# 12 — partial constructors do not exist. // Either declare a regular constructor and call a private generated helper, // or put the constructor body in a single file: partial class ServerConnection { public ServerConnection(string host, int port) => InitGenerated(host, port); private partial void InitGenerated(string host, int port); // partial methods are C# 9+ } partial class ServerConnection { private partial void InitGenerated(string host, int port) { /* generated body */ } } ``` ### `#:` Ignored Directives / file-based programs C# 14 / .NET 10 SDK only — no .NET 8 equivalent. `dotnet run app.cs` requires the .NET 10 SDK. ```csharp // .NET 10 only — file-based program with inline package reference #!/usr/bin/dotnet run #:package System.CommandLine@2.0.0-* Console.WriteLine("Hello"); ``` ```csharp // .NET 8 — create a full project (dotnet new console -f net8.0) and reference // System.CommandLine in the .csproj. There is no inline-package syntax. ``` ## C# 13 Features — require .NET 9+ (NOT available on .NET 8) This repository targets .NET 10. Use the .NET 8 fallback only when reviewing external multi-targeted code. ### `Lock` Object ```csharp // .NET 10 / C# 13+ — dedicated System.Threading.Lock type private readonly Lock _gate = new(); public void Enqueue(ChatMessage msg) { lock (_gate) _queue.Add(msg); } ``` ```csharp // .NET 8 / C# 12 — lock on a plain object reference (the only option) private readonly object _gate = new(); public void Enqueue(ChatMessage msg) { lock (_gate) _queue.Add(msg); } ``` ### `params` Collections (`params ReadOnlySpan<T>`) The runtime overloads accepting `params ReadOnlySpan<T>` ship in the .NET 9 BCL. On .NET 8 use `params T[]`. ```csharp // .NET 10 / C# 13+ — params span avoids the array allocation public void Log(params ReadOnlySpan<string> messages) { foreach (var msg in messages) Console.WriteLine(msg); } ``` ```csharp // .NET 8 / C# 12 — params array (one heap allocation per call) public void Log(params string[] messages) { foreach (var msg in messages) Console.WriteLine(msg); } ``` ### Partial Properties ```csharp // .NET 10 / C# 13+ — partial property for source generators partial class Config { public partial string Host { get; set; } } partial class Config { public partial string Host { get => _host; set => _host = value; } private string _host = ""; } ``` ```csharp // .NET 8 / C# 12 — partial properties do not exist; declare a normal property // and let the source generator emit the backing field or a helper method. partial class Config { public string Host { get; set; } = ""; } ``` ## C# 12 Features (.NET 8+ — available on both targets) ### Primary Constructors Use for simple parameter capture. Parameters are `camelCase`, mutable — assign to `readonly` fields when immutability matters. ```csharp // CORRECT: primary constructor captures dependencies public class ChatLogger(string logFilePath, bool appendMode) : ChatBot { private readonly StreamWriter _writer = new(logFilePath, appendMode); public override void GetText(string text) => _writer.WriteLine(text); } ``` ```csharp // WRONG: verbose constructor boilerplate for simple capture public class ChatLogger : ChatBot { private readonly StreamWriter _writer; public ChatLogger(string logFilePath, bool appendMode) { _writer = new StreamWriter(logFilePath, appendMode); } public override void GetText(string text) => _writer.WriteLine(text); } ``` ### Collection Expressions Use `[...]` and `..` spread for arrays, lists, spans. ```csharp // CORRECT: collection expressions (C# 12) int[] ids = [1, 2, 3]; List<string> names = ["Steve", "Alex"]; ReadOnlySpan<byte> header = [0xFE, 0x01]; // no heap alloc int[] combined = [..firstArray, ..secondArray, 42]; IReadOnlyList<string> empty = []; ``` ```csharp // WRONG: verbose initialization int[] ids = new int[] { 1, 2, 3 }; var names = new List<string> { "Steve", "Alex" }; ReadOnlySpan<byte> header = new byte[] { 0xFE, 0x01 }; // allocates var combined = firstArray.Concat(secondArray).Append(42).ToArray(); ``` ### Type Aliases ```csharp // CORRECT: alias complex types for readability using Coordinate = (int X, int Y, int Z); using PacketMap = System.Collections.Generic.Dictionary<int, System.Action<byte[]>>; ``` ### Default Lambda Parameters ```csharp // CORRECT: C# 12 var greet = (string name, string prefix = "Player") => $"{prefix} {name}"; ``` ## Modern Syntax (C# 10–14) ### File-Scoped Namespaces ```csharp // CORRECT: file-scoped namespace — one per file, less nesting namespace SomeNamespace.SomeClasses; public class SomeClass : SomeInterface { } ``` ```csharp // WRONG: block-scoped namespace adds unnecessary nesting namespace SomeNamespace.SomeClasses { public class SomeClass : SomeInterface { } } ``` ### Target-Typed `new` Use when the type is obvious from the left-hand side.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen