ModernCCodingStandardsSkill modern-csharp-coding-standards

掌握现代C#编程标准,包括记录类型、模式匹配、异步编程等,以编写高效、可维护的代码

后端开发 0 次安装 0 次浏览 更新于 2/26/2026

现代C#编码标准

何时使用此技能

使用此技能时:

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

核心原则

  1. 默认不可变性 - 使用record类型和init-only属性
  2. 类型安全性 - 利用可空引用类型和值对象
  3. 现代模式匹配 - 广泛使用switch表达式和模式
  4. 异步无处不在 - 优先选择带有适当取消支持的异步API
  5. 零分配模式 - 对于性能关键代码,使用Span<T>Memory<T>
  6. API设计 - 接受抽象,返回适当具体的类型
  7. 组合优于继承 - 避免抽象基类,优先选择组合
  8. 值对象作为结构体 - 对于值对象使用readonly record struct

语言模式

用于不可变数据的记录(C# 9+)

对DTO、消息、事件和领域实体使用record类型。

// 简单的不可变DTO
public record CustomerDto(string Id, string Name, string Email);

// 构造函数中带有验证的记录
public record EmailAddress
{
    public string Value { get; init; }

    public EmailAddress(string value)
    {
        if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
            throw new ArgumentException("无效的电子邮件地址", nameof(value));

        Value = value;
    }
}

// 带有计算属性的记录
public record Order(string Id, decimal Subtotal, decimal Tax)
{
    public decimal Total => Subtotal + Tax;
}

// 带有集合的记录 - 使用IReadOnlyList
public record ShoppingCart(
    string CartId,
    string CustomerId,
    IReadOnlyList<CartItem> Items
)
{
    public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}

何时使用record classrecord 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不能为空", nameof(value)))
    {
    }

    public override string ToString() => Value;

    // 无隐式转换 - 破坏类型安全性!
    // 显式访问内部值:orderId.Value
}

// 多值对象
public readonly record struct Money(decimal Amount, string Currency)
{
    public Money(decimal amount, string currency) : this(
        amount >= 0 ? amount : throw new ArgumentException("金额不能为负", nameof(amount)),
        ValidateCurrency(currency))
    {
    }

    private static string ValidateCurrency(string currency)
    {
        if (string.IsNullOrWhiteSpace(currency) || currency.Length != 3)
            throw new ArgumentException("货币必须是一个3字母代码", nameof(currency));
        return currency.ToUpperInvariant();
    }

    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new InvalidOperationException($"不能将{Currency}加到{other.Currency}");

        return new Money(Amount + other.Amount, Currency);
    }

    public override string ToString() => $"{Amount:N2} {Currency}";
}

