关于 csharp
csharp 是编写和修改 C# 12 / .NET 8+ 代码的团队规范,面向开发者,涵盖语言模式、异步与取消、数据访问、依赖注入和配置。
主要功能
- 01将可空警告视为错误
- 02C# 12 语言规范
- 03预期失败的 Result 模式
- 04异步方法传递取消令牌
- 05EF Core 查询与更新实践
- 06经过验证的选项和 HTTP 客户端模式
适用场景
- 01规范新编写的 C# 代码
- 02审查 .NET 8+ 项目变更
- 03指导 EF Core 查询实现
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ alexander-danilenko/ cortex-ai-skills --skill csharp安装后在 Claude Code 中输入/csharp确认已加载。
详细介绍
C# 约定
C# 12 / .NET 8+ 的项目约定。对你编写或修改的代码应用这些约定——除非有人要求,否则不要重写未改动的文件来符合这些约定。
约定
-
<Nullable>enable</Nullable>并将警告视为错误。 只有在警告会阻止构建时,可空注解才有用;忽略警告后,它们就成了装饰,让人误以为哪些引用可以为 null。 -
C# 12 默认做法: 文件范围的命名空间、主构造函数、集合表达式、
required成员。DTO 和值对象使用记录类型——比较数据时需要的是值相等性,而手写的Equals/GetHashCode往往会逐渐不一致。 -
优先使用模式匹配,而非类型测试后再强制转换的链式写法。 对封闭层次结构使用 switch 表达式,编译器可以警告未处理的情况;而
if/else if链在出现新类型时只会静默跳过。 -
预期内的失败使用 Result 模式,真正异常的情况使用异常。 “未找到”和“输入无效”都是常见结果,应体现在返回类型中,以便调用方必须处理;用异常控制流程会让方法签名无法体现这些情况,并且每次发生都要进行一次堆栈展开。封闭的记录类型层次结构可以对各种结果进行穷尽式
switch:public abstract record Result<T> { public sealed record Ok(T Value) : Result<T>; public sealed record Fail(string Error) : Result<T>; } var message = GetUser(id) switch { Result<User>.Ok(var user) => $"Found {user.Name}", Result<User>.Fail(var error) => error, }; -
异步一路贯穿到底。
.Result和.Wait()会在同步上下文中导致死锁,在其他情况下则会占用线程池线程。如果调用链底层必须是异步的,那么顶层也必须是异步的。 -
每个异步公共方法都要接受并传递
CancellationToken。 否则,请求取消后,数据库查询和 HTTP 调用仍会继续运行至完成,而已经离开的调用方仍要承担这部分开销。 -
绝不通过 API 暴露 EF 实体。 使用
Select投影到 DTO——否则你会把未来新增到架构中的字段一并发送出去,而延迟加载还会让序列化引发 N+1 查询风暴。 -
这些 EF 特性引发的是生产事故,而非测试失败: 对每个只读查询使用
AsNoTracking(),因为在大型结果集上进行更改跟踪纯属额外开销;Include多个集合时使用AsSplitQuery(),因为单次联接会以笛卡尔方式成倍增加行数;进行批量更改时使用ExecuteUpdateAsync/ExecuteDeleteAsync(.NET 7+),而不是加载实体后再修改;使用全局HasQueryFilter处理软删除,避免任何查询遗漏标记。 -
当一个接口有多个实现时使用键控服务(.NET 8):使用
AddKeyedScoped<INotifier, EmailNotifier>("email")注册,并通过[FromKeyedServices("email")]解析。它优于使用枚举切换的工厂,因为生命周期仍由容器管理。 -
使用经过验证的强类型配置和
IOptions<T>。configuration["Some:Key"]会在调用处因拼写错误而于运行时失败。绑定选项类并使用ValidateOnStart,则会在启动时失败,并指出具体字段。 -
使用
IHttpClientFactory,绝不要每次调用都new HttpClient()——滞留的 TIME_WAIT 连接导致套接字耗尽是经典故障,而且只会在高负载时显现。 -
只有在性能分析器显示分配开销确实重要时,才使用
Span<T>/Memory<T>和对象池。 它们会让代码变复杂,因此应在有测量依据时才承担这份复杂性,而不是出于原则。