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.

インストールへ移動

ソース情報

リポジトリ
MCCTeam/UMPK
ソースの最終更新活動
2026年9月12日 15:34
検出された SKILL.md の言語
英語
スター
2
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

ファイルエクスプローラー
5 ファイル

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
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.
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る