// 具有工厂模式的复杂值对象
public readonly record struct PhoneNumber
{
    public string Value { get; }

    private PhoneNumber(string value) => Value = value;

    public static Result<PhoneNumber, string> Create(string input)
    {
        if (string.IsNullOrWhiteSpace(input))
            return Result<PhoneNumber, string>.Failure("电话号码不能为空");

        // 规范化:移除所有非数字
        var digits = new string(input.Where(char.IsDigit).ToArray());

        if (digits.Length < 10 || digits.Length > 15)
            return Result<PhoneNumber, string>.Failure("电话号码必须是10-15位数字");

        return Result<PhoneNumber, string>.Success(new PhoneNumber(digits));
    
    public override string ToString() => Value;
}

// 百分比值对象带范围验证
public readonly record struct Percentage
{
    private readonly decimal _value;

    public decimal Value => _value;

    public Percentage(decimal value)
    {
        if (value < 0 || value > 100)
            throw new ArgumentOutOfRangeException(nameof(value), "百分比必须在0到100之间");
        _value = value;
    }

    public decimal AsDecimal() => _value / 100m;

    public static Percentage FromDecimal(decimal decimalValue)
    {
        if (decimalValue < 0 || decimalValue > 1)
            throw new ArgumentOutOfRangeException(nameof(decimalValue), "小数必须在0和1之间");
        return new Percentage(decimalValue * 100);
    }

    public override string ToString() => $"{_value}%";
}

// 强类型ID
public readonly record struct CustomerId(Guid Value)
{
    public static CustomerId New() => new(Guid.NewGuid());
    public override string ToString() => Value.ToString();
}

// 带单位的数量
public readonly record struct Quantity(int Value, string Unit)
{
    public Quantity(int value, string unit) : this(
        value >= 0 ? value : throw new ArgumentException("数量不能为负"),
        !string.IsNullOrWhiteSpace(unit) ? unit : throw new ArgumentException("单位不能为空"))
    {
    }

    public override string ToString() => $"{Value} {Unit}";
}

为什么对值对象使用readonly record struct

  • 值语义:基于内容的等式,而不是引用
  • 栈分配:更好的性能,没有GC压力
  • 不可变性readonly防止意外变异
  • 模式匹配:与switch表达式无缝协作

**至关重要:无隐式转换。**隐式运算符破坏了值对象的目的,允许静默类型强制:

// 错误 - 破坏编译时安全性:
public readonly record struct UserId(Guid Value)
{
    public static implicit operator UserId(Guid value) => new(value);  // 不!
    public static implicit operator Guid(UserId value) => value.Value; // 不!
}

// 有隐式运算符时,这会无声息地编译:
void ProcessUser(UserId userId) { }
ProcessUser(Guid.NewGuid());  // 哎呀 - 打算传递PostId

// 正确 - 所有转换都是显式的:
public readonly record struct UserId(Guid Value)
{
    public static UserId New() => new(Guid.NewGuid());
    // 没有隐式运算符
    // 创建:new UserId(guid)或UserId.New()
    // 提取:userId.Value
}

显式转换迫使每个边界交叉都可见:

// API边界 - 显式转换IN
var userId = new UserId(request.UserId);  // 入口验证

// 数据库边界 - 显式转换OUT
await _db.ExecuteAsync(sql, new { UserId = userId.Value });

模式匹配(C# 8-12)

利用现代模式匹配获得更清晰、更富有表现力的代码。

// 带有值对象的switch表达式
public string GetPaymentMethodDescription(PaymentMethod payment) => payment switch
{
    { Type: PaymentType.CreditCard, Last4: var last4 } => $"信用卡以{last4}结尾",
    { Type: PaymentType.BankTransfer, AccountNumber: var account } => $"银行转账来自{account}",
    { Type: PaymentType.Cash } => "现金支付",
    _ => "未知支付方式"
};

// 属性模式
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
};

// 关系和逻辑模式
public string ClassifyTemperature(int temp) => temp switch
{
    < 0 => "冻结",
    >= 0 and < 10 => "冷",
    >= 10 and < 20 => "凉爽",
    >= 20 and < 30 => "温暖",
    >= 30 => "热",
    _ => throw new ArgumentOutOfRangeException(nameof(temp))
};

// 列表模式(C# 11+)
public bool IsValidSequence(int[] numbers) => numbers switch
{
    [] => false,                                      // 空
    [_] => true,                                      // 单个元素
    [var first, .., var last] when first < last => true,  // 第一个<最后一个
    _ => false
};

// 带有null检查的类型模式
public string FormatValue(object? value) => value switch
{
    null => "null",
    string s => $"\"{s}\"",
    int i => i.ToString(),
    double d => d.ToString("F2"),
    DateTime dt => dt.ToString("yyyy-MM-dd"),
    Money m => m.ToString(),
    IEnumerable<object> collection => $"[{string.Join(", ", collection)}]",
    _ => value.ToString() ?? "未知"
};

// 结合模式进行复杂逻辑
public record OrderState(bool IsPaid, bool IsShipped, bool IsCancelled);

public string GetOrderStatus(OrderState state) => state switch
{
    { IsCancelled: true } => "已取消",
    { IsPaid: true, IsShipped: true } => "已交付",
    { IsPaid: true, IsShipped: false } => "处理中",
    { IsPaid: false } => "等待支付",
    _ => "未知"
};

// 带有值对象的模式匹配
public decimal CalculateShipping(Money total, Country destination) => (total, destination) switch
{
    ({ Amount: > 100m }, _) => 0m,                    // 100美元以上免费送货
    (_, { Code: "US" or "CA" }) => 5m,                // 北美
    (_, { Code: "GB" or "FR" or "DE" }) => 10m,       // 欧洲
    _ => 25m                                           // 国际
};

可空引用类型(C# 8+)

在你的项目中启用可空引用类型,并显式处理null。

// 在.csproj中
<PropertyGroup>
    <Nullable>enable</Nullable>
</PropertyGroup>

// 显式空性
public class UserService
{
    // 默认不可空
    public string GetUserName(User user) => user.Name;

    // 显式可空返回
    public string? FindUserName(string userId)
    {
        var user = _repository.Find(userId);
        return user?.Name;  // 如果用户未找到则返回null
    }

    // Null-forgiving运算符(慎用!)
    public string GetRequiredConfigValue(string key)
    {
        var value = Configuration[key];
        return value!;  // 只有在你确定它不为空时才使用
    }

    // 可空值对象
    public Money? GetAccountBalance(string accountId)
    {
        var account = _repository.Find(accountId);
        return account?.Balance;
    }
}

// 带有null检查的模式匹配
public decimal GetDiscount(Customer? customer) => customer switch
{
    null => 0m,
    { IsVip: true } => 0.20m,
    { OrderCount: > 10 } => 0.10m,
    _ => 0.05m
};

// Null-coalescing模式
public string GetDisplayName(User? user) =>
    user?.PreferredName ?? user?.Email ?? "Guest";

// 带有ArgumentNullException.ThrowIfNull的Guard子句(C# 11+)
public void ProcessOrder(Order? order)
{
    ArgumentNullException.ThrowIfNull(order);

    // 在此范围内order现在是不可空的
    Console.WriteLine(order.Id);
}

组合优于继承

避免抽象基类和继承层次结构。 使用组合和接口代替。

// ❌ 不好:抽象基类层次结构
public abstract class PaymentProcessor
{
    public abstract Task<PaymentResult> ProcessAsync(Money amount);

    protected async Task<bool> ValidateAsync(Money amount)
    {
        // 共享验证逻辑
        return amount.Amount > 0;
    }
}

public class CreditCardProcessor : PaymentProcessor
{
    public override async Task<PaymentResult> ProcessAsync(Money amount)
    {
        await ValidateAsync(amount);
        // 处理信用卡...
    }
}

// ✅ 好:组合接口
public interface IPaymentProcessor
{
    Task<PaymentResult> ProcessAsync(Money amount, CancellationToken cancellationToken);
}

public interface IPaymentValidator
{
    Task<ValidationResult> ValidateAsync(Money amount, CancellationToken cancellationToken);
}

// 具体实现组合验证器
public sealed class CreditCardProcessor : IPaymentProcessor
{
    private readonly IPaymentValidator _validator;
    private readonly ICreditCardGateway _gateway;

    public CreditCardProcessor(IPaymentValidator validator, ICreditCardGateway gateway)
    {
        _validator = validator;
        _gateway = gateway;
    }

    public async Task<PaymentResult> ProcessAsync(Money amount, CancellationToken cancellationToken)
    {
        var validation = await _validator.ValidateAsync(amount, cancellationToken);
        if (!validation.IsValid)
            return PaymentResult.Failed(validation.Error);

        return await _gateway.ChargeAsync(amount, cancellationToken);
    }
}

// ✅ 好:静态帮助类共享逻辑(无继承)
public static class PaymentValidation
{
    public static ValidationResult ValidateAmount(Money amount)
    {
        if (amount.Amount <= 0)
            return ValidationResult.Invalid("金额必须为正");

        if (amount.Amount > 10000m)
            return ValidationResult.Invalid("金额超出最大值");

        return ValidationResult.Valid();
    }
}

// ✅ 好:记录建模变体(不继承)
public enum PaymentType { CreditCard, BankTransfer, Cash }

public record PaymentMethod
{
    public PaymentType Type { get; init; }
    public string? Last4 { get; init; }           // 用于信用卡
    public string? AccountNumber { get; init; }    // 用于银行转账

    public static PaymentMethod CreditCard(string last4) => new()
    {
        Type = PaymentType.CreditCard,
        Last4 = last4
    };

    public static PaymentMethod BankTransfer(string accountNumber) => new()
    {
        Type = PaymentType.BankTransfer,
        AccountNumber = accountNumber
    };

    public static PaymentMethod Cash() => new() { Type = PaymentType.Cash };
}

继承是可以接受的:

  • 框架要求(例如ASP.NET Core中的ControllerBase
  • 库集成(例如从Exception继承的自定义异常)
  • 这些应该是你的应用程序代码中的罕见情况

性能模式

Async/Await最佳实践

始终对I/O绑定操作使用异步:

// ✅ 好:异步到底
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
    var order = await _repository.GetAsync(orderId, cancellationToken);
    var customer = await _customerService.GetCustomerAsync(order.CustomerId, cancellationToken);
    return order;
}

// ❌ 不好:在异步代码上阻塞
public Order GetOrder(string orderId)
{
    return _repository.GetAsync(orderId).Result;  // 死锁风险!
}

// ✅ 好:频繁调用的方法使用ValueTask
public ValueTask<Order?> GetCachedOrderAsync(string orderId, CancellationToken cancellationToken)
{
    if (_cache.TryGetValue(orderId, out var order))
        return ValueTask.FromResult<Order?>(order);  // 同步路径,无分配

    return GetFromDatabaseAsync(orderId, cancellationToken);  // 异步路径
}

private async ValueTask<Order?> GetFromDatabaseAsync(string orderId, CancellationToken cancellationToken)
{
    var order = await _repository.GetAsync(orderId, cancellationToken);
    if (order is not null)
        _cache[orderId] = order;
    return order;
}

// ✅ 好:流式IAsyncEnumerable
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;
    }
}

