关于 dotnet-csharp-modern-patterns
为 C# 开发者提供适配项目目标框架的现代语言特性指南。按 TFM 和 C# 版本梳理语法用法与限制,并给出多目标框架项目的兼容建议。
主要功能
- 01按 TFM 对照 C# 版本
- 02记录与主构造函数用法
- 03集合表达式与模式匹配示例
- 04说明各版本特性限制
- 05多目标框架兼容建议
适用场景
- 01确认目标框架支持的 C# 语法
- 02选择合适的数据类型与语言特性
- 03为多目标框架项目处理兼容性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-csharp-modern-patterns安装后在 Claude Code 中输入/dotnet-csharp-modern-patterns确认已加载。
详细介绍
dotnet-csharp-modern-patterns
根据项目的目标框架调整的现代 C# 语言功能指南。始终先运行 [skill:dotnet-version-detection],以确定 TFM 和 C# 版本。
交叉引用:[skill:dotnet-csharp-coding-standards],了解命名和风格约定;[skill:dotnet-csharp-async-patterns],了解异步专用模式。
快速参考:TFM 对应的 C# 版本
| TFM | C# | 主要语言功能 |
|---|---|---|
| net8.0 | 12 | 主构造函数、集合表达式、任意类型别名 |
| net9.0 | 13 | params 集合、Lock 类型、分部属性 |
| net10.0 | 14 | field 关键字、扩展块、未绑定泛型的 nameof |
| net11.0 | 15(预览版) | 带 with() 参数的集合表达式 |
记录
在需要基于值而非标识进行相等性比较的场景中,例如不可变数据传输对象、值语义和领域建模,请使用记录。
记录类(引用类型)
// 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}>";
}
记录结构体(值类型,C# 10+)
// 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);
何时使用记录而非类
| 用例 | 优先使用 |
|---|---|
| DTO、API 响应 | record |
| 领域值对象(Money、Email) | readonly record struct |
| 具有标识的实体(User、Order) | class |
| 高吞吐量、小型数据 | readonly record struct |
| 需要继承 | record(基于类) |
非破坏性变更
var updated = order with { Total = order.Total + tax };
主构造函数(C# 12+,net8.0+)
在类或结构体主体中直接捕获构造函数参数。参数在整个类型中均可用,但不是字段或属性——它们是被捕获的状态。
用于服务(DI 注入)
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);
}
}
注意事项
- 主构造函数参数是可变的捕获变量,而不是
readonly字段。如果需要不可变性,请在主体中将其赋给readonly字段。 - 如果需要在构造时验证参数,请勿使用主构造函数——应改用带有保护子句的传统构造函数。
- 对于记录,位置参数会自动成为公共属性。对于类和结构体,它们仍是私有捕获变量。
// Explicit readonly field when immutability matters
public class Config(string connectionString)
{
private readonly string _connectionString = connectionString
?? throw new ArgumentNullException(nameof(connectionString));
}
集合表达式(C# 12+,net8.0+)
使用统一的 [...] 语法创建集合。
// 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 = [];
带参数的集合表达式(C# 15 预览版,net11.0+)
指定容量、比较器或其他构造函数参数:
// 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+。 需要
<LangVersion>preview</LangVersion>。请勿用于更早的 TFM。
模式匹配
Switch 表达式(C# 8+)
string GetDiscount(Customer customer) => customer switch
{
{ Tier: "Gold", YearsActive: > 5 } => "30%",
{ Tier: "Gold" } => "20%",
{ Tier: "Silver" } => "10%",
_ => "0%"
};
列表模式(C# 11+)
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}"
};
类型和属性模式
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 成员(C# 11+)
强制调用方在构造时通过对象初始化器初始化属性。
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 = "[email protected]" };
适用于需要反序列化的 DTO(System.Text.Json 在 .NET 8+ 中会遵循 required)。
field 关键字(C# 14,net10.0+)
在属性访问器中直接访问编译器生成的后备字段。
public class TemperatureSensor
{
public double Reading
{
get => field;
set => field = value >= -273.15
? value
: throw new ArgumentOutOfRangeException(nameof(value));
}
}
它取代了声明私有字段并搭配自定义逻辑属性的手动模式。如果需要在 setter 中执行验证或转换,又不想单独使用后备字段,请使用此功能。
仅限 net10.0+。 对于更早的 TFM,请使用传统的私有字段。
扩展块(C# 14,net10.0+)
在单个块中为某个类型组合扩展成员。
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扩展方法。
任意类型别名(using,C# 12+,net8.0+)
using Point = (double X, double Y);
using UserId = System.Guid;
Point origin = (0, 0);
UserId id = UserId.NewGuid();
适用于元组别名和领域类型别名,无需创建完整类型。
params 集合(C# 13,net9.0+)
现在,params 除数组外还支持其他集合类型,包括 Span<T>、ReadOnlySpan<T> 以及实现特定集合接口的类型。
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+。 在 net8.0 上,
params仅支持数组。
Lock 类型(C# 13,net9.0+)
使用 System.Threading.Lock 代替 object 进行锁定。
private readonly Lock _lock = new();
public void DoWork()
{
lock (_lock)
{
// thread-safe operation
}
}
Lock 为高级场景提供基于 Scope 的 API,表达能力优于 lock (object)。
仅适用于 net9.0+。 在 net8.0 上,使用
private readonly object _gate = new();和lock (_gate)。
部分属性(C# 13,net9.0+)
部分属性允许源生成器定义属性签名,再由用户实现,反之亦然。
// 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+。 有关生成器模式,请参阅 [skill:dotnet-csharp-source-generators]。
对未绑定泛型类型使用 nameof(C# 14,net10.0+)
string name = nameof(List<>); // "List"
string name2 = nameof(Dictionary<,>); // "Dictionary"
适用于日志记录、诊断和反射场景。
仅适用于 net10.0+。
多目标框架的 Polyfill 指引
面向多个 TFM 时,较新的语言功能可能无法在较旧的目标框架上编译。可采用以下方法:
- PolySharp —— 为编译器要求的类型(
IsExternalInit、RequiredMemberAttribute等)提供 Polyfill,使init、required和record等语言功能可在较旧的 TFM 上使用。 - Polyfill —— 为运行时 API 提供 Polyfill(例如,为 netstandard2.0 提供
string.Contains(char))。 - 条件编译 —— 对无法通过 Polyfill 实现的功能使用
#if:
#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
有关 Polyfill 的完整指引,请参阅 [skill:dotnet-multi-targeting]。
知识来源
此技能中的功能指引基于以下公开的语言设计理由:
- C# 语言设计说明(Mads Torgersen 等) —— 各个 C# 版本功能背后的设计决策。与此技能相关的关键设计理由包括:主构造函数(减少 DI 密集型服务中的样板代码)、集合表达式(统一集合初始化语法)、
field关键字(消除后备字段的繁琐写法)以及扩展块(按目标类型对扩展进行分组)。每项功能都在表达能力与安全性之间取得平衡——例如,主构造函数参数有意设计为可变捕获(而非只读),以保持功能简单;需要不可变性时,请使用显式的只读字段。来源:https://github.com/dotnet/csharplang/tree/main/meetings - C# 语言提案仓库 —— 已接受和拟议功能的详细规范及设计理由。来源:https://github.com/dotnet/csharplang/tree/main/proposals
注意: 此技能采用公开记载的设计理由,并不代表或代言上述来源。