C# 风格指南

对于每个项目而言, 拥有定义良好且一致的编码约定非常重要,Godot也不例外.

本页面包含一份编码风格指南,Godot 本身的开发人员和贡献者都遵循该指南。因此,它的目标读者是希望为该项目做出贡献的人员,但是由于本文中提到的约定和规范被该语言用户最广泛采用,所以我们建议你也这样做,尤其是如果你还没有这样的指南。

备注

本文绝不是关于如何遵循标准编码约定或最佳实践的详尽指南。如果您不确定此处未涉及的方面,请参阅更全面的文档,例如 C# 编码约定框架设计规范

语言规范

Godot当前在其引擎和示例源代码中使用 C# 7.0 版本 . 因此, 在我们迁移使用较新版本之前, 必须注意避免混合仅在 C# 7.1 或更高版本中可用的语言功能.

有关不同版本的C#功能的详细信息, 请参阅 C #中的新功能 .

格式

总体规范

  • 使用换行符( LF )来换行, 而不是 CRLFCR.

  • 在每个文件末尾使用一个换行符, 但 csproj 文件除外.

  • 使用不带 字节顺序标记(BOM)UTF-8 编码.

  • 使用 4空格 代替制表符进行缩进(称为 “软制表符”).

  • 如果长度超过100个字符, 请考虑将其分成几行.

换行符和空白行

对于一般缩进规则, 请遵循 Allman 风格, 它建议将与控制语句关联的大括号放在下一行, 缩进到同一级别:

  1. // Use this style:
  2. if (x > 0)
  3. {
  4. DoSomething();
  5. }
  6. // NOT this:
  7. if (x > 0) {
  8. DoSomething();
  9. }

但是, 您可以选择省略括号内的换行符:

  • 对于简单的属性访问者.

  • 对于简单对象, 数组, 或集合初始化.

  • 对于抽象的自动属性, 索引器, 或事件声明.

  1. // You may put the brackets in a single line in following cases:
  2. public interface MyInterface
  3. {
  4. int MyProperty { get; set; }
  5. }
  6. public class MyClass : ParentClass
  7. {
  8. public int Value
  9. {
  10. get { return 0; }
  11. set
  12. {
  13. ArrayValue = new [] {value};
  14. }
  15. }
  16. }

插入一个空行:

  • 在一列 using 语句之后.

  • 在方法, 属性, 和内部类型声明之间.

  • 在每个文件的末尾.

字段声明和常量声明可以根据相关性编组在一起. 在这种情况下, 请考虑在编组之间插入空白行以便于阅读.

避免插入空白行:

  • 在开括号 { 之后。

  • 在闭合括号 } 之前。

  • 在注释块或单行注释之后.

  • 与另一个空白行相邻.

  1. using System;
  2. using Godot;
  3. // Blank line after `using` list.
  4. public class MyClass
  5. { // No blank line after `{`.
  6. public enum MyEnum
  7. {
  8. Value,
  9. AnotherValue // No blank line before `}`.
  10. }
  11. // Blank line around inner types.
  12. public const int SomeConstant = 1;
  13. public const int AnotherConstant = 2;
  14. private Vector3 _x; // Related constants or fields can be
  15. private Vector3 _y; // grouped together.
  16. private float _width;
  17. private float _height;
  18. public int MyProperty { get; set; }
  19. // Blank line around properties.
  20. public void MyMethod()
  21. {
  22. // Some comment.
  23. AnotherMethod(); // No blank line after a comment.
  24. }
  25. // Blank line around methods.
  26. public void AnotherMethod()
  27. {
  28. }
  29. }

使用空格