// ✅ 好:库代码中的ConfigureAwait(false)(不是应用程序代码)
public async Task<string> ProcessDataAsync(string input, CancellationToken cancellationToken)
{
    var data = await FetchDataAsync(cancellationToken).ConfigureAwait(false);
    var result = await TransformDataAsync(data, cancellationToken).ConfigureAwait(false);
    return result;
}

始终接受CancellationToken:

// ✅ 好:带有默认的CancellationToken参数
public async Task<List<Order>> GetOrdersAsync(
    string customerId,
    CancellationToken cancellationToken = default)
{
    var orders = await _repository.GetOrdersByCustomerAsync(customerId, cancellationToken);
    return orders;
}

// 通过调用堆栈传递取消
public async Task<OrderSummary> GetOrderSummaryAsync(
    string customerId,
    CancellationToken cancellationToken = default)
{
    var orders = await GetOrdersAsync(customerId, cancellationToken);
    var total = orders.Sum(o => o.Total);
    return new OrderSummary(customerId, orders.Count, total);
}

// 组合操作时链接取消令牌
public async Task<ProcessResult> ProcessWithTimeoutAsync(
    string data,
    TimeSpan timeout,
    CancellationToken cancellationToken = default)
{
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
    cts.CancelAfter(timeout);

    return await ProcessAsync(data, cts.Token);
}

