类型设计性能优化Skill type-design-performance

本技能专注于.NET类型设计的最佳实践,旨在通过密封类、使用只读结构体、优先采用静态纯函数、延迟枚举和选择合适集合类型等核心原则,显著提升应用程序性能。内容涵盖C#代码优化、内存管理、JIT去虚拟化、集合选择、异步编程和API设计,适用于后端开发、架构设计和性能调优场景。关键词:.NET性能优化、C#类型设计、内存管理、JIT优化、集合性能、异步编程、API设计、代码审查。

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

name: type-design-performance description: 为性能设计.NET类型。密封类,使用只读结构体,优先使用静态纯函数,避免过早枚举,并选择合适的集合类型。 invocable: false

为性能设计类型

何时使用此技能

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

  • 设计新类型和API时
  • 审查代码以查找性能问题时
  • 在类、结构体和记录之间进行选择时
  • 处理集合和可枚举对象时

核心原则

  1. 密封你的类型 - 除非明确设计为用于继承
  2. 优先使用只读结构体 - 适用于小的、不可变的值类型
  3. 优先使用静态纯函数 - 更好的性能和可测试性
  4. 延迟枚举 - 在需要之前不要物化
  5. 返回不可变集合 - 从API边界返回

默认密封类

密封类支持JIT去虚拟化,并传达API意图。

// 做:密封非为继承设计的类
public sealed class OrderProcessor
{
    public void Process(Order order) { }
}

// 做:密封记录(它们是类)
public sealed record OrderCreated(OrderId Id, CustomerId CustomerId);

// 不要:没有理由地保持未密封
public class OrderProcessor  // 可以被继承 - 是有意的吗?
{
    public virtual void Process(Order order) { }  // 虚方法 = 更慢
}

好处:

  • JIT可以对方法调用进行去虚拟化
  • 传达“这不是一个扩展点”
  • 防止意外的破坏性更改

为值类型使用只读结构体

当结构体不可变时,应标记为 readonly。这可以防止防御性复制。

// 做:为不可变值类型使用只读结构体
public readonly record struct OrderId(Guid Value)
{
    public static OrderId New() => new(Guid.NewGuid());
    public override string ToString() => Value.ToString();
}

// 做:为小的、短生命周期的数据使用只读结构体
public readonly struct Money
{
    public decimal Amount { get; }
    public string Currency { get; }

    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }
}

// 不要:可变结构体(导致防御性复制)
public struct Point  // 不是只读!
{
    public int X { get; set; }  // 可变的!
    public int Y { get; set; }
}

何时使用结构体

使用结构体当 使用类当
小(通常≤16字节) 更大的对象
短生命周期 长生命周期
频繁分配 需要共享引用
需要值语义 需要标识语义
不可变 可变状态

优先使用静态纯函数

没有副作用的静态方法更快且更易于测试。

// 做:静态纯函数
public static class OrderCalculator
{
    public static Money CalculateTotal(IReadOnlyList<OrderItem> items)
    {
        var total = items.Sum(i => i.Price * i.Quantity);
        return new Money(total, "USD");
    }
}

// 用法 - 可预测,可测试
var total = OrderCalculator.CalculateTotal(items);

好处:

  • 无需vtable查找(更快)
  • 无隐藏状态
  • 更易于测试(纯输入 → 输出)
  • 设计上线程安全
  • 强制显式依赖
// 不要:隐藏依赖项的实例方法
public class OrderCalculator
{
    private readonly ITaxService _taxService;  // 隐藏的依赖项
    private readonly IDiscountService _discountService;  // 隐藏的依赖项

    public Money CalculateTotal(IReadOnlyList<OrderItem> items)
    {
        // 这实际上依赖于什么?
    }
}

// 更好:通过参数显式依赖
public static class OrderCalculator
{
    public static Money CalculateTotal(
        IReadOnlyList<OrderItem> items,
        decimal taxRate,
        decimal discountPercent)
    {
        // 所有输入可见
    }
}

不要过度使用 - 当你确实需要状态或多态性时,请使用实例方法。


延迟枚举

在必要时才物化可枚举对象。避免过多的LINQ链。

// 不好:过早物化
public IReadOnlyList<Order> GetActiveOrders()
{
    return _orders
        .Where(o => o.IsActive)
        .ToList()  // 物化了!
        .OrderBy(o => o.CreatedAt)  // 又一次迭代
        .ToList();  // 再次物化!
}

// 好:延迟到最后
public IReadOnlyList<Order> GetActiveOrders()
{
    return _orders
        .Where(o => o.IsActive)
        .OrderBy(o => o.CreatedAt)
        .ToList();  // 单次物化
}

// 好:如果调用者可能不需要所有项,则返回IEnumerable
public IEnumerable<Order> GetActiveOrders()
{
    return _orders
        .Where(o => o.IsActive)
        .OrderBy(o => o.CreatedAt);
    // 调用者决定何时物化
}

异步枚举

