type-design-performance

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Type Design for Performance

面向性能的类型设计

When to Use This Skill

何时使用此技巧

Use this skill when:
  • Designing new types and APIs
  • Reviewing code for performance issues
  • Choosing between class, struct, and record
  • Working with collections and enumerables

在以下场景使用此技巧:
  • 设计新类型和API
  • 审查代码以排查性能问题
  • 在类、结构体和记录类型之间做选择
  • 处理集合和可枚举类型

Core Principles

核心原则

  1. Seal your types - Unless explicitly designed for inheritance
  2. Prefer readonly structs - For small, immutable value types
  3. Prefer static pure functions - Better performance and testability
  4. Defer enumeration - Don't materialize until you need to
  5. Return immutable collections - From API boundaries

  1. 密封你的类型 - 除非明确为继承设计
  2. 优先使用readonly结构体 - 适用于小型、不可变的值类型
  3. 优先选择静态纯函数 - 性能更优且更易测试
  4. 延迟枚举 - 直到需要时再实例化
  5. 返回不可变集合 - 在API边界处

Seal Classes by Default

默认密封类

Sealing classes enables JIT devirtualization and communicates API intent.
csharp
// 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
}
Benefits:
  • JIT can devirtualize method calls
  • Communicates "this is not an extension point"
  • Prevents accidental breaking changes

密封类支持JIT去虚拟化,并能传达API设计意图。
csharp
// 推荐:密封未为继承设计的类
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 Structs for Value Types

为值类型使用Readonly结构体

Structs should be
readonly
when immutable. This prevents defensive copies.
csharp
// 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; }
}
结构体在不可变时应标记为
readonly
,这能避免防御性复制。
csharp
// 推荐:为不可变值类型使用readonly结构体
public readonly record struct OrderId(Guid Value)
{
    public static OrderId New() => new(Guid.NewGuid());
    public override string ToString() => Value.ToString();
}

// 推荐:为小型、短生命周期的数据使用readonly结构体
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  // 未标记readonly!
{
    public int X { get; set; }  // 可变!
    public int Y { get; set; }
}

When to Use Structs

何时使用结构体

Use Struct WhenUse Class When
Small (≤16 bytes typically)Larger objects
Short-livedLong-lived
Frequently allocatedShared references needed
Value semantics requiredIdentity semantics required
ImmutableMutable state

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

Prefer Static Pure Functions

优先选择静态纯函数

Static methods with no side effects are faster and more testable.
csharp
// 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);
Benefits:
  • No vtable lookup (faster)
  • No hidden state
  • Easier to test (pure input → output)
  • Thread-safe by design
  • Forces explicit dependencies
csharp
// 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
    }
}
Don't go overboard - Use instance methods when you genuinely need state or polymorphism.

无副作用的静态方法速度更快且更易测试。
csharp
// 推荐:静态纯函数
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);
优势:
  • 无需虚表查找(速度更快)
  • 无隐藏状态
  • 更易测试(纯输入→输出)
  • 天生线程安全
  • 强制显式依赖
csharp
// 不推荐:隐藏依赖的实例方法
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)
    {
        // 所有输入可见
    }
}
不要过度使用 - 当确实需要状态或多态时,使用实例方法。

Defer Enumeration

延迟枚举

Don't materialize enumerables until necessary. Avoid excessive LINQ chains.
csharp
// 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
}
不要提前实例化可枚举类型,避免过多的LINQ链式调用。
csharp
// 不良实践:提前实例化
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);
    // 由调用方决定何时实例化
}

Async Enumeration

异步枚举

Be careful with async and IEnumerable:
csharp
// 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)));

使用异步和IEnumerable时要注意:
csharp
// 不良实践: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 vs Task

ValueTask vs Task

Use
ValueTask
for hot paths that often complete synchronously. For real I/O, just use
Task
.
csharp
// 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 rules:
  • Never await a ValueTask more than once
  • Never use
    .Result
    or
    .GetAwaiter().GetResult()
    before completion
  • If in doubt, use Task

在经常同步完成的热点路径中使用
ValueTask
。对于真实I/O操作,直接使用
Task
即可。
csharp
// 推荐:在缓存/同步路径中使用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 and Memory for Bytes

用于字节处理的Span和Memory

Use
Span<T>
and
Memory<T>
instead of
byte[]
for low-level operations.
csharp
// 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<T>
Memory<T>
替代
byte[]
csharp
// 推荐:同步操作中接受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);
}

Common Span Patterns

常见Span模式

csharp
// 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);
}

csharp
// 无分配切片
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);
}

Collection Return Types

集合返回类型

Return Immutable Collections from APIs

从API返回不可变集合

csharp
// 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!
}
csharp
// 推荐:返回不可变集合
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;  // 调用方可以修改!
}

Internal Mutation is Fine

内部可变是允许的

csharp
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
}
csharp
public IReadOnlyList<OrderItem> BuildOrderItems(Cart cart)
{
    var items = new List<OrderItem>();  // 内部使用可变集合

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

    return items;  // 以IReadOnlyList类型返回
}

Collection Guidelines

集合使用指南

ScenarioReturn Type
API boundary
IReadOnlyList<T>
,
IReadOnlyCollection<T>
Static lookup data
FrozenDictionary<K,V>
,
FrozenSet<T>
Internal building
List<T>
, then return as readonly
Single item or none
T?
(nullable)
Zero or more, lazy
IEnumerable<T>

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

Quick Reference

快速参考

PatternBenefit
sealed class
Devirtualization, clear API
readonly record struct
No defensive copies, value semantics
Static pure functionsNo vtable, testable, thread-safe
Defer
.ToList()
Single materialization
ValueTask
for hot paths
Avoid Task allocation
Span<T>
for bytes
Stack allocation, no copying
IReadOnlyList<T>
return
Immutable API contract
FrozenDictionary
Fastest lookup for static data

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

Anti-Patterns

反模式

csharp
// 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

csharp
// 不推荐:无理由的未密封类
public class OrderService { }  // 密封它!

// 不推荐:可变结构体
public struct Point { public int X; public int Y; }  // 改为readonly

// 不推荐:可改为静态的实例方法
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

Resources

参考资源