关于 modern-csharp-coding-standards
Modern C# Coding Standards 是供开发者编写或重构 C#、设计 API 时参考的技能,涵盖现代语言模式、类型安全、异步代码、性能、组合与错误处理。
主要功能
- 01不可变 record 与值对象
- 02可空引用类型与模式匹配
- 03支持取消的异步 API
- 04Span 和 Memory 性能指导
- 05组合设计与 Result 错误处理
适用场景
- 01编写新 C# 代码时应用规范
- 02重构现有 C# 应用
- 03设计库或服务的公共 API
- 04改进高吞吐量或异步代码路径
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill modern-csharp-coding-standards安装后在 Claude Code 中输入/modern-csharp-coding-standards确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
anti-patterns-and-reflection.mdcomposition-and-error-handling.mdperformance-and-api-design.mdvalue-objects-and-patterns.md详细介绍
现代 C# 编码规范
何时使用此技能
在以下情况下使用此技能:
- 编写新的 C# 代码或重构现有代码
- 为库或服务设计公共 API
- 优化对性能要求较高的代码路径
- 使用强类型构建领域模型
- 构建大量使用 async/await 的应用
- 处理二进制数据、缓冲区或高吞吐量场景
参考文件
- value-objects-and-patterns.md:完整的值对象示例和模式匹配代码
- performance-and-api-design.md:Span/Memory 示例和 API 设计原则
- composition-and-error-handling.md:优先组合而非继承、Result 类型和测试模式
- anti-patterns-and-reflection.md:避免反射和常见反模式
核心原则
- 默认不可变 - 使用
record类型和仅限init的属性 - 类型安全 - 利用可空引用类型和值对象
- 现代模式匹配 - 广泛使用
switch表达式和模式 - 全面采用异步 - 优先使用支持适当取消机制的异步 API
- 零分配模式 - 在对性能要求较高的代码中使用
Span<T>和Memory<T> - API 设计 - 接受抽象类型,返回具体程度适当的类型
- 优先组合而非继承 - 避免抽象基类,优先使用组合
- 将值对象定义为结构体 - 对值对象使用
readonly record struct
语言模式
使用 Record 表示不可变数据(C# 9+)
对 DTO、消息、事件和领域实体使用 record 类型。
// Simple immutable DTO
public record CustomerDto(string Id, string Name, string Email);
// Record with validation in constructor
public record EmailAddress
{
public string Value { get; init; }
public EmailAddress(string value)
{
if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
throw new ArgumentException("Invalid email address", nameof(value));
Value = value;
}
}
// Records with collections - use IReadOnlyList
public record ShoppingCart(
string CartId,
string CustomerId,
IReadOnlyList<CartItem> Items
)
{
public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}
何时使用 record class 与 record struct:
record class(默认):引用类型,用于实体、聚合以及具有多个属性的 DTOrecord struct:值类型,用于值对象(参见下一节)
将值对象定义为 readonly record struct
为实现性能和符合值语义,值对象始终应为 readonly record struct。使用显式转换,切勿使用隐式运算符。
public readonly record struct OrderId(string Value)
{
public OrderId(string value) : this(
!string.IsNullOrWhiteSpace(value)
? value
: throw new ArgumentException("OrderId cannot be empty", 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());
}
请参阅 value-objects-and-patterns.md,了解完整示例,包括多值对象、工厂模式和禁止隐式转换的规则。
模式匹配(C# 8-12)
使用 switch 表达式、属性模式、关系模式和列表模式,让代码更简洁。
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
};
请参阅 value-objects-and-patterns.md,了解完整的模式匹配示例。
可空引用类型(C# 8+)
在项目中启用可空引用类型,并显式处理 null。
// In .csproj
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// Explicit nullability
public string? FindUserName(string userId)
{
var user = _repository.Find(userId);
return user?.Name;
}
// Pattern matching with null checks
public decimal GetDiscount(Customer? customer) => customer switch
{
null => 0m,
{ IsVip: true } => 0.20m,
{ OrderCount: > 10 } => 0.10m,
_ => 0.05m
};
// Guard clauses with ArgumentNullException.ThrowIfNull (C# 11+)
public void ProcessOrder(Order? order)
{
ArgumentNullException.ThrowIfNull(order);
// order is now non-nullable in this scope
Console.WriteLine(order.Id);
}
优先组合而非继承
避免抽象基类。 使用接口与组合。使用静态辅助方法共享逻辑。使用带工厂方法的 record 表示不同变体。
请参阅 composition-and-error-handling.md,了解完整示例。
性能模式
Async/Await 最佳实践
// Async all the way - always accept CancellationToken
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
var order = await _repository.GetAsync(orderId, cancellationToken);
return order;
}
// ValueTask for frequently-called, often-synchronous methods
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 for 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;
}
}
关键规则:
- 始终接受
CancellationToken,并将= default作为默认值 - 在库代码中使用
ConfigureAwait(false) - 切勿阻塞异步代码(不要使用
.Result或.Wait()) - 使用链接的 CancellationTokenSource 实现超时
Span 和 Memory
同步零分配操作使用 Span<T>,异步操作使用 Memory<T>,大型临时缓冲区使用 ArrayPool<T>。
请参阅 performance-and-api-design.md,了解完整的 Span/Memory 示例和 API 设计章节。
错误处理:Result 类型
对于预期错误,使用特定于领域的结果类型,而不是异常或通用的 Result<T>。仅对意外错误或系统错误使用异常。
请参阅 composition-and-error-handling.md,了解完整的 Result 类型实现和用法示例。
避免基于反射的元编程
禁止使用: AutoMapper、Mapster、ExpressMapper。请改用显式映射扩展方法。确实需要访问私有成员时,请使用 UnsafeAccessorAttribute(.NET 8+)。
完整指南请参阅 anti-patterns-and-reflection.md。
代码组织
// File: Domain/Orders/Order.cs
namespace MyApp.Domain.Orders;
// 1. Primary domain type
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", "Can only add items to draft orders"));
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 for state
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }
// 3. Related types
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. Errors
public readonly record struct OrderError(string Code, string Message);
最佳实践总结
应该做
- 使用
record表示 DTO、消息和领域实体 - 使用
readonly record struct表示值对象 - 使用
switch表达式进行模式匹配 - 启用并遵循可空引用类型
- 对所有 I/O 操作使用 async/await
- 在所有异步方法中接受
CancellationToken - 在高性能场景中使用
Span<T>和Memory<T> - 接受抽象类型(
IEnumerable<T>、IReadOnlyList<T>) - 使用
Result<T, TError>处理预期错误 - 对大型分配使用
ArrayPool<T>池化缓冲区 - 优先使用组合而非继承
不应该做
- 能使用记录时,不要使用可变类
- 不要用类表示值对象(应使用
readonly record struct) - 不要创建深层继承层次结构
- 不要忽略可空引用类型警告
- 不要阻塞异步代码(
.Result、.Wait()) Span<byte>足够时,不要使用byte[]- 不要遗漏
CancellationToken参数 - 不要从 API 返回可变集合
- 不要为预期的业务错误抛出异常
- 不要反复分配大型数组(应使用
ArrayPool)
详细的反模式示例请参阅 anti-patterns-and-reflection.md。
更多资源
- C# 语言规范:https://learn.microsoft.com/en-us/dotnet/csharp/
- 模式匹配:https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching
- Span 和 Memory:https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/
- 异步最佳实践:https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
- .NET 性能提示:https://learn.microsoft.com/en-us/dotnet/framework/performance/