小心处理异步和IEnumerable:

// 不好:LINQ中的异步 - 隐藏的分配
var results = orders
    .Select(async o => await ProcessOrderAsync(o))  // 每个项一个Task!
    .ToList();
await Task.WhenAll(results);

// 好:使用IAsyncEnumerable进行流式处理
public async IAsyncEnumerable<OrderResult> ProcessOrdersAsync(
    IEnumerable<Order> orders,
    [EnumeratorCancellation] CancellationToken ct = default)
{
    foreach (var order in orders)
    {
        ct.ThrowIfCancellationRequested();
        yield return await ProcessOrderAsync(order, ct);
    }
}

// 好:用于并行处理的批处理
var results = await Task.WhenAll(
    orders.Select(o => ProcessOrderAsync(o)));

ValueTask 与 Task

对于经常同步完成的热路径,使用 ValueTask。对于真正的I/O,直接使用 Task

// 做:为缓存/同步路径使用ValueTask
public ValueTask<User?> GetUserAsync(UserId id)
{
    if (_cache.TryGetValue(id, out var user))
    {
        return ValueTask.FromResult<User?>(user);  // 无分配
    }

    return new ValueTask<User?>(FetchUserAsync(id));
}

// 做:为真正的I/O使用Task(更简单,无陷阱)
public Task<Order> CreateOrderAsync(CreateOrderCommand cmd)
{
    // 这总是会访问数据库
    return _repository.CreateAsync(cmd);
}

ValueTask规则:

  • 切勿多次等待同一个ValueTask
  • 在完成之前切勿使用 .Result.GetAwaiter().GetResult()
  • 如有疑问,使用Task

为字节使用 Span 和 Memory

对于低级操作,使用 Span<T>Memory<T> 代替 byte[]

// 做:为同步操作接受Span
public static int ParseInt(ReadOnlySpan<char> text)
{
    return int.Parse(text);
}

// 做:为异步操作接受Memory
public async Task WriteAsync(ReadOnlyMemory<byte> data)
{
    await _stream.WriteAsync(data);
}

// 不要:强制数组分配
public static int ParseInt(string text)  // 分配了字符串
{
    return int.Parse(text);
}

常见的Span模式

// 无分配切片
ReadOnlySpan<char> span = "Hello, World!".AsSpan();
var hello = span[..5];  // 无分配

// 为小缓冲区进行栈分配
Span<byte> buffer = stackalloc byte[256];

// 为更大的缓冲区使用ArrayPool
var buffer = ArrayPool<byte>.Shared.Rent(4096);
try
{
    // 使用缓冲区...
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}

集合返回类型

从API返回不可变集合

// 做:返回不可变集合
public IReadOnlyList<Order> GetOrders()
{
    return _orders.ToList();  // 调用者无法修改内部状态
}

// 做:为静态数据使用冻结集合(.NET 8+)
private static readonly FrozenDictionary<string, Handler> _handlers =
    new Dictionary<string, Handler>
    {
        ["create"] = new CreateHandler(),
        ["update"] = new UpdateHandler(),
    }.ToFrozenDictionary();

// 不要:返回可变集合
public List<Order> GetOrders()
{
    return _orders;  // 调用者可以修改!
}

内部可变性是可以的

public IReadOnlyList<OrderItem> BuildOrderItems(Cart cart)
{
    var items = new List<OrderItem>();  // 内部可变

    foreach (var cartItem in cart.Items)
    {
        items.Add(CreateOrderItem(cartItem));
    }

    return items;  // 作为IReadOnlyList返回
}

集合指南

场景 返回类型
API边界 IReadOnlyList<T>, IReadOnlyCollection<T>
静态查找数据 FrozenDictionary<K,V>, FrozenSet<T>
内部构建 List<T>,然后作为只读返回
单个项或无 T?(可空)
零个或多个,惰性 IEnumerable<T>

快速参考

模式 好处
sealed class 去虚拟化,清晰的API
readonly record struct 无防御性复制,值语义
静态纯函数 无vtable,可测试,线程安全
延迟 .ToList() 单次物化
为热路径使用 ValueTask 避免Task分配
为字节使用 Span<T> 栈分配,无复制
返回 IReadOnlyList<T> 不可变的API契约
FrozenDictionary 静态数据的最快查找

反模式

// 不要:没有理由的未密封类
public class OrderService { }  // 密封它!

// 不要:可变结构体
public struct Point { public int X; public int Y; }  // 设为只读

// 不要:可以是静态的实例方法
public int Add(int a, int b) => a + b;  // 设为静态

// 不要:多次调用ToList()
items.Where(...).ToList().OrderBy(...).ToList();  // 最后调用一次ToList

// 不要:从公共API返回List<T>
public List<Order> GetOrders();  // 返回IReadOnlyList<T>

// 不要:为总是异步的操作使用ValueTask
public ValueTask<Order> CreateOrderAsync();  // 直接使用Task

资源