Span<T>和Memory<T>零分配代码

对性能关键代码,使用Span<T>Memory<T>代替byte[]string

// ✅ 好:Span<T>用于同步、零分配操作
public int ParseOrderId(ReadOnlySpan<char> input)
{
    // 不分配工作数据
    if (!input.StartsWith("ORD-"))
        throw new FormatException("无效的订单ID格式");

    var numberPart = input.Slice(4);
    return int.Parse(numberPart);
}

// stackalloc与Span<T>
public void FormatMessage()
{
    Span<char> buffer = stackalloc char[256];
    var written = FormatInto(buffer);
    var message = new string(buffer.Slice(0, written));
}

// SkipLocalsInit与stackalloc - 跳过零初始化以提高性能
// 默认情况下,.NET会零初始化所有局部变量(.locals init标志)。这可能
// 对stackalloc有可衡量的开销。当:
//   - 你在读取之前写入缓冲区(如下面的FormatInto)
//   - 性能分析显示零初始化是瓶颈
// ⚠️ 警告:在写入之前读取会返回垃圾数据(见文档示例)
// 需要:<AllowUnsafeBlocks>true</AllowUnsafeBlocks>在.csproj中
// 见:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/general#skiplocalsinit-attribute
using System.Runtime.CompilerServices;
[SkipLocalsInit]
public void FormatMessage()
{
    Span<char> buffer = stackalloc char[256];
    var written = FormatInto(buffer);
    var message = new string(buffer.Slice(0, written));
}

// ✅ 好:异步操作的Memory<T>(Span不能跨越await边界)
public async Task<int> ReadDataAsync(
    Memory<byte> buffer,
    CancellationToken cancellationToken)
{
    return await _stream.ReadAsync(buffer, cancellationToken);
}

// ✅ 好:避免分配的字符串操作与Span
public bool TryParseKeyValue(ReadOnlySpan<char> line, out string key, out string value)
{
    key = string.Empty;
    value = string.Empty;

    int colonIndex = line.IndexOf(':');
    if (colonIndex == -1)
        return false;

    // 只有在我们知道格式有效时才分配字符串
    key = new string(line.Slice(0, colonIndex).Trim());
    value = new string(line.Slice(colonIndex + 1).Trim());
    return true;
}

// ✅ 好:ArrayPool临时大缓冲区
public async Task ProcessLargeFileAsync(
    Stream stream,
    CancellationToken cancellationToken)
{
    var buffer = ArrayPool<byte>.Shared.Rent(8192);
    try
    {
        int bytesRead;
        while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(), cancellationToken)) > 0)
        {
            ProcessChunk(buffer.AsSpan(0, bytesRead));
        }
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(buffer);
    }
}

// 混合缓冲区模式用于短暂的UTF-8工作。见相应部分的SkipLocalsInit注意事项。

[SkipLocalsInit]
static short GenerateHashCode(string? key)
{
    if (key is null) return 0;

    const int StackLimit = 256;

    var enc = Encoding.UTF8;
    var max = enc.GetMaxByteCount(key.Length);

    byte[]? rented = null;
    Span<byte> buf = max <= StackLimit
        ? stackalloc byte[StackLimit]
        : (rented = ArrayPool<byte>.Shared.Rent(max));

    try
    {
        var written = enc.GetBytes(key.AsSpan(), buf);
        ComputeHash(buf[..written], out var h1, out var h2);
        return unchecked((short)(h1 ^ h2));
    }
    finally
    {
        if (rented is not null) ArrayPool<byte>.Shared.Return(rented);
    }
}

// ✅ 好:无子字符串分配的基于Span的解析
public static (string Protocol, string Host, int Port) ParseUrl(ReadOnlySpan<char> url)
{
    var protocolEnd = url.IndexOf("://");
    var protocol = new string(url.Slice(0, protocolEnd));

    var afterProtocol = url.Slice(protocolEnd + 3);
    var portStart = afterProtocol.IndexOf(':');

    var host = new string(afterProtocol.Slice(0, portStart));
    var portSpan = afterProtocol.Slice(portStart + 1);
    var port = int.Parse(portSpan);

    return (protocol, host, port);
}

// ✅ 好:向Span写数据
public bool TryFormatOrderId(int orderId, Span<char> destination, out int charsWritten)
{
    const string prefix = "ORD-";

    if (destination.Length < prefix.Length + 10)
    {
        charsWritten = 0;
        return false;
    }

    prefix.AsSpan().CopyTo(destination);
    var numberWritten = orderId.TryFormat(
        destination.Slice(prefix.Length),
        out var numberChars);

    charsWritten = prefix.Length + numberChars;
    return numberWritten;
}

