mcpField
T
type-design-performance

type-design-performance

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

关于 type-design-performance

面向 C# 开发者的类型与 API 性能设计技能,涵盖类型选择、枚举处理、异步值、内存和集合边界。

主要功能

  1. 01默认密封类型
  2. 02选择只读结构体
  3. 03使用静态纯函数
  4. 04延迟枚举处理
  5. 05选择异步与内存类型
  6. 06返回只读集合

适用场景

  1. 01设计新的 C# 类型与 API
  2. 02审查代码中的性能问题
  3. 03选择类、结构体或记录
  4. 04处理集合与可枚举对象

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/aaronontheweb/dotnet-skills --skill type-design-performance

安装后在 Claude Code 中输入/type-design-performance确认已加载。

详细介绍

面向性能的类型设计

何时使用此技能

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

  • 设计新的类型和 API
  • 检查代码中的性能问题
  • 在 class、struct 和 record 之间进行选择
  • 使用集合和可枚举对象

核心原则

  1. 默认密封类型 - 除非明确设计为可继承
  2. 优先使用 readonly struct - 适用于小型、不可变的值类型
  3. 优先使用静态纯函数 - 性能更好,也更易测试
  4. 延迟枚举 - 直到确有需要时才物化
  5. 返回不可变集合 - 在 API 边界处返回

默认密封类

密封类可启用 JIT 去虚拟化,并传达 API 意图。

// DO: Seal classes not designed for inheritance
public sealed class OrderProcessor
{
    public void Process(Order order) { }
}

// DO: Seal records (they're classes)
public sealed record OrderCreated(OrderId Id, CustomerId CustomerId);

// DON'T: Leave unsealed without reason
public class OrderProcessor  // Can be subclassed - intentional?
{
    public virtual void Process(Order order) { }  // Virtual = slower
}

优点:

  • JIT 可以对方法调用进行去虚拟化
  • 表明“此处不是扩展点”
  • 防止意外的破坏性更改

值类型使用 readonly struct

如果 struct 不可变,就应将其声明为 readonly。这样可以避免防御性复制。

// DO: Readonly struct for immutable value types
public readonly record struct OrderId(Guid Value)
{
    public static OrderId New() => new(Guid.NewGuid());
    public override string ToString() => Value.ToString();
}

// DO: Readonly struct for small, short-lived data
public readonly struct Money
{
    public decimal Amount { get; }
    public string Currency { get; }

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

// DON'T: Mutable struct (causes defensive copies)
public struct Point  // Not readonly!
{
    public int X { get; set; }  // Mutable!
    public int Y { get; set; }
}
何时使用 Struct
使用 Struct 的情况使用 Class 的情况
较小(通常 ≤16 字节)较大的对象
生命周期较短生命周期较长
频繁分配需要共享引用
需要值语义需要身份语义
不可变可变状态

优先使用静态纯函数

没有副作用的静态方法速度更快,也更易测试。

// DO: Static pure function
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");
    }
}

// Usage - predictable, testable
var total = OrderCalculator.CalculateTotal(items);

优点:

  • 无虚方法表查找(更快)
  • 没有隐藏状态
  • 更易测试(纯粹的输入 → 输出)
  • 天然线程安全
  • 强制显式声明依赖项
// DON'T: Instance method hiding dependencies
public class OrderCalculator
{
    private readonly ITaxService _taxService;  // Hidden dependency
    private readonly IDiscountService _discountService;  // Hidden dependency

    public Money CalculateTotal(IReadOnlyList<OrderItem> items)
    {
        // What does this actually depend on?
    }
}

// BETTER: Explicit dependencies via parameters
public static class OrderCalculator
{
    public static Money CalculateTotal(
        IReadOnlyList<OrderItem> items,
        decimal taxRate,
        decimal discountPercent)
    {
        // All inputs visible
    }
}

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


延迟枚举

除非确有必要,否则不要物化可枚举对象。避免过多的 LINQ 链式调用。

// BAD: Premature materialization
public IReadOnlyList<Order> GetActiveOrders()
{
    return _orders
        .Where(o => o.IsActive)
        .ToList()  // Materialized!
        .OrderBy(o => o.CreatedAt)  // Another iteration
        .ToList();  // Materialized again!
}

