type-design-performance by aaronontheweb/dotnet-skills
npx skills add https://github.com/aaronontheweb/dotnet-skills --skill type-design-performance在以下情况下使用此技能:
密封类支持 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) { } // 虚拟方法 = 更慢
}
好处:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
当结构体不可变时,应使用 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);
好处:
// 不应该做:隐藏依赖的实例方法
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。对于真正的 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 规则:
.Result 或 .GetAwaiter().GetResult()对于低级操作,使用 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);
}
// 无分配切片
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);
}
// 应该做:返回不可变集合
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
每周安装数
114
仓库
GitHub 星标数
491
首次出现
2026年1月28日
安全审计
已安装于
claude-code87
codex73
opencode73
github-copilot71
gemini-cli68
kimi-cli65
Use this skill when:
Sealing classes enables JIT devirtualization and communicates API intent.
// 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:
Structs should be readonly when immutable. This prevents defensive copies.
// 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; }
}
| 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 |
Static methods with no side effects are faster and more testable.
// 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
// 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.
Don't materialize enumerables until necessary. Avoid excessive LINQ chains.
// 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
}
Be careful with async and 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)));
Use ValueTask for hot paths that often complete synchronously. For real I/O, just use 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 rules:
.Result or .GetAwaiter().GetResult() before completionUse Span<T> and Memory<T> instead of byte[] for low-level operations.
// 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);
}
// 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);
}
// 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
}
| Scenario | Return 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> |
| Pattern | Benefit |
|---|---|
sealed class | Devirtualization, clear API |
readonly record struct | No defensive copies, value semantics |
| Static pure functions | No 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 |
// 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
Weekly Installs
114
Repository
GitHub Stars
491
First Seen
Jan 28, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code87
codex73
opencode73
github-copilot71
gemini-cli68
kimi-cli65
ESLint迁移到Oxlint完整指南:JavaScript/TypeScript项目性能优化工具
1,600 周安装
tsdown - 基于Rolldown的极速TypeScript/JavaScript库打包工具,支持ESM/CJS/IIFE/UMD
523 周安装
ClawSec 安全套件 - OpenClaw 技能安全监控与恶意防护工具
523 周安装
Vitest 测试框架最佳实践指南:44条性能优化与规则详解
540 周安装
MUI v7 使用指南:组件样式、主题定制与响应式设计模式详解
527 周安装
Google Flights 航班搜索技能:快速查找比价航班时刻表与机票价格
533 周安装
Obsidian Canvas 创建器:一键将文本转换为思维导图和视觉画布 | AI 驱动
554 周安装
| Immutable API contract |
FrozenDictionary | Fastest lookup for static data |