何时使用什么:

类型 用例
Span<T> 同步操作,栈分配缓冲区,无分配切片
ReadOnlySpan<T> 只读视图,你不会修改的数据的方法参数
Memory<T> 异步操作(Span不能跨越await边界)
ReadOnlyMemory<T> 只读异步操作
byte[] 当你需要长期存储数据或传递给需要数组的API时
ArrayPool<T> 大型临时缓冲区(>1KB)以减少GC压力

API设计原则

接受抽象,返回适当具体

对于参数(接受):

// ✅ 好:如果你只迭代一次,接受IEnumerable<T>
public decimal CalculateTotal(IEnumerable<OrderItem> items)
{
    return items.Sum(item => item.Price * item.Quantity);
}

// ✅ 好:如果你需要计数,接受IReadOnlyCollection<T>
public bool HasMinimumItems(IReadOnlyCollection<OrderItem> items, int minimum)
{
    return items.Count >= minimum;
}

// ✅ 好:如果你需要索引,接受IReadOnlyList<T>
public OrderItem GetMiddleItem(IReadOnlyList<OrderItem> items)
{
    if (items.Count == 0)
        throw new ArgumentException("列表不能为空");

    return items[items.Count / 2];  // 索引访问
}

// ✅ 好:对于高性能、零分配API,接受ReadOnlySpan<T>
public int Sum(ReadOnlySpan<int> numbers)
{
    int total = 0;
    foreach (var num in numbers)
        total += num;
    return total;
}

// ✅ 好:对于异步流,接受IAsyncEnumerable<T>
public async Task<int> CountItemsAsync(
    IAsyncEnumerable<Order> orders,
    CancellationToken cancellationToken)
{
    int count = 0;
    await foreach (var order in orders.WithCancellation(cancellationToken))
        count++;
    return count;
}

对于返回类型:

// ✅ 好:对于懒加载/延迟执行,返回IEnumerable<T>
public IEnumerable<Order> GetOrdersLazy(string customerId)
{
    foreach (var order in _repository.Query())
    {
        if (order.CustomerId == customerId)
            yield return order;  // 懒加载评估
    }
}

// ✅ 好:对于具体化、不可变集合,返回IReadOnlyList<T>
public IReadOnlyList<Order> GetOrders(string customerId)
{
    return _repository
        .Query()
        .Where(o => o.CustomerId == customerId)
        .ToList();  // 具体化
}

// ✅ 好:当调用者需要变异时,返回具体类型
public List<Order> GetMutableOrders(string customerId)
{
    // 明确允许变异,返回List<T>
    return _repository
        .Query()
        .Where(o => o.CustomerId == customerId)
        .ToList();
}

// ✅ 好:对于异步流,返回IAsyncEnumerable<T>
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;
    }
}

// ✅ 好:对于互操作或当调用者期望数组时,返回数组
public byte[] SerializeOrder(Order order)
{
    // 二进制序列化 - byte[]在这里是合适的
    return MessagePackSerializer.Serialize(order);
}

总结表:

场景 接受 返回
只迭代一次 IEnumerable<T> IEnumerable<T>(如果是懒加载)
需要计数 IReadOnlyCollection<T> IReadOnlyCollection<T>
需要索引 IReadOnlyList<T> IReadOnlyList<T>
高性能,同步 ReadOnlySpan<T> Span<T>(很少)
异步流 IAsyncEnumerable<T> IAsyncEnumerable<T>
调用者需要变异 - List<T>, T[]

方法签名最佳实践

// ✅ 好:完整的异步方法签名
public async Task<Result<Order, OrderError>> CreateOrderAsync(
    CreateOrderRequest request,
    CancellationToken cancellationToken = default)
{
    // 实现
}

// ✅ 好:可选参数在最后
public async Task<List<Order>> GetOrdersAsync(
    string customerId,
    DateTime? startDate = null,
    DateTime? endDate = null,
    CancellationToken cancellationToken = default)
{
    // 实现
}

// ✅ 好:用于多个相关参数的记录
public record SearchOrdersRequest(
    string? CustomerId,
    DateTime? StartDate,
    DateTime? EndDate,
    OrderStatus? Status,
    int PageSize = 20,
    int PageNumber = 1
);

public async Task<PagedResult<Order>> SearchOrdersAsync(
    SearchOrdersRequest request,
    CancellationToken cancellationToken = default)
{
    // 实现
}

// ✅ 好:简单类的主构造函数(C# 12+)
public sealed class OrderService(IOrderRepository repository, ILogger<OrderService> logger)
{
    public async Task<Order> GetOrderAsync(OrderId orderId, CancellationToken cancellationToken)
    {
        logger.LogInformation("正在获取订单{OrderId}", orderId);
        return await repository.GetAsync(orderId, cancellationToken);
    }
}

