type-design-performance
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseType 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
核心原则
- Seal your types - Unless explicitly designed for inheritance
- Prefer readonly structs - For small, immutable value types
- Prefer static pure functions - Better performance and testability
- Defer enumeration - Don't materialize until you need to
- Return immutable collections - From API boundaries
- 密封你的类型 - 除非明确为继承设计
- 优先使用readonly结构体 - 适用于小型、不可变的值类型
- 优先选择静态纯函数 - 性能更优且更易测试
- 延迟枚举 - 直到需要时再实例化
- 返回不可变集合 - 在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 when immutable. This prevents defensive copies.
readonlycsharp
// 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; }
}结构体在不可变时应标记为,这能避免防御性复制。
readonlycsharp
// 推荐:为不可变值类型使用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 When | Use Class When |
|---|---|
| Small (≤16 bytes typically) | Larger objects |
| Short-lived | Long-lived |
| Frequently allocated | Shared references needed |
| Value semantics required | Identity semantics required |
| Immutable | Mutable 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 for hot paths that often complete synchronously. For real I/O, just use .
ValueTaskTaskcsharp
// 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 or
.Resultbefore completion.GetAwaiter().GetResult() - If in doubt, use Task
在经常同步完成的热点路径中使用。对于真实I/O操作,直接使用即可。
ValueTaskTaskcsharp
// 推荐:在缓存/同步路径中使用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 and instead of for low-level operations.
Span<T>Memory<T>byte[]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
集合使用指南
| Scenario | Return Type |
|---|---|
| API boundary | |
| Static lookup data | |
| Internal building | |
| Single item or none | |
| Zero or more, lazy | |
| 场景 | 返回类型 |
|---|---|
| API边界 | |
| 静态查找数据 | |
| 内部构建 | |
| 单个项或无项 | |
| 零个或多个,延迟加载 | |
Quick Reference
快速参考
| Pattern | Benefit |
|---|---|
| Devirtualization, clear API |
| No defensive copies, value semantics |
| Static pure functions | No vtable, testable, thread-safe |
Defer | Single materialization |
| Avoid Task allocation |
| Stack allocation, no copying |
| Immutable API contract |
| Fastest lookup for static data |
| 模式 | 优势 |
|---|---|
| 去虚拟化、清晰的API定义 |
| 无防御性复制、值语义 |
| 静态纯函数 | 无需虚表、易测试、线程安全 |
延迟 | 单次实例化 |
热点路径使用 | 避免Task分配 |
字节处理使用 | 栈分配、无复制 |
返回 | 不可变API契约 |
| 静态数据的最快查找速度 |
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 Taskcsharp
// 不推荐:无理由的未密封类
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(); // 直接使用TaskResources
参考资源
- Performance Best Practices: https://learn.microsoft.com/en-us/dotnet/standard/performance/
- Span<T> Guidance: https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/
- Frozen Collections: https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen