modern-csharp-coding-standards by aaronontheweb/dotnet-skills
npx skills add https://github.com/aaronontheweb/dotnet-skills --skill modern-csharp-coding-standards在以下情况下使用此技能:
record 类型和 init 仅属性广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
switchSpan<T> 和 Memory<T>readonly record struct对 DTO、消息、事件和领域实体使用 record 类型。
// 简单的不可变 DTO
public record CustomerDto(string Id, string Name, string Email);
// 在构造函数中进行验证的记录
public record EmailAddress
{
public string Value { get; init; }
public EmailAddress(string value)
{
if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
throw new ArgumentException("Invalid email address", nameof(value));
Value = value;
}
}
// 包含集合的记录 - 使用 IReadOnlyList
public record ShoppingCart(
string CartId,
string CustomerId,
IReadOnlyList<CartItem> Items
)
{
public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}
何时使用 record class 与 record struct:
record class(默认):引用类型,用于具有多个属性的实体、聚合、DTOrecord struct:值类型,用于值对象(参见下一节)值对象应始终为 readonly record struct,以实现性能和值语义。使用显式转换,切勿使用隐式运算符。
public readonly record struct OrderId(string Value)
{
public OrderId(string value) : this(
!string.IsNullOrWhiteSpace(value)
? value
: throw new ArgumentException("OrderId cannot be empty", nameof(value)))
{ }
public override string ToString() => Value;
}
public readonly record struct Money(decimal Amount, string Currency);
public readonly record struct CustomerId(Guid Value)
{
public static CustomerId New() => new(Guid.NewGuid());
}
有关完整示例(包括多值对象、工厂模式和无隐式转换规则),请参阅 value-objects-and-patterns.md。
使用 switch 表达式、属性模式、关系模式和列表模式来编写更简洁的代码。
public decimal CalculateDiscount(Order order) => order switch
{
{ Total: > 1000m } => order.Total * 0.15m,
{ Total: > 500m } => order.Total * 0.10m,
{ Total: > 100m } => order.Total * 0.05m,
_ => 0m
};
有关完整的模式匹配示例,请参阅 value-objects-and-patterns.md。
在项目中启用可为空的引用类型并显式处理 null 值。
// 在 .csproj 中
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// 显式可空性
public string? FindUserName(string userId)
{
var user = _repository.Find(userId);
return user?.Name;
}
// 包含 null 检查的模式匹配
public decimal GetDiscount(Customer? customer) => customer switch
{
null => 0m,
{ IsVip: true } => 0.20m,
{ OrderCount: > 10 } => 0.10m,
_ => 0.05m
};
// 使用 ArgumentNullException.ThrowIfNull 的守卫子句 (C# 11+)
public void ProcessOrder(Order? order)
{
ArgumentNullException.ThrowIfNull(order);
// 在此作用域内,order 现在不可为空
Console.WriteLine(order.Id);
}
避免抽象基类。 使用接口 + 组合。对共享逻辑使用静态辅助方法。对变体使用带有工厂方法的记录。
有关完整示例,请参阅 composition-and-error-handling.md。
// 全程异步 - 始终接受 CancellationToken
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
var order = await _repository.GetAsync(orderId, cancellationToken);
return order;
}
// 对频繁调用、通常是同步的方法使用 ValueTask
public ValueTask<Order?> GetCachedOrderAsync(string orderId, CancellationToken cancellationToken)
{
if (_cache.TryGetValue(orderId, out var order))
return ValueTask.FromResult<Order?>(order);
return GetFromDatabaseAsync(orderId, cancellationToken);
}
// 对流式处理使用 IAsyncEnumerable
public async IAsyncEnumerable<Order> StreamOrdersAsync(
string customerId,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var order in _repository.StreamAllAsync(cancellationToken))
{
if (order.CustomerId == customerId)
yield return order;
}
}
关键规则:
= default 接受 CancellationTokenConfigureAwait(false).Result 或 .Wait())对同步零分配操作使用 Span<T>,对异步操作使用 Memory<T>,对大型临时缓冲区使用 ArrayPool<T>。
有关完整的 Span/Memory 示例和 API 设计部分,请参阅 performance-and-api-design.md。
对于预期错误,使用 Result<T, TError> 而不是异常。仅对意外/系统错误使用异常。
有关完整的 Result 类型实现和使用示例,请参阅 composition-and-error-handling.md。
禁止使用: AutoMapper、Mapster、ExpressMapper。请改用显式映射扩展方法。当确实需要访问私有成员时,使用 UnsafeAccessorAttribute (.NET 8+)。
有关完整指南,请参阅 anti-patterns-and-reflection.md。
// 文件:Domain/Orders/Order.cs
namespace MyApp.Domain.Orders;
// 1. 主要领域类型
public record Order(
OrderId Id,
CustomerId CustomerId,
Money Total,
OrderStatus Status,
IReadOnlyList<OrderItem> Items
)
{
public bool IsCompleted => Status is OrderStatus.Completed;
public Result<Order, OrderError> AddItem(OrderItem item)
{
if (Status is not OrderStatus.Draft)
return Result<Order, OrderError>.Failure(
new OrderError("ORDER_NOT_DRAFT", "Can only add items to draft orders"));
var newItems = Items.Append(item).ToList();
var newTotal = new Money(
Items.Sum(i => i.Total.Amount) + item.Total.Amount,
Total.Currency);
return Result<Order, OrderError>.Success(
this with { Items = newItems, Total = newTotal });
}
}
// 2. 状态枚举
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }
// 3. 相关类型
public record OrderItem(ProductId ProductId, Quantity Quantity, Money UnitPrice)
{
public Money Total => new(UnitPrice.Amount * Quantity.Value, UnitPrice.Currency);
}
// 4. 值对象
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
// 5. 错误
public readonly record struct OrderError(string Code, string Message);
recordreadonly record structswitch 表达式进行模式匹配CancellationTokenSpan<T> 和 Memory<T>IEnumerable<T>、IReadOnlyList<T>)Result<T, TError>ArrayPool<T> 池化缓冲区readonly record struct).Result、.Wait())Span<byte> 足够时,不要使用 byte[]CancellationToken 参数ArrayPool)有关详细的反模式示例,请参阅 anti-patterns-and-reflection.md。
每周安装次数
514
仓库
GitHub 星标数
488
首次出现
2026年1月28日
安全审计
安装于
codex425
claude-code380
opencode283
github-copilot268
gemini-cli267
cursor224
Use this skill when:
record types and init-only propertiesswitch expressions and patterns extensivelySpan<T> and Memory<T> for performance-critical codereadonly record struct for value objectsUse record types for DTOs, messages, events, and domain entities.
// Simple immutable DTO
public record CustomerDto(string Id, string Name, string Email);
// Record with validation in constructor
public record EmailAddress
{
public string Value { get; init; }
public EmailAddress(string value)
{
if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
throw new ArgumentException("Invalid email address", nameof(value));
Value = value;
}
}
// Records with collections - use IReadOnlyList
public record ShoppingCart(
string CartId,
string CustomerId,
IReadOnlyList<CartItem> Items
)
{
public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}
When to userecord class vs record struct:
record class (default): Reference types, use for entities, aggregates, DTOs with multiple propertiesrecord struct: Value types, use for value objects (see next section)Value objects should always bereadonly record struct for performance and value semantics. Use explicit conversions, never implicit operators.
public readonly record struct OrderId(string Value)
{
public OrderId(string value) : this(
!string.IsNullOrWhiteSpace(value)
? value
: throw new ArgumentException("OrderId cannot be empty", nameof(value)))
{ }
public override string ToString() => Value;
}
public readonly record struct Money(decimal Amount, string Currency);
public readonly record struct CustomerId(Guid Value)
{
public static CustomerId New() => new(Guid.NewGuid());
}
See value-objects-and-patterns.md for complete examples including multi-value objects, factory patterns, and the no-implicit-conversion rule.
Use switch expressions, property patterns, relational patterns, and list patterns for cleaner code.
public decimal CalculateDiscount(Order order) => order switch
{
{ Total: > 1000m } => order.Total * 0.15m,
{ Total: > 500m } => order.Total * 0.10m,
{ Total: > 100m } => order.Total * 0.05m,
_ => 0m
};
See value-objects-and-patterns.md for full pattern matching examples.
Enable nullable reference types in your project and handle nulls explicitly.
// In .csproj
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// Explicit nullability
public string? FindUserName(string userId)
{
var user = _repository.Find(userId);
return user?.Name;
}
// Pattern matching with null checks
public decimal GetDiscount(Customer? customer) => customer switch
{
null => 0m,
{ IsVip: true } => 0.20m,
{ OrderCount: > 10 } => 0.10m,
_ => 0.05m
};
// Guard clauses with ArgumentNullException.ThrowIfNull (C# 11+)
public void ProcessOrder(Order? order)
{
ArgumentNullException.ThrowIfNull(order);
// order is now non-nullable in this scope
Console.WriteLine(order.Id);
}
Avoid abstract base classes. Use interfaces + composition. Use static helpers for shared logic. Use records with factory methods for variants.
See composition-and-error-handling.md for full examples.
// Async all the way - always accept CancellationToken
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
var order = await _repository.GetAsync(orderId, cancellationToken);
return order;
}
// ValueTask for frequently-called, often-synchronous methods
public ValueTask<Order?> GetCachedOrderAsync(string orderId, CancellationToken cancellationToken)
{
if (_cache.TryGetValue(orderId, out var order))
return ValueTask.FromResult<Order?>(order);
return GetFromDatabaseAsync(orderId, cancellationToken);
}
// IAsyncEnumerable for streaming
public async IAsyncEnumerable<Order> StreamOrdersAsync(
string customerId,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var order in _repository.StreamAllAsync(cancellationToken))
{
if (order.CustomerId == customerId)
yield return order;
}
}
Key rules:
CancellationToken with = defaultConfigureAwait(false) in library code.Result or .Wait())Use Span<T> for synchronous zero-allocation operations, Memory<T> for async, and ArrayPool<T> for large temporary buffers.
See performance-and-api-design.md for complete Span/Memory examples and the API design section.
For expected errors, use Result<T, TError> instead of exceptions. Use exceptions only for unexpected/system errors.
See composition-and-error-handling.md for the full Result type implementation and usage examples.
Banned: AutoMapper, Mapster, ExpressMapper. Use explicit mapping extension methods instead. Use UnsafeAccessorAttribute (.NET 8+) when you genuinely need private member access.
See anti-patterns-and-reflection.md for full guidance.
// File: Domain/Orders/Order.cs
namespace MyApp.Domain.Orders;
// 1. Primary domain type
public record Order(
OrderId Id,
CustomerId CustomerId,
Money Total,
OrderStatus Status,
IReadOnlyList<OrderItem> Items
)
{
public bool IsCompleted => Status is OrderStatus.Completed;
public Result<Order, OrderError> AddItem(OrderItem item)
{
if (Status is not OrderStatus.Draft)
return Result<Order, OrderError>.Failure(
new OrderError("ORDER_NOT_DRAFT", "Can only add items to draft orders"));
var newItems = Items.Append(item).ToList();
var newTotal = new Money(
Items.Sum(i => i.Total.Amount) + item.Total.Amount,
Total.Currency);
return Result<Order, OrderError>.Success(
this with { Items = newItems, Total = newTotal });
}
}
// 2. Enums for state
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }
// 3. Related types
public record OrderItem(ProductId ProductId, Quantity Quantity, Money UnitPrice)
{
public Money Total => new(UnitPrice.Amount * Quantity.Value, UnitPrice.Currency);
}
// 4. Value objects
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
// 5. Errors
public readonly record struct OrderError(string Code, string Message);
record for DTOs, messages, and domain entitiesreadonly record struct for value objectsswitch expressionsCancellationToken in all async methodsSpan<T> and Memory<T> for high-performance scenariosIEnumerable<T>, IReadOnlyList<T>)Result<T, TError> for expected errorsreadonly record struct).Result, .Wait())byte[] when Span<byte> sufficesCancellationToken parametersArrayPool)See anti-patterns-and-reflection.md for detailed anti-pattern examples.
Weekly Installs
514
Repository
GitHub Stars
488
First Seen
Jan 28, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykPass
Installed on
codex425
claude-code380
opencode283
github-copilot268
gemini-cli267
cursor224
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
136,300 周安装
PRD生成器:AI驱动产品需求文档工具,快速创建清晰可执行PRD
737 周安装
Devcontainer 设置技能:一键创建预配置开发容器,集成 Claude Code 和语言工具
739 周安装
Plankton代码质量工具:Claude Code自动格式化与Linter强制执行系统
741 周安装
ML Pipeline专家指南:生产级机器学习流水线架构、编排与自动化部署
741 周安装
Tavily API 网络搜索技能 - AI 优化搜索,获取结构化实时网络数据
742 周安装
Playwright 开发指南:微软官方自动化测试框架架构、API 与打包教程
745 周安装
ArrayPool<T> for large allocations