// ✅ 好:复杂配置的选项模式
public sealed class EmailServiceOptions
{
    public required string SmtpHost { get; init; }
    public int SmtpPort { get; init; } = 587;
    public bool UseSsl { get; init; } = true;
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(30);
}

public sealed class EmailService(IOptions<EmailServiceOptions> options)
{
    private readonly EmailServiceOptions _options = options.Value;
}

错误处理

结果类型模式(面向铁路的编程)

对于预期错误,使用Result<T, TError>类型而不是异常。

// 简单的Result类型作为readonly记录结构
public readonly record struct Result<TValue, TError>
{
    private readonly TValue? _value;
    private readonly TError? _error;
    private readonly bool _isSuccess;

    private Result(TValue value)
    {
        _value = value;
        _error = default;
        _isSuccess = true;
    }

    private Result(TError error)
    {
        _value = default;
        _error = error;
        _isSuccess = false;
    }

    public bool IsSuccess => _isSuccess;
    public bool IsFailure => !_isSuccess;

    public TValue Value => _isSuccess
        ? _value!
        : throw new InvalidOperationException("不能访问失败结果的值");

    public TError Error => !_isSuccess
        ? _error!
        : throw new InvalidOperationException("不能访问成功结果的错误");

    public static Result<TValue, TError> Success(TValue value) => new(value);
    public static Result<TValue, TError> Failure(TError error) => new(error);

    public Result<TOut, TError> Map<TOut>(Func<TValue, TOut> mapper)
        => _isSuccess
            ? Result<TOut, TError>.Success(mapper(_value!))
            : Result<TOut, TError>.Failure(_error!);

    public Result<TOut, TError> Bind<TOut>(Func<TValue, Result<TOut, TError>> binder)
        => _isSuccess ? binder(_value!) : Result<TOut, TError>.Failure(_error!);

    public TValue GetValueOr(TValue defaultValue)
        => _isSuccess ? _value! : defaultValue;

    public TResult Match<TResult>(
        Func<TValue, TResult> onSuccess,
        Func<TError, TResult> onFailure)
        => _isSuccess ? onSuccess(_value!) : onFailure(_error!);
}

// 错误类型作为readonly记录结构
public readonly record struct OrderError(string Code, string Message);

// 使用示例
public sealed class OrderService(IOrderRepository repository)
{
    public async Task<Result<Order, OrderError>> CreateOrderAsync(
        CreateOrderRequest request,
        CancellationToken cancellationToken)
    {
        // 验证
        var validationResult = ValidateRequest(request);
        if (validationResult.IsFailure)
            return Result<Order, OrderError>.Failure(validationResult.Error);

        // 检查库存
        var inventoryResult = await CheckInventoryAsync(request.Items, cancellationToken);
        if (inventoryResult.IsFailure)
            return Result<Order, OrderError>.Failure(inventoryResult.Error);

        // 创建订单
        var order = new Order(
            OrderId.New(),
            new CustomerId(request.CustomerId),
            request.Items);

        await repository.SaveAsync(order, cancellationToken);

        return Result<Order, OrderError>.Success(order);
    }

    // 模式匹配Result
    public IActionResult MapToActionResult(Result<Order, OrderError> result)
    {
        return result.Match(
            onSuccess: order => new OkObjectResult(order),
            onFailure: error => error.Code switch
            {
                "VALIDATION_ERROR" => new BadRequestObjectResult(error.Message),
                "INSUFFICIENT_INVENTORY" => new ConflictObjectResult(error.Message),
                "NOT_FOUND" => new NotFoundObjectResult(error.Message),
                _ => new ObjectResult(error.Message) { StatusCode = 500 }
            }
        );
    }
}

何时使用Result与异常:

  • 使用Result:预期错误(验证、业务规则、未找到)
  • 使用异常:意外错误(网络故障、系统错误、编程错误)

测试模式

// 使用记录作为测试数据构建器
public record OrderBuilder
{
    public OrderId Id { get; init; } = OrderId.New();
    public CustomerId CustomerId { get; init; } = CustomerId.New();
    public Money Total { get; init; } = new Money(100m, "USD");
    public IReadOnlyList<OrderItem> Items { get; init; } = Array.Empty<OrderItem>();

    public Order Build() => new(Id, CustomerId, Total, Items);
}

// 使用'with'表达式进行测试变化
[Fact]
public void CalculateDiscount_LargeOrder_AppliesCorrectDiscount()
{
    // 安排
    var baseOrder = new OrderBuilder().Build();
    var largeOrder = baseOrder with
    {
        Total = new Money(1500m, "USD")
    };

    // 行动
    var discount = _service.CalculateDiscount(largeOrder);

    // 断言
    discount.Should().Be(new Money(225m, "USD")); // 1500的15%
}

// 基于Span的测试
[Theory]
[InlineData("ORD-12345", true)]
[InlineData("INVALID", false)]
public void TryParseOrderId_VariousInputs_ReturnsExpectedResult(
    string input,
    bool expected)
{
    // 行动
    var result = OrderIdParser.TryParse(input.AsSpan(), out var orderId);

    // 断言
    result.Should().Be(expected);
}

