mcpField
M
modern-csharp-coding-standards

modern-csharp-coding-standards

github.com/aaronontheweb开发1.2k2.3k 次安装今天更新
代码仓库

关于 modern-csharp-coding-standards

Modern C# Coding Standards 是供开发者编写或重构 C#、设计 API 时参考的技能,涵盖现代语言模式、类型安全、异步代码、性能、组合与错误处理。

主要功能

  1. 01不可变 record 与值对象
  2. 02可空引用类型与模式匹配
  3. 03支持取消的异步 API
  4. 04Span 和 Memory 性能指导
  5. 05组合设计与 Result 错误处理

适用场景

  1. 01编写新 C# 代码时应用规范
  2. 02重构现有 C# 应用
  3. 03设计库或服务的公共 API
  4. 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.md
composition-and-error-handling.md
performance-and-api-design.md
value-objects-and-patterns.md

详细介绍

现代 C# 编码规范

何时使用此技能

在以下情况下使用此技能:

  • 编写新的 C# 代码或重构现有代码
  • 为库或服务设计公共 API
  • 优化对性能要求较高的代码路径
  • 使用强类型构建领域模型
  • 构建大量使用 async/await 的应用
  • 处理二进制数据、缓冲区或高吞吐量场景

参考文件

核心原则

  1. 默认不可变 - 使用 record 类型和仅限 init 的属性
  2. 类型安全 - 利用可空引用类型和值对象
  3. 现代模式匹配 - 广泛使用 switch 表达式和模式
  4. 全面采用异步 - 优先使用支持适当取消机制的异步 API
  5. 零分配模式 - 在对性能要求较高的代码中使用 Span<T> 和 Memory<T>
  6. API 设计 - 接受抽象类型,返回具体程度适当的类型
  7. 优先组合而非继承 - 避免抽象基类,优先使用组合
  8. 将值对象定义为结构体 - 对值对象使用 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(默认):引用类型,用于实体、聚合以及具有多个属性的 DTO
  • record 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。


更多资源

评论

支持 Markdown