插入一个空格:

  • 在二元和三元运算符的两侧。

  • 在左括号和 ifforforeachcatchwhilelockusing 关键字之间。

  • 在单行访问器块之前和之内.

  • 在单行访问器块中的访问器之间.

  • 在不是在行尾的逗号之后.

  • for 语句中的分号之后.

  • 在单行 case 语句中的冒号之后.

  • 在类型声明中的冒号周围.

  • 围绕一个lambda箭头.

  • 在单行注释符号(//)之后,并且如果在行末使用,则在它之前。

不要使用空格:

  • 在类型转换括号后.

  • 在单行初始化括号内侧.

下面的示例根据上述的一些约定显示了对空格的正确使用:

  1. public class MyClass<A, B> : Parent<A, B>
  2. {
  3. public float MyProperty { get; set; }
  4. public float AnotherProperty
  5. {
  6. get { return MyProperty; }
  7. }
  8. public void MyMethod()
  9. {
  10. int[] values = {1, 2, 3, 4}; // No space within initializer brackets.
  11. int sum = 0;
  12. // Single line comment.
  13. for (int i = 0; i < values.Length; i++)
  14. {
  15. switch (i)
  16. {
  17. case 3: return;
  18. default:
  19. sum += i > 2 ? 0 : 1;
  20. break;
  21. }
  22. }
  23. i += (int)MyProperty; // No space after a type cast.
  24. }
  25. }

命名约定

对所有命名空间、类型名称、成员级别标识符(即方法、属性、常量、事件)使用 PascalCase,私有字段除外:

  1. namespace ExampleProject
  2. {
  3. public class PlayerCharacter
  4. {
  5. public const float DefaultSpeed = 10f;
  6. public float CurrentSpeed { get; set; }
  7. protected int HitPoints;
  8. private void CalculateWeaponDamage()
  9. {
  10. }
  11. }
  12. }

camelCase 用于所有其他标识符(即局部变量、方法参数),并使用下划线(_)作为私有字段的前缀(但不能用于方法或属性,如上所述):

  1. private Vector3 _aimingAt; // Use a `_` prefix for private fields.
  2. private void Attack(float attackStrength)
  3. {
  4. Enemy targetFound = FindTarget(_aimingAt);
  5. targetFound?.Hit(attackStrength);
  6. }

类似 UI 这种只有两个字母的首字母缩写应特殊处理,使用 PascalCase 时都应写作大写字母,否则都应写作小写字母。

请注意,id 不是首字母缩写,因此应将其视为普通标识符:

  1. public string Id { get; }
  2. public UIManager UI
  3. {
  4. get { return uiManager; }
  5. }

通常不建议将类型名称用作标识符的前缀,例如 string strTextfloat fPower。但是,对于接口来说是个例外,实际上,接口应该在其名称前加上大写字母 I,例如 IInventoryHolderIDamageable

最后,请考虑有意义的名称,请勿对名称进行过度缩写,以免影响可读性。

例如,如果您想编写代码来查找附近的敌人并用武器击中它,请选择:

  1. FindNearbyEnemy()?.Damage(weaponDamage);

而不是:

  1. FindNode()?.Change(wpnDmg);

成员变量

如果变量只在方法中使用, 勿声明其为成员变量, 因为我们难以定位在何处使用了该变量. 相反, 你应该将它们在方法内部定义为局部变量.

局部变量

声明局部变量的位置离首次使用它的位置越近越好. 这让人更容易跟上代码的思路, 而不需要上下翻找该变量的声明位置.

隐式类型的局部变量

考虑使用隐式类型化(var)声明局部变量,但是请只在赋值右侧能够推出该类型时使用:

  1. // You can use `var` for these cases:
  2. var direction = new Vector2(1, 0);
  3. var value = (int)speed;
  4. var text = "Some value";
  5. for (var i = 0; i < 10; i++)
  6. {
  7. }
  8. // But not for these:
  9. var value = GetValue();
  10. var velocity = direction * 1.5;
  11. // It's generally a better idea to use explicit typing for numeric values, especially with
  12. // the existence of the `real_t` alias in Godot, which can either be double or float
  13. // depending on the build configuration.
  14. var value = 1.5;

其他注意事项

  • 使用显式访问修饰符。

  • 使用属性而不是非私有字段。

  • 按此顺序使用修饰符:public/protected/private/internal/virtual/override/abstract/new/static/readonly

  • 避免在不必要时,为成员使用完全限定的名称或 this. 前缀。

  • 删除未使用的 using 语句和不必要的括号。

  • 考虑省略类型的默认初始值。

  • 考虑使用空条件运算符或类型初始化器来使代码更紧凑。

  • 当值可能会成为另一种不同的类型,请使用安全类型转换,否则使用直接类型转换。