// 测试值对象
[Fact]
public void Money_Add_SameCurrency_ReturnsSum()
{
    // 安排
    var money1 = new Money(100m, "USD");
    var money2 = new Money(50m, "USD");

    // 行动
    var result = money1.Add(money2);

    // 断言
    result.Should().Be(new Money(150m, "USD"));
}

[Fact]
public void Money_Add_DifferentCurrency_ThrowsException()
{
    // 安排
    var usd = new Money(100m, "USD");
    var eur = new Money(50m, "EUR");

    // 行动 & 断言
    var act = () => usd.Add(eur);
    act.Should().Throw<InvalidOperationException>()
        .WithMessage("*不同货币*");
}

避免反射式元编程

优先选择静态类型、显式代码而不是反射式"魔法"库。

反射式库如AutoMapper牺牲编译时安全性以换取便利。当映射失败时,你会在运行时(或更糟,在生产中)而不是编译时发现。

禁止库

问题
AutoMapper 反射魔法,隐藏映射,运行时失败,难以调试
Mapster 与AutoMapper相同的问题
ExpressMapper 与AutoMapper相同的问题

为什么反射映射失败

// 使用AutoMapper - 编译得很好,运行时失败
public record UserDto(string Id, string Name, string Email);
public record UserEntity(Guid Id, string FullName, string EmailAddress);

// 这个映射无声息地产生垃圾:
// - Id:字符串与Guid不匹配
// - Name与FullName:无匹配,null/default
// - Email与EmailAddress:无匹配,null/default
var dto = _mapper.Map<UserDto>(entity);  // 编译!在运行时中断。

使用显式映射方法代替

// 扩展方法 - 编译时检查,易于查找,易于调试
public static class UserMappings
{
    public static UserDto ToDto(this UserEntity entity) => new(
        Id: entity.Id.ToString(),
        Name: entity.FullName,
        Email: entity.EmailAddress);

    public static UserEntity ToEntity(this CreateUserRequest request) => new(
        Id: Guid.NewGuid(),
        FullName: request.Name,
        EmailAddress: request.Email);
}

// 使用 - 显式和可追溯
var dto = entity.ToDto();
var entity = request.ToEntity();

显式映射的好处

方面 AutoMapper 显式方法
编译时安全性 否 - 运行时错误 是 - 编译器捕获不匹配
可发现性 隐藏在配置文件中 "Go to Definition"有效
调试 黑箱 逐步通过代码
重构 重命名静默失败 IDE正确重命名
性能 反射开销 直接属性访问
测试 需要集成测试 简单的单元测试

复杂映射

对于复杂的转换,显式代码更有价值:

public static OrderSummaryDto ToSummary(this Order order) => new(
    OrderId: order.Id.Value.ToString(),
    CustomerName: order.Customer.FullName,
    ItemCount: order.Items.Count,
    Total: order.Items.Sum(i => i.Quantity * i.UnitPrice),
    Status: order.Status switch
    {
        OrderStatus.Pending => "等待支付",
        OrderStatus.Paid => "处理中",
        OrderStatus.Shipped => "在路上",
        OrderStatus.Delivered => "已完成",
        _ => "未知"
    },
    FormattedDate: order.CreatedAt.ToString("MMMM d, yyyy"));

这是:

  • 可读性:任何人都能理解转换
  • 可调试性:设置断点,检查值
  • 可测试性:传递一个Order,断言结果
  • 可重构性:更改属性名称,编译器告诉您它在哪里使用

何时反射是可以接受的

反射有合法用途,但映射DTO不是其中之一:

用例 可以接受?
序列化(System.Text.Json, Newtonsoft) 是 - 经过充分测试,有源生成器
依赖注入容器 是 - 框架基础设施
ORM实体映射(EF Core) 是 - 必要的数据库抽象
测试固定装置和构建器 有时 - 仅在测试中的便利
DTO/领域对象映射 不 - 使用显式方法

UnsafeAccessorAttribute(.NET 8+)

当你真的需要访问私有或内部成员(序列化器、测试帮助器、框架代码)时,使用UnsafeAccessorAttribute而不是传统反射。它提供了零开销,AOT兼容的成员访问。

// 避免:传统反射 - 慢,分配,破坏AOT
var field = typeof(Order).GetField("_status", BindingFlags.NonPublic | BindingFlags.Instance);
var status = (OrderStatus)field!.GetValue(order)!;

// 更喜欢:UnsafeAccessor - 零开销,AOT兼容
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_status")]
static extern ref OrderStatus GetStatusField(Order order);

var status = GetStatusField(order);  // 直接访问,无反射

支持的访问器类型:

// 私有字段访问
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_items")]
static extern ref List<OrderItem> GetItemsField(Order order);

// 私有方法访问
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Recalculate")]
static extern void CallRecalculate(Order order);

// 私有静态字段
[UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = "_instanceCount")]
static extern ref int GetInstanceCount(Order order);

