关于 The Standard Code CSharp
The Standard Code CSharp 是用于生成、审查、重构和评估 C# 与 .NET 代码的风格技能,规定文件、命名、方法和代码组织惯例。
主要功能
- 01PascalCase 文件命名规范
- 02采用清晰名称并避免缩写
- 03变量声明规则
- 04方法命名及异步后缀规范
- 05方法结构与组织指导
适用场景
- 01按统一规范生成 C# 代码
- 02检查 C# 代码风格问题
- 03重构 .NET 方法和声明
- 04应用命名与文件组织规则
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hassanhabib/ the-standard-skills --skill the-standard-code-csharp安装后在 Claude Code 中输入/the-standard-code-csharp确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
manifest.jsoncontracts/contracts.jsonexamples/bad/example_bad_methods.csexample_bad_classes.csexample_bad_variables.csexamples/good/example_methods.csexample_variables.csexample_classes.csrules/rules.jsonrules.mdtemplates/service_template.csbroker_template.csvalidations/checklist.mdanti-patterns.md详细介绍
The Standard Code CSharp
此技能的内容
此技能是 The Standard 的规范 C# 编码风格与组织规范。 生成、审查、重构或评估 C# 代码时,请使用此技能。
明确覆盖范围
此技能明确涵盖所有提供的 C# 风格规则文件:
- 文件
- 变量
- 方法
- 类和接口
- 注释和文档
它还保留了实现规范中提供的实现配置文件命名约定。
适用场景
将此技能用于任何 C# 或 .NET 代码,包括模型、代理、服务、控制器、测试和支持代码。
强制行为规范
- 严格遵循这些规则。
- 优先考虑明确易读,而非个人偏好。
- 即使团队本地习惯不同,也要采用 Standard 命名规范。
- 避免使用缩写、含糊的名称、冗余包装器和不一致的格式。
- 尽可能将方法保持在细节层级 0。
- 链式调用或较长的声明在理解上变得困难时,应拆分代码。
- 实例化模型时保留属性顺序。
- 私有字段使用
this.。 - 为多维类遵循分部文件约定。
规范规则集
0 文件
以下是 C# 文件的命名约定和命名指导。
0.0 命名
文件名应遵循 PascalCase 约定,并使用文件扩展名 .cs。
应该这样做
Student.cs
也应该这样做
StudentService.cs
不要这样做
student.cs
也不要这样做
studentService.cs
也不要这样做
Student_Service.cs
0.1 分部类文件
分部类文件是包含根文件嵌套类的文件。例如:
- StudentService.cs
- StudentService.Validations.cs
- StudentService.Exceptions.cs
验证和异常都是分部类,用于在多维空间中展示给定类的不同方面。
应该这样做
StudentService.Validations.cs
也应该这样做
StudentService.Validations.Add.cs
不要这样做
StudentServiceValidations.cs
也不要这样做
StudentService_Validations.cs
0. 变量
0.0 命名
变量名应简洁,并能体现其所持有或可能持有的值的性质和数量。
0.0.0 名称
应该这样做
var student = new Student();
不要这样做
var s = new Student();
也不要这样做
var stdnt = new Student();
同样的规则也适用于 lambda 表达式:
应该这样做
students.Where(student => student ... );
不要这样做
students.Where(s => s ... );
这适用于 lambda 参数出现的所有位置,包括测试替身。模拟设置或验证应使用有意义的名称——即其所代表的类型名称,而不是单个字母:
应该这样做
storageBrokerMock.Setup(broker => broker.InsertStudentAsync(student));
不要这样做
storageBrokerMock.Setup(b => b.InsertStudentAsync(student));
0.0.1 复数
应该这样做
var students = new List<Student>();
不要这样做
var studentList = new List<Student>();
0.0.2 带类型的名称
应该这样做
var student = new Student();
不要这样做
var studentModel = new Student();
也不要这样做
var studentObj = new Student();
0.0.3 空值或默认值
如果变量值是其默认值,例如 int 的 0 或字符串的 null,并且你不打算更改该值(例如用于测试),那么名称应标明该值。
应该这样做
Student noStudent = null;
不要这样做
Student student = null;
也应该这样做
int noChangeCount = 0;
但是,不要这样做
int changeCount = 0;
0.1 声明
声明并实例化变量时,应体现变量的直接类型,即使其值稍后才确定。
0.1.0 明确的类型
如果右侧的类型明确,则使用 var 声明变量
应该这样做
var student = new Student();
不要这样做
Student student = new Student();
0.1.1 较明确的类型
如果无法从右侧明确判断返回值类型(但已知该类型),则必须显式声明变量类型。
应该这样做
Student student = GetStudent();
不要这样做
var student = GetStudent();
0.1.2 不明确的类型
如果无法明确判断且未知返回值类型(例如匿名类型),则可以使用 var 作为变量类型。
应该这样做
var student = new
{
Name = "Hassan",
Score = 100
};
0.1.3 单属性类型
声明仅有一个属性的类型时,直接赋值属性。
应该这样做
var inputStudentEvent = new StudentEvent();
inputStudentEvent.Student = inputProcessedStudent;
不要这样做
var inputStudentEvent = new StudentEvent
{
Student = inputProcessedStudent
};
也应该这样做
var studentEvent = new StudentEvent
{
Student = someStudent,
Date = someDate
}
不要这样做
var studentEvent = new StudentEvent();
studentEvent.Student = someStudent;
studentEvent.Date = someDate;
0.2 组织
0.2.0 拆分
如果变量声明超过 120 个字符,请从等号处开始拆分。
应这样做
List<Student> washingtonSchoolsStudentsWithGrades =
await GetAllWashingtonSchoolsStudentsWithTheirGradesAsync();
不应这样做
List<Student> washgintonSchoolsStudentsWithGrades = await GetAllWashingtonSchoolsStudentsWithTheirGradesAsync();
0.2.1 多个声明
占用两行或更多行的声明,前后都应换行,与前后的变量声明分隔开。
应这样做
Student student = GetStudent();
List<Student> washingtonSchoolsStudentsWithGrades =
await GetAllWashingtonSchoolsStudentsWithTheirGradesAsync();
School school = await GetSchoolAsync();
不应这样做
Student student = GetStudent();
List<Student> washgintonSchoolsStudentsWithGrades =
await GetAllWashingtonSchoolsStudentsWithTheirGradesAsync();
School school = await GetSchoolAsync();
此外,只有一行的变量声明之间不应有空行。
应这样做
Student student = GetStudent();
School school = await GetSchoolAsync();
不应这样做
Student student = GetStudent();
School school = await GetSchoolAsync();
1 方法
1.0 命名
方法名应概括方法的用途,保持准确、简短,并能体现操作的同步性。
1.0.0 动词
方法名必须包含动词,以表示其执行的操作。
应这样做
public List<Student> GetStudents()
{
...
}
不应这样做
public List<Student> Students()
{
...
}
1.0.1 异步性
异步方法应在名称后缀中使用 Async,通常适用于返回 Task 或 ValueTask 的方法。
应这样做
public async ValueTask<List<Student>> GetStudentsAsync()
{
...
}
不应这样做
public async ValueTask<List<Student>> GetStudents()
{
...
}
1.0.2 输入参数
输入参数应明确说明将赋值给对象的哪个属性,或用于搜索等操作的用途。
应这样做
public async ValueTask<Student> GetStudentByNameAsync(string studentName)
{
...
}
不应这样做
public async ValueTask<Student> GetStudentByNameAsync(string text)
{
...
}
同样不应这样做
public async ValueTask<Student> GetStudentByNameAsync(string name)
{
...
}
1.0.3 操作参数
如果方法使用特定参数执行操作,请明确指定该参数。
应这样做
public async ValueTask<Student> GetStudentByIdAsync(Guid studentId)
{
...
}
不应这样做
public async ValueTask<Student> GetStudentAsync(Guid studentId)
{
...
}
1.0.4 传递参数
调用方法时,如果输入参数别名与传入变量部分或完全匹配,则无需使用别名;否则必须使用别名指定值。
假设有一个方法:
Student GetStudentByNameAsync(string studentName);
应这样做
string studentName = "Todd";
Student student = await GetStudentByNameAsync(studentName);
同样应这样做
Student student = await GetStudentByNameAsync(studentName: "Todd");
同样应这样做
Student student = await GetStudentByNameAsync(toddName);
不应这样做
Student student = await GetStudentByNameAsync("Todd");
不应这样做
Student student = await GetStudentByNameAsync(todd);
1.1 组织
通常应将多行相同逻辑封装到独立方法中,并始终让方法保持在细节层级 0。
1.1.0 单行与多行方法
1.1.0.0 单行方法
只包含一行代码的方法应使用胖箭头。
应这样做
public List<Student> GetStudents() => this.storageBroker.GetStudents();
不应这样做
public List<Student> Students()
{
return this.storageBroker.GetStudents();
}
如果单行方法超过 120 个字符,则应在胖箭头后换行,并在新行缩进一个制表符。
应这样做
public async ValueTask<List<Student>> GetAllWashingtonSchoolsStudentsAsync() =>
await this.storageBroker.GetStudentsAsync();
不应这样做
public async ValueTask<List<Student>> GetAllWashingtonSchoolsStudentsAsync() => await this.storageBroker.GetStudentsAsync();
1.1.0.1 多行方法
胖箭头(表达式主体)方法的主体必须在箭头下方占一行。只要主体需要超过一行——例如调用链,或调用参数需要跨多行换行——就必须使用带有明确 return 的作用域 { } 主体。方法自身的参数跨行也不适用胖箭头:如果整体无法读作一个箭头加一行内容,就应使用作用域。
应这样做
public Student AddStudent(Student student)
{
ValidateStudent(student);
return this.storageBroker.InsertStudent(student);
}
同样应这样做
public Student AddStudent(Student student)
{
return this.storageBroker.InsertStudent(student)
.WithLogging();
}
同样应这样做
public Student AddStudent(
Student student)
{
return this.storageBroker.InsertStudent(student);
}
不应这样做
public Student AddStudent(Student student) =>
this.storageBroker.InsertStudent(student)
.WithLogging();
同样不应这样做
public Student AddStudent(
Student student) =>
this.storageBroker.InsertStudent(student);
调用参数跨行换行时,其主体也属于多行——应使用作用域,不要使用胖箭头:
不应这样做
private async ValueTask<TResult> PostAsync<TContent, TResult>(
string relativeUrl,
TContent content) =>
await this.apiClient.PostContentAsync<TContent, TResult>(
relativeUrl,
content,
serializationFunction: async value => Serialize(value),
deserializationFunction: async json => Deserialize<TResult>(json));
应这样做
private async ValueTask<TResult> PostAsync<TContent, TResult>(
string relativeUrl,
TContent content)
{
return await this.apiClient.PostContentAsync<TContent, TResult>(
relativeUrl,
content,
serializationFunction: async value => Serialize(value),
deserializationFunction: async json => Deserialize<TResult>(json));
}
一个有意保留的例外:如果胖箭头的主体是一个接收带花括号 Lambda 的单次调用,则保留 Lambda 自身的 { } 作为作用域,因此仍使用胖箭头。这是 TryCatch 服务模式,写法正确:
应这样做
public ValueTask<Student> AddStudentAsync(Student student) =>
TryCatch(async () =>
{
ValidateStudent(student);
return await this.storageBroker.InsertStudentAsync(student);
});
1.1.1 返回值
对于多行方法,方法逻辑与最后的返回行(如果有)之间应换行。
应这样做
public List<Student> GetStudents()
{
StudentsClient studentsApiClient = InitializeStudentApiClient();
return studentsApiClient.GetStudents();
}
不应这样做
public List<Student> GetStudents()
{
StudentsClient studentsApiClient = InitializeStudentApiClient();
return studentsApiClient.GetStudents();
}
1.1.2 多次调用
有多个方法调用时,如果两个调用都少于 120 个字符,则可以堆叠排列;但如果最后一个调用是方法返回,则应另起一行。
应这样做
public List<Student> GetStudents()
{
StudentsClient studentsApiClient = InitializeStudentApiClient();
List<Student> students = studentsApiClient.GetStudents();
return students;
}
不应这样做
public List<Student> GetStudents()
{
StudentsClient studentsApiClient = InitializeStudentApiClient();
List<Student> students = studentsApiClient.GetStudents();
return students;
}
同样应这样做
public async ValueTask<List<Student>> GetStudentsAsync()
{
StudentsClient washingtonSchoolsStudentsApiClient =
await InitializeWashingtonSchoolsStudentsApiClientAsync();
List<Student> students = studentsApiClient.GetStudents();
return students;
}
不应这样做
public async ValueTask<List<Student>> GetStudentsAsync()
{
StudentsClient washingtonSchoolsStudentsApiClient =
await InitializeWashingtonSchoolsStudentsApiClientAsync();
List<Student> students = studentsApiClient.GetStudents();
return students;
}
1.1.3 声明
方法声明不应超过 120 个字符。
应这样做
public async ValueTask<List<Student>> GetAllRegisteredWashgintonSchoolsStudentsAsync(
StudentsQuery studentsQuery)
{
...
}
不应这样做
public async ValueTask<List<Student>> GetAllRegisteredWashgintonSchoolsStudentsAsync(StudentsQuery studentsQuery)
{
...
}
#### 1.1.4 多个参数 如果传递多个参数,且方法调用长度超过 120 个字符,则必须按参数换行,每行 **一个** 参数。 ##### 应该这样做 ```cs List redmondHighStudents = await QueryAllWashingtonStudentsByScoreAndSchoolAsync( MinimumScore: 130, SchoolName: "Redmond High"); ```
不要这样做
List<Student> redmondHighStudents = await QueryAllWashingtonStudentsByScoreAndSchoolAsync(
MinimumScore: 130,SchoolName: "Redmond High");
1.1.5 链式调用(丑化/美化)
某些方法提供了调用其他方法的扩展。例如,可以在 Where() 方法后调用 Select() 方法。如此继续,直到完成整个查询。
我们将采用丑化美化流程。我们会丑化代码,以美化链式方法的视图。以下是一些示例:
应该这样做
students.Where(student => student.Name is "Elbek")
.Select(student => student.Name)
.ToList();
不要这样做
students
.Where(student => student.Name is "Elbek")
.Select(student => student.Name)
.ToList();
第一种方式会强制简化并截断链式调用,因为继续添加调用会让代码变得越来越难看,如下所示:
students.SomeMethod(...)
.SomeOtherMethod(...)
.SomeOtherMethod(...)
.SomeOtherMethod(...)
.SomeOtherMethod(...);
丑化流程会将链拆分成较短的列表,然后再进行处理。第二种方式(不丑化的方式)可能需要额外的认知资源来区分新语句和已有语句,如下所示:
student
.Where(student => student.Name is "Elbek")
.Select(student => student.Name)
.OrderBy(student => student.Name)
.ToList();
ProcessStudents(students);
4 类
4.0 命名
在符合标准的架构中,表示服务或代理的类应在命名约定中体现类的类型,但模型不适用此规则。
4.0.0 模型
应该这样做
class Student {
...
}
不要这样做
class StudentModel {
}
4.0.1 服务
对于包含业务逻辑的类,使用单数形式。
应该这样做
class StudentService {
....
}
不要这样做
class StudentsService{
...
}
也不要这样做
class StudentBusinessLogic {
...
}
也不要这样做
class StudentBL {
...
}
4.0.2 代理
对于作为服务与外部资源之间垫片的类,使用单数形式。
应该这样做
class StudentBroker {
....
}
不要这样做
class StudentsBroker {
...
}
4.0.3 控制器
使用复数形式,以对应诸如 /api/students 这样的端点,通过 RESTful 操作公开逻辑。
应该这样做
class StudentsController {
....
}
不要这样做
class StudentController {
...
}
4.1 字段
字段是在类或结构体中直接声明的任意类型变量。字段是其所属类型的成员。
4.1.0 命名
类字段采用驼峰命名法。
应该这样做
class StudentsController {
private readonly string studentName;
}
不要这样做
class StudentController {
private readonly string StudentName;
}
也不要这样做
class StudentController {
private readonly string _studentName;
}
命名应遵循“变量”部分中提到的相同规则。
4.1.1 引用
引用类的私有字段时,使用 this 关键字,以区分类的私有成员与作用域内的方法级或构造函数级变量。
应该这样做
class StudentsController {
private readonly string studentName;
public StudentsController(string studentName) {
this.studentName = studentName;
}
}
不要这样做
class StudentController {
private readonly string _studentName;
public StudentsController(string studentName) {
_studentName = studentName;
}
}
4.2 实例化
4.2.0 输入参数别名
如果输入变量名与输入别名匹配,则使用这些名称;否则必须使用别名,尤其是传入值时。
应该这样做
int score = 150;
string name = "Josh";
var student = new Student(name, score);
也应该这样做
var student = new Student(name: "Josh", score: 150);
但是不要这样做
var student = new Student("Josh", 150);
也不要这样做
Student student = new (...);
4.2.1 遵循属性顺序
实例化类时,请确保属性赋值顺序与类声明中的属性顺序一致。
应该这样做
public class Student
{
public Guid Id {get; set;}
public string Name {get; set;}
}
var student = new Student
{
Id = Guid.NewGuid(),
Name = "Elbek"
}
也应该这样做
public class Student
{
private readonly Guid id;
private readonly string name;
public Student(Guid id, string name)
{
this.id = id;
this.name = name;
}
}
var student = new Student (id: Guid.NewGuid(), name: "Elbek");
不要这样做
public class Student
{
public Guid Id {get; set;}
public string Name {get; set;}
}
var student = new Student
{
Name = "Elbek",
Id = Guid.NewGuid()
}
也不要这样做
public class Student
{
private readonly Guid id;
private readonly string name;
public Student(string name, Guid id)
{
this.id = id;
this.name = name;
}
}
var student = new Student (id: Guid.NewGuid(), name: "Elbek");
也不要这样做
public class Student
{
private readonly Guid id;
private readonly string name;
public Student(Guid id, string name)
{
this.id = id;
this.name = name;
}
}
var student = new Student (name: "Elbek", id: Guid.NewGuid());
12 注释
12.0 简介
注释只能用于解释代码无法解释的内容,无论代码是否可见。
12.1 版权
标注版权的注释应遵循以下格式:
应该这样做
// ---------------------------------------------------------------
// Copyright (c) Coalition of the Good-Hearted Engineers
// FREE TO USE TO CONNECT THE WORLD
// ---------------------------------------------------------------
不要这样做
//----------------------------------------------------------------
// <copyright file="StudentService.cs" company="OpenSource">
// Copyright (C) Coalition of the Good-Hearted Engineers
// </copyright>
//----------------------------------------------------------------
也不要这样做
/*
* ==============================================================
* Copyright (c) Coalition of the Good-Hearted Engineers
* FREE TO USE TO CONNECT THE WORLD
* ==============================================================
*/
12.2 方法
对于开发时无法访问其代码或执行复杂功能的方法,其文档应包含以下详细信息。
- 用途
- 输入
- 输出
- 副作用
实现配置文件命名补充说明
10. Naming Conventions
| Element | Pattern | Example |
|---|---|---|
| Broker interface | I{Resource}Broker | IStorageBroker, IModernApiBroker, ILoggingBroker |
| Broker class | {Resource}Broker | StorageBroker, ModernApiBroker, LoggingBroker |
| Broker method | {Action}{Entity}Async | InsertLegacyUserAsync, PostPersonAsync |
| Service interface | I{Entity}Service | ILegacyUserService |
| Service class | {Entity}Service | LegacyUserService |
| Service method | Add{Entity}Async | AddLegacyUserAsync |
| Inner exception | {Adjective}{Entity}Exception | NullLegacyUserException |
| Outer exception | {Entity}{Category}Exception | LegacyUserValidationException |
| Test class | {Entity}ServiceTests | LegacyUserServiceTests |
| Test method | Should{Action}Async / ShouldThrow{Exception}On{Action}… | ShouldAddLegacyUserAsync |