// GOOD: Defer until the end
public IReadOnlyList<Order> GetActiveOrders()
{
    return _orders
        .Where(o => o.IsActive)
        .OrderBy(o => o.CreatedAt)
        .ToList();  // Single materialization
}

// GOOD: Return IEnumerable if caller might not need all items
public IEnumerable<Order> GetActiveOrders()
{
    return _orders
        .Where(o => o.IsActive)
        .OrderBy(o => o.CreatedAt);
    // Caller decides when to materialize
}
异步枚举

使用异步操作和 IEnumerable 时要谨慎:

// BAD: Async in LINQ - hidden allocations
var results = orders
    .Select(async o => await ProcessOrderAsync(o))  // Task per item!
    .ToList();
await Task.WhenAll(results);

// GOOD: Use IAsyncEnumerable for streaming
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);
    }
}

// GOOD: Batch processing for parallelism
var results = await Task.WhenAll(
    orders.Select(o => ProcessOrderAsync(o)));

ValueTask 与 Task

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

// DO: ValueTask for cached/synchronous paths
public ValueTask<User?> GetUserAsync(UserId id)
{
    if (_cache.TryGetValue(id, out var user))
    {
        return ValueTask.FromResult<User?>(user);  // No allocation
    }

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

// DO: Task for real I/O (simpler, no footguns)
public Task<Order> CreateOrderAsync(CreateOrderCommand cmd)
{
    // This always hits the database
    return _repository.CreateAsync(cmd);
}

ValueTask 使用规则:

  • 切勿多次 await 同一个 ValueTask
  • 完成之前,切勿使用 .Result 或 .GetAwaiter().GetResult()
  • 如有疑虑,请使用 Task

用于字节操作的 Span 和 Memory

对于低级操作,使用 Span<T> 和 Memory<T>,而不是 byte[]。

// DO: Accept Span for synchronous operations
public static int ParseInt(ReadOnlySpan<char> text)
{
    return int.Parse(text);
}

// DO: Accept Memory for async operations
public async Task WriteAsync(ReadOnlyMemory<byte> data)
{
    await _stream.WriteAsync(data);
}

// DON'T: Force array allocation
public static int ParseInt(string text)  // String allocated
{
    return int.Parse(text);
}
常见的 Span 模式
// Slice without allocation
ReadOnlySpan<char> span = "Hello, World!".AsSpan();
var hello = span[..5];  // No allocation

// Stack allocation for small buffers
Span<byte> buffer = stackalloc byte[256];

// Use ArrayPool for larger buffers
var buffer = ArrayPool<byte>.Shared.Rent(4096);
try
{
    // Use buffer...
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}

集合返回类型

API 返回不可变集合
// DO: Return immutable collection
public IReadOnlyList<Order> GetOrders()
{
    return _orders.ToList();  // Caller can't modify internal state
}

// DO: Use frozen collections for static data (.NET 8+)
private static readonly FrozenDictionary<string, Handler> _handlers =
    new Dictionary<string, Handler>
    {
        ["create"] = new CreateHandler(),
        ["update"] = new UpdateHandler(),
    }.ToFrozenDictionary();

// DON'T: Return mutable collection
public List<Order> GetOrders()
{
    return _orders;  // Caller can modify!
}
内部修改没有问题
public IReadOnlyList<OrderItem> BuildOrderItems(Cart cart)
{
    var items = new List<OrderItem>();  // Mutable internally

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

    return items;  // Return as IReadOnlyList
}
集合使用指南
场景返回类型
API 边界IReadOnlyList<T>、IReadOnlyCollection<T>
静态查找数据FrozenDictionary<K,V>、FrozenSet<T>
内部构建List<T>,然后以只读形式返回
单个项目或无项目T?(可空)
零个或多个,惰性IEnumerable<T>

快速参考

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

反模式

// DON'T: Unsealed class without reason
public class OrderService { }  // Seal it!

// DON'T: Mutable struct
public struct Point { public int X; public int Y; }  // Make readonly

// DON'T: Instance method that could be static
public int Add(int a, int b) => a + b;  // Make static

// DON'T: Multiple ToList() calls
items.Where(...).ToList().OrderBy(...).ToList();  // One ToList at end

// DON'T: Return List<T> from public API
public List<Order> GetOrders();  // Return IReadOnlyList<T>

// DON'T: ValueTask for always-async operations
public ValueTask<Order> CreateOrderAsync();  // Just use Task

资源

评论

支持 Markdown