// 私有构造函数
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
static extern Order CreateOrder(OrderId id, CustomerId customerId);

为什么UnsafeAccessor比反射好:

方面 反射 UnsafeAccessor
性能 慢(100-1000倍) 零开销
AOT兼容
分配 是(装箱,数组)
编译时检查 部分(签名)

用例:

  • 序列化器访问私有支持字段
  • 测试帮助器验证内部状态
  • 需要绕过可见性的框架代码

资源:


避免反模式

❌ 不要:使用可变DTO

// 坏:可变DTO
public class CustomerDto
{
    public string Id { get; set; }
    public string Name { get; set; }
}

// 好:不可变记录
public record CustomerDto(string Id, string Name);

❌ 不要:将值对象作为类

// 坏:值对象作为类
public class OrderId
{
    public string Value { get; }
    public OrderId(string value) => Value = value;
}

// 好:值对象作为readonly记录结构
public readonly record struct OrderId(string Value);

❌ 不要:创建深层继承层次结构

// 坏:深层继承
public abstract class Entity { }
public abstract class AggregateRoot : Entity { }
public abstract class Order : AggregateRoot { }
public class CustomerOrder : Order { }

// 好:平面结构与组合
public interface IEntity
{
    Guid Id { get; }
}

public record Order(OrderId Id, CustomerId CustomerId, Money Total) : IEntity
{
    Guid IEntity.Id => Id.Value;
}

❌ 不要:当你意味着IReadOnlyList<T>时返回List<T>

// 坏:暴露内部列表以进行修改
public List<Order> GetOrders() => _orders;

// 好:返回只读视图
public IReadOnlyList<Order> GetOrders() => _orders;

❌ 不要:当ReadOnlySpan<byte>可以工作时使用byte[]

// 坏:每次调用都分配数组
public byte[] GetHeader()
{
    var header = new byte[64];
    // 填充标题
    return header;
}

// 好:零分配与Span
public void GetHeader(Span<byte> destination)
{
    if (destination.Length < 64)
        throw new ArgumentException("缓冲区太小");

    // 直接在调用者的缓冲区中填充标题
}

❌ 不要:忘记异步方法中的CancellationToken

// 坏:没有取消支持
public async Task<Order> GetOrderAsync(OrderId id)
{
    return await _repository.GetAsync(id);
}

// 好:取消支持
public async Task<Order> GetOrderAsync(
    OrderId id,
    CancellationToken cancellationToken = default)
{
    return await _repository.GetAsync(id, cancellationToken);
}

❌ 不要:在异步代码上阻塞

// 坏:死锁风险!
public Order GetOrder(OrderId id)
{
    return GetOrderAsync(id).Result;
}

// 坏:也是死锁风险!
public Order GetOrder(OrderId id)
{
    return GetOrderAsync(id).GetAwaiter().GetResult();
}

// 好:异步到底
public async Task<Order> GetOrderAsync(
    OrderId id,
    CancellationToken cancellationToken)
{
    return await _repository.GetAsync(id, cancellationToken);
}

代码组织

// 文件:Domain/Orders/Order.cs

namespace MyApp.Domain.Orders;

// 1. 主要领域类型
public record Order(
    OrderId Id,
    CustomerId CustomerId,
    Money Total,
    OrderStatus Status,
    IReadOnlyList<OrderItem> Items
)
{
    // 计算属性
    public bool IsCompleted => Status is OrderStatus.Completed;

    // 返回Result的领域方法以处理预期错误
    public Result<Order, OrderError> AddItem(OrderItem item)
    {
        if (Status is not OrderStatus.Draft)
            return Result<Order, OrderError>.Failure(
                new OrderError("ORDER_NOT_DRAFT", "只能向草稿订单添加项目"));

        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. 状态枚举
public enum OrderStatus
{
    Draft,
    Submitted,
    Processing,
    Completed,
    Cancelled
}

// 3. 相关类型
public record OrderItem(
    ProductId ProductId,
    Quantity Quantity,
    Money UnitPrice
)
{
    public Money Total => new(
        UnitPrice.Amount * Quantity.Value,
        UnitPrice.Currency);
}

// 4. 值对象
public readonly record struct OrderId(Guid Value)
{
    public static OrderId New() => new(Guid.NewGuid());
}

// 5. 错误
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>
  • 在库代码中使用ConfigureAwait(false)
  • 对大型分配使用ArrayPool<T>进行池化
  • 优先选择组合而不是继承
  • 避免在应用程序代码中使用抽象基类

不做的❌

  • 不要使用可变类当记录有效时
  • 不要将值对象作为类(使用readonly record struct
  • 不要创建深层继承层次结构
  • 不要忽略可空引用类型警告
  • 不要在异步代码上阻塞(.Result, .Wait()
  • 不要使用byte[]Span<byte>足够时
  • 不要忘记CancellationToken参数
  • 不要从API返回可变集合
  • 不要为预期的业务错误抛出异常
  • 不要在循环中使用string连接
  • 不要重复分配大型数组(使用ArrayPool

其他资源