mcpField
T
The Standard Code CSharp

The Standard Code CSharp

github.com/hassanhabib开发2073 次安装13 周前更新
代码仓库

关于 The Standard Code CSharp

The Standard Code CSharp 是用于生成、审查、重构和评估 C# 与 .NET 代码的风格技能,规定文件、命名、方法和代码组织惯例。

主要功能

  1. 01PascalCase 文件命名规范
  2. 02采用清晰名称并避免缩写
  3. 03变量声明规则
  4. 04方法命名及异步后缀规范
  5. 05方法结构与组织指导

适用场景

  1. 01按统一规范生成 C# 代码
  2. 02检查 C# 代码风格问题
  3. 03重构 .NET 方法和声明
  4. 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.json
contracts/
contracts.json
examples/bad/
example_bad_methods.cs
example_bad_classes.cs
example_bad_variables.cs
examples/good/
example_methods.cs
example_variables.cs
example_classes.cs
rules/
rules.json
rules.md
templates/
service_template.cs
broker_template.cs
validations/
checklist.md
anti-patterns.md

详细介绍

The Standard Code CSharp

此技能的内容

此技能是 The Standard 的规范 C# 编码风格与组织规范。 生成、审查、重构或评估 C# 代码时,请使用此技能。

明确覆盖范围

此技能明确涵盖所有提供的 C# 风格规则文件:

  • 文件
  • 变量
  • 方法
  • 类和接口
  • 注释和文档

它还保留了实现规范中提供的实现配置文件命名约定。

适用场景

将此技能用于任何 C# 或 .NET 代码,包括模型、代理、服务、控制器、测试和支持代码。

强制行为规范

  1. 严格遵循这些规则。
  2. 优先考虑明确易读,而非个人偏好。
  3. 即使团队本地习惯不同,也要采用 Standard 命名规范。
  4. 避免使用缩写、含糊的名称、冗余包装器和不一致的格式。
  5. 尽可能将方法保持在细节层级 0。
  6. 链式调用或较长的声明在理解上变得困难时,应拆分代码。
  7. 实例化模型时保留属性顺序。
  8. 私有字段使用 this.。
  9. 为多维类遵循分部文件约定。

规范规则集

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

ElementPatternExample
Broker interfaceI{Resource}BrokerIStorageBroker, IModernApiBroker, ILoggingBroker
Broker class{Resource}BrokerStorageBroker, ModernApiBroker, LoggingBroker
Broker method{Action}{Entity}AsyncInsertLegacyUserAsync, PostPersonAsync
Service interfaceI{Entity}ServiceILegacyUserService
Service class{Entity}ServiceLegacyUserService
Service methodAdd{Entity}AsyncAddLegacyUserAsync
Inner exception{Adjective}{Entity}ExceptionNullLegacyUserException
Outer exception{Entity}{Category}ExceptionLegacyUserValidationException
Test class{Entity}ServiceTestsLegacyUserServiceTests
Test methodShould{Action}Async / ShouldThrow{Exception}On{Action}…ShouldAddLegacyUserAsync

评论

支持 Markdown