自用C#代码规范
-
在没有代码规范的情况下,一个多人合作的项目中很可能会出现多种代码风格,就像是一栋房子的装修中同时出现中式古典风格、现代简约风格、欧式奢华风格以及亚马逊原始风格。抛开美观不谈,这对项目成员间的协作以及后续维护会造成较大的阻碍,所以统一的代码规范是必要的。
本规范综合参考Godot C# Style Guide、微软的C# Coding Style与Unity Code Style Guide,可作为Godot、Unity、.NET等C#项目代码规范。
文中未特别说明部分优先遵循Godot C# Style Guide,其次遵循微软的C# Coding Style。
格式
本地文件中的换行符在提交到git后应转换为LF,而不是CRLF或CR,一般情况下git默认开启此功能。
使用UTF-8无BOM编码,如果使用Visual Studio需要注意设置。
使用4个空格作为tab键缩进,一般这是Unity、VSCode、Rider的默认设置,Godot需要在
Editor Settings -> Text Editor -> Behavior -> Indent
中修改。花括号换行使用Allman风格,而非K&R风格:
while (x == y) { DoSomething(); DoSomethingElse(); } if (x > 0) { DoSomething(); }
内容只有一行时,可省略花括号:
while (x == y) DoSomething(); if (x > 0) DoSomething();
属性的get/set方法,以及方法体较为简单时,可写成一行:
public interface MyInterface { int MyProperty { get; set; } } public class MyClass : ParentClass { public int Value { get { return 0; } set { ArrayValue = new [] {value}; } } public int Foo() { return GetSomeValue(); } public void Bar() => DoSomething(); }
字段、属性、方法之间的空行不要超过2行。
方法内语句之间的空行不要超过2行。
保持代码紧凑易阅读,不要无意义空行。
命名
基础
C#代码文件使用PascalCase命名,例如
MyClass.cs
。不使用默认命名空间,即必须指定类所在的命名空间,命名空间使用PascalCase。
namespace Game { public class MyClass { } }
C# 10.0以上可使用文件范围的命名空间。
namespace Game; public class MyClass { }
命名空间与文件夹结构尽量保持一致,例如
namespace Game.Combat.Characters
,则其文件夹结构为Game/Combat/Characters
。接口使用PascalCase命名,加“I”前缀。
接口成员的访问修饰符没有要求,其他地方使用显式访问修饰符。
public interface ITouchable { // 接口成员可省略访问修饰符 void Interact(); }
枚举使用PascalCase命名,不加任何前缀。
public enum DrinkType { None, Soft, Hard }
类与结构体使用PascalCase命名,不加任何前缀,基类可用“BaseXxx”命名但不是硬性要求。
public class SomeClass { }
所有常量使用PascalCase命名,无论其访问修饰符是什么,包括局部常量,且不使用任何前缀如“k_”。
public class SomeClass { public const float DefaultSpeed = 10f; private const string LogTag = "MyClass"; }
所有private字段,无论是否为static,均使用camelCase命名,加下划线前缀,除此之外不使用其他前缀如“m_”、“s_”、“t_”等。
public class SomeClass { private static T _instance; private static readonly object _lockObj = new(); private Vector3 _aimingAt; private Vector3 _velocity; }
非private字段使用PascalCase命名。
public class SomeClass { protected int HitPoints; internal int State; public string Name; }
所有属性使用PascalCase命名,无论其访问修饰符是什么。
public class SomeClass { private bool IsAlive => HitPoints > 0; protected float MyProperty { get; set; } public float AnotherProperty { get { return MyProperty; } } }
所有方法使用PascalCase命名。
public class SomeClass { public void MyMethod() { } }
局部变量及方法参数使用camelCase命名,不加任何前缀。局部常量使用PascalCase命名。
public float SomeMethod(float someValue) { const float Increment = 1.2f; var result = someValue + Increment; return result; }
可读性
含有三个字母及以上的缩写词时,遵循当前命名约定,例如“APIHandler”在PascalCase时写作“ApiHandler”,camelCase时写作“apiHandler”。
两个字母的缩写词为特例,例如“UIUtil”在PascalCase时写作“UIUtil”,camelCase时写作“uiUtil”,仅限首字母缩写词,例如“Id”不是首字母缩写。
命名应该尽量表达清晰,尽量达到自解释,缩写不要影响可读性,例如:
FindNearbyEnemy()?.Damage(weaponDamage); √ FindNode()?.Change(wpnDmg); ×
bool
变量不加任何固定的前缀,例如“b”。但推荐使用“is”、“has”等词来表明其含义,例如“isDead”, “isWalking”, "hasDamageMultiplier"。尽量使用动词短语为事件命名,用动词时态区分事件发生的时机。事件不加任何前缀或后缀。
public event Action OpeningDoor; // 开门事件发生之前 public event Action DoorOpened; // 开门事件发生之后
事件的接收方法以“On事件名”命名。
public void OnOpeningDoor() { } public void OnDoorOpened() { }
非特殊情况不使用拼音命名。
避免拼写错误,很多时候拼写错误会造成一些让人一时摸不着头脑的Bug(例如JSON序列化、服务端传值错误等等),建议开启编辑器的拼写检查功能。
示例
namespace MyGame; // 类与结构体使用PascalCase命名,不加任何前缀 public class MyClass<T, R> : Parent<T, R> where T : class, new() { // 所有常量使用PascalCase命名 public const float DefaultSpeed = 10f; private const string LogTag = "MyClass"; // 所有private字段使用camelCase命名,加下划线前缀 // 使用显式访问修饰符,不省略private private static T _instance; private static readonly object _lockObj = new(); private Vector3 _aimingAt; private Vector3 _velocity; // 非private字段使用PascalCase命名 protected int HitPoints; internal int State; public string Name; // 所有属性使用PascalCase命名 private bool IsAlive => HitPoints > 0; protected float MyProperty { get; set; } public float AnotherProperty { get { return MyProperty; } } public static T Instance { get { if (null == _instance) { lock (_lockObj) { _instance ??= new T(); } } return _instance; } } // 所有方法使用PascalCase命名 public void MyMethod() { int[] values = {1, 2, 3, 4}; int sum = 0; for (int i = 0; i < values.Length; i++) { switch (i) { case 3: return; default: sum += i > 2 ? 0 : 1; break; } } i += (int)MyProperty; } // 使用显式访问修饰符,不省略private private int MyMethod2() { return 0; } }