dotnet-csharp-modern-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesedotnet-csharp-modern-patterns
.NET C# 现代模式
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.
适配项目目标框架的现代C#语言特性指南。请先运行[skill:dotnet-version-detection]来确定TFM和C#版本。
交叉参考:命名/风格规范请见[skill:dotnet-csharp-coding-standards],异步特定模式请见[skill:dotnet-csharp-async-patterns]。
Quick Reference: TFM to C# Version
快速参考:TFM 对应 C# 版本
| TFM | C# | Key Language Features |
|---|---|---|
| net8.0 | 12 | Primary constructors, collection expressions, alias any type |
| net9.0 | 13 | |
| net10.0 | 14 | |
| net11.0 | 15 (preview) | Collection expression |
| TFM | C#版本 | 核心语言特性 |
|---|---|---|
| net8.0 | 12 | 主构造函数、集合表达式、任意类型别名 |
| net9.0 | 13 | |
| net10.0 | 14 | |
| net11.0 | 15(预览版) | 集合表达式 |
Records
Records
Use records for immutable data transfer objects, value semantics, and domain modeling where equality is based on values rather than identity.
将records用于不可变数据传输对象、值语义,以及基于值而非标识判断相等性的领域建模。
Record Classes (reference type)
Record Classes(引用类型)
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}>";
}csharp
// 位置式record:简洁、不可变、值相等性
public record OrderSummary(int OrderId, decimal Total, DateOnly OrderDate);
// 包含额外成员的record
public record Customer(string Name, string Email)
{
public string DisplayName => $"{Name} <{Email}>";
}Record Structs (value type, C# 10+)
Record Structs(值类型,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);csharp
// 位置式record struct:具有值语义的值类型
public readonly record struct Point(double X, double Y);
// 可变record struct(少见——优先选择readonly)
public record struct MutablePoint(double X, double Y);When to Use Records vs Classes
何时使用Records vs Classes
| Use Case | Prefer |
|---|---|
| DTOs, API responses | |
| Domain value objects (Money, Email) | |
| Entities with identity (User, Order) | |
| High-throughput, small data | |
| Inheritance needed | |
| 使用场景 | 优先选择 |
|---|---|
| DTO、API响应 | |
| 领域值对象(Money、Email) | |
| 带标识的实体(User、Order) | |
| 高吞吐量、小型数据 | |
| 需要继承 | |
Non-destructive Mutation
非破坏性变更
csharp
var updated = order with { Total = order.Total + tax };csharp
var updated = order with { Total = order.Total + tax };Primary Constructors (C# 12+, net8.0+)
主构造函数(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);
}
}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 fields. If immutability matters, assign to a
readonlyfield in the body.readonly - 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));
}- 主构造函数参数是可变的捕获变量,而非字段。如果需要不可变性,请在主体中赋值给
readonly字段。readonly - 当需要在构造时验证参数时,请勿使用主构造函数——改用带有守卫子句的传统构造函数。
- 对于records,位置参数会自动成为公共属性;对于类/结构体,它们仍为私有捕获变量。
csharp
// 当需要不可变性时,显式声明readonly字段
public class Config(string connectionString)
{
private readonly string _connectionString = connectionString
?? throw new ArgumentNullException(nameof(connectionString));
}Collection Expressions (C# 12+, net8.0+)
集合表达式(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 = [];使用创建集合的统一语法。
[...]csharp
// 数组
int[] numbers = [1, 2, 3];
// 列表
List<string> names = ["Alice", "Bob"];
// Span
ReadOnlySpan<byte> bytes = [0x00, 0xFF];
// 展开运算符
int[] combined = [..first, ..second, 99];
// 空集合
List<int> empty = [];Collection Expression with Arguments (C# 15 preview, net11.0+)
带参数的集合表达式(C# 15预览版,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. Do not use on earlier TFMs.<LangVersion>preview</LangVersion>
指定容量、比较器或其他构造函数参数:
csharp
// 容量提示
List<int> nums = [with(capacity: 1000), ..Generate()];
// 自定义比较器
HashSet<string> set = [with(comparer: StringComparer.OrdinalIgnoreCase), "Alice", "bob"];
// 带比较器的字典
Dictionary<string, int> map = [with(comparer: StringComparer.OrdinalIgnoreCase),
new("key1", 1), new("key2", 2)];仅net11.0+支持。需要设置。请勿在更早的TFM中使用。<LangVersion>preview</LangVersion>
Pattern Matching
模式匹配
Switch Expressions (C# 8+)
Switch表达式(C# 8+)
csharp
string GetDiscount(Customer customer) => customer switch
{
{ Tier: "Gold", YearsActive: > 5 } => "30%",
{ Tier: "Gold" } => "20%",
{ Tier: "Silver" } => "10%",
_ => "0%"
};csharp
string GetDiscount(Customer customer) => customer switch
{
{ Tier: "Gold", YearsActive: > 5 } => "30%",
{ Tier: "Gold" } => "20%",
{ Tier: "Silver" } => "10%",
_ => "0%"
};List Patterns (C# 11+)
列表模式(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}"
};csharp
bool IsValid(int[] data) => data is [> 0, .., > 0]; // 第一个和最后一个元素为正
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
};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+)
requiredrequired
成员(C# 11+)
requiredForce 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 in .NET 8+).
required强制调用者通过对象初始化器在构造时初始化属性。
csharp
public class UserDto
{
public required string Name { get; init; }
public required string Email { get; init; }
public string? Phone { get; init; }
}
// 编译器会强制要求初始化Name和Email
var user = new UserDto { Name = "Alice", Email = "alice@example.com" };适用于需要反序列化的DTO(.NET 8+中System.Text.Json支持)。
requiredfield
Keyword (C# 14, net10.0+)
fieldfield
关键字(C# 14,net10.0+)
fieldAccess 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.
在属性访问器中直接访问编译器生成的后备字段。
csharp
public class TemperatureSensor
{
public double Reading
{
get => field;
set => field = value >= -273.15
? value
: throw new ArgumentOutOfRangeException(nameof(value));
}
}替代了手动声明私有字段加自定义逻辑属性的模式。当需要在setter中进行验证或转换且无需单独后备字段时使用。
仅net10.0+支持。在更早的TFM中,请使用传统私有字段。
Extension Blocks (C# 14, net10.0+)
扩展块(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 traditionalextension methods.static
将某个类型的扩展成员分组到单个块中。
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+支持。在更早的TFM中,请使用传统扩展方法。static
Alias Any Type (using
, C# 12+, net8.0+)
using任意类型别名(using
,C# 12+,net8.0+)
usingcsharp
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.
csharp
using Point = (double X, double Y);
using UserId = System.Guid;
Point origin = (0, 0);
UserId id = UserId.NewGuid();无需创建完整类型即可用于元组别名和领域类型别名,非常实用。
params
Collections (C# 13, net9.0+)
paramsparams
集合(C# 13,net9.0+)
paramsparamsSpan<T>ReadOnlySpan<T>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,only supports arrays.params
paramsSpan<T>ReadOnlySpan<T>csharp
public void Log(params ReadOnlySpan<string> messages)
{
foreach (var msg in messages)
Console.WriteLine(msg);
}
// 调用方式:编译器可通过基于span的params避免堆分配
Log("hello", "world");仅net9.0+支持。在net8.0中,仅支持数组。params
Lock
Type (C# 13, net9.0+)
LockLock
类型(C# 13,net9.0+)
LockUse instead of for locking.
System.Threading.Lockobjectcsharp
private readonly Lock _lock = new();
public void DoWork()
{
lock (_lock)
{
// thread-safe operation
}
}LockScopelock (object)net9.0+ only. On net8.0, useandprivate readonly object _gate = new();.lock (_gate)
使用替代进行锁定。
System.Threading.Lockobjectcsharp
private readonly Lock _lock = new();
public void DoWork()
{
lock (_lock)
{
// 线程安全操作
}
}LockScopelock (object)仅net9.0+支持。在net8.0中,请使用和private readonly object _gate = new();。lock (_gate)
Partial Properties (C# 13, net9.0+)
分部属性(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.
分部属性允许源代码生成器定义由用户实现的属性签名,反之亦然。
csharp
// 生成文件中
public partial class ViewModel
{
public partial string Name { get; set; }
}
// 用户文件中
public partial class ViewModel
{
private string _name = "";
public partial string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
}仅net9.0+支持。生成器模式请见[skill:dotnet-csharp-source-generators]。
nameof
for Unbound Generic Types (C# 14, net10.0+)
nameof未绑定泛型的nameof
(C# 14,net10.0+)
nameofcsharp
string name = nameof(List<>); // "List"
string name2 = nameof(Dictionary<,>); // "Dictionary"Useful in logging, diagnostics, and reflection scenarios.
net10.0+ only.
csharp
string name = nameof(List<>); // "List"
string name2 = nameof(Dictionary<,>); // "Dictionary"在日志、诊断和反射场景中非常实用。
仅net10.0+支持。
Polyfill Guidance for Multi-Targeting
多目标框架的Polyfill指南
When targeting multiple TFMs, newer language features may not compile on older targets. Use these approaches:
- PolySharp -- Polyfills compiler-required types (,
IsExternalInit, etc.) so language features likeRequiredMemberAttribute,init, andrequiredwork on older TFMs.record - Polyfill -- Polyfills runtime APIs (e.g., for netstandard2.0).
string.Contains(char) - Conditional compilation -- Use for features that cannot be polyfilled:
#if
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); }
#endifSee [skill:dotnet-multi-targeting] for comprehensive polyfill guidance.
当面向多个TFM时,较新的语言特性可能无法在旧目标框架上编译。请使用以下方法:
- PolySharp —— 填充编译器所需类型(、
IsExternalInit等),使RequiredMemberAttribute、init和required等语言特性可在旧TFM上运行。record - Polyfill —— 填充运行时API(例如netstandard2.0的)。
string.Contains(char) - 条件编译 —— 对无法填充的特性使用:
#if
csharp
#if NET10_0_OR_GREATER
// 使用field关键字
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完整的polyfill指南请见[skill:dotnet-multi-targeting]。
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), 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
field - 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.
本技能中的特性指南基于公开可用的语言设计原理,来源包括:
- C#语言设计笔记(Mads Torgersen等人) —— 每个C#版本特性背后的设计决策。与本技能相关的核心原理:主构造函数(减少依赖注入密集型服务的样板代码)、集合表达式(统一集合初始化语法)、关键字(消除后备字段的繁琐代码)、扩展块(按目标类型分组扩展)。每个特性在表现力与安全性之间取得平衡——例如,主构造函数参数被设计为可变捕获变量(而非readonly)以保持特性简洁;当需要不可变性时,请使用显式readonly字段。来源:https://github.com/dotnet/csharplang/tree/main/meetings
field - C#语言提案仓库 —— 已接受和提案特性的详细规范与设计原理。来源:https://github.com/dotnet/csharplang/tree/main/proposals
注意:本技能应用公开文档中的设计原理,不代表或代言上述来源。