C# 风格指南

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

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

注解

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

语言规范

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

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

格式

一般性指导

  • 使用换行符(LF)来换行,而不是 CRLFCR
  • 在每个文件末尾使用一个换行符,但 csproj 文件除外。
  • 使用不带 字节顺序标记(BOM)UTF-8 编码。
  • 使用 4空格 代替制表符进行缩进(称为“软制表符”)。
  • 如果长度超过100个字符,请考虑将其分成几行。

换行符和空白行

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

// Use this style:
if (x > 0)
{
    DoSomething();
}

// NOT this:
if (x > 0) {
    DoSomething();
}

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

  • 对于简单的属性访问者。
  • 对于简单对象、数组、或集合初始化。
  • 对于抽象的自动属性、索引器、或事件声明。
// You may put the brackets in a single line in following cases:
public interface MyInterface
{
    int MyProperty { get; set; }
}

public class MyClass : ParentClass
{
    public int Value
    {
        get { return 0; }
        set
        {
            ArrayValue = new [] {value};
        }
    }
}

插入一个空行:

  • 在一列 using 语句之后。
  • 在方法、属性、和内部类型声明之间。
  • 在每个文件的末尾。

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

避免插入空白行:

  • 在开括号 { 之后。
  • 在闭合括号 } 之前。
  • 在注释块或单行注释之后。
  • 与另一个空白行相邻。
using System;
using Godot;
                                          // Blank line after `using` list.
public class MyClass
{                                         // No blank line after `{`.
    public enum MyEnum
    {
        Value,
        AnotherValue                      // No blank line before `}`.
    }
                                          // Blank line around inner types.
    public const int SomeConstant = 1;
    public const int AnotherConstant = 2;

    private Vector3 _x;                  // Related constants or fields can be
    private Vector3 _y;                  // grouped together.

    private float _width;
    private float _height;

    public int MyProperty { get; set; }
                                          // Blank line around properties.
    public void MyMethod()
    {
        // Some comment.
        AnotherMethod();                  // No blank line after a comment.
    }
                                          // Blank line around methods.
    public void AnotherMethod()
    {
    }
}

使用空格

插入空格:

  • 围绕二元和三元运算符。
  • 在左括号和 ifforforeachcatchwhilelockusing 关键字之间。
  • 在单行访问器块之前和之内。
  • 在单行访问器块中的访问器之间。
  • 在不是在行尾的逗号之后。
  • for 语句中的分号之后。
  • 在单行 case 语句中的冒号之后。
  • 在类型声明中的冒号周围。
  • 围绕一个lambda箭头。
  • 在单行注释符号(//)之后,并且如果在行末使用,则在它之前。

不要使用空格:

  • 在类型转换括号后。
  • 在单行初始化括号内侧。

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

public class MyClass<A, B> : Parent<A, B>
{
    public float MyProperty { get; set; }

    public float AnotherProperty
    {
        get { return MyProperty; }
    }

    public void MyMethod()
    {
        int[] values = {1, 2, 3, 4}; // No space within initializer brackets.
        int sum = 0;

        // Single line comment.
        for (int i = 0; i < values.Length; i++)
        {
            switch (i)
            {
                case 3: return;
                default:
                    sum += i > 2 ? 0 : 1;
                    break;
            }
        }

        i += (int)MyProperty; // No space after a type cast.
    }
}

命名约定

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

namespace ExampleProject
{
    public class PlayerCharacter
    {
        public const float DefaultSpeed = 10f;

        public float CurrentSpeed { get; set; }

        protected int HitPoints;

        private void CalculateWeaponDamage()
        {
        }
    }
}

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

private Vector3 _aimingAt; // Use a `_` prefix for private fields.

private void Attack(float attackStrength)
{
    Enemy targetFound = FindTarget(_aimingAt);

    targetFound?.Hit(attackStrength);
}

首字母缩略词有一个例外情况:它由两个字母组成,例如 UI,应当使用PascalCase的大写字母,否则使用小写字母。

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

public string Id { get; }

public UIManager UI
{
    get { return uiManager; }
}

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

最后,考虑选择描述性名称,如果它影响可读性,请不要尝试过多地缩短描述性名称。

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

FindNearbyEnemy()?.Damage(weaponDamage);

而不是:

FindNode()?.Change(wpnDmg);

Member variables

Don't declare member variables if they are only used locally in a method, as it makes the code more difficult to follow. Instead, declare them as local variables in the method's body.

Local variables

Declare local variables as close as possible to their first use. This makes it easier to follow the code, without having to scroll too much to find where the variable was declared.

隐式类型的局部变量

考虑使用隐式类型化(var)声明局部变量,但是 仅当类型明显时 即可从右侧的赋值推断类型:

// You can use `var` for these cases:

var direction = new Vector2(1, 0);

var value = (int)speed;

var text = "Some value";

for (var i = 0; i < 10; i++)
{
}

// But not for these:

var value = GetValue();

var velocity = direction * 1.5;

// It's generally a better idea to use explicit typing for numeric values, especially with
// the existence of the `real_t` alias in Godot, which can either be double or float
// depending on the build configuration.

var value = 1.5;

其他注意事项

  • 使用显式访问修饰符。
  • 使用属性而代替非私有字段。
  • 按此顺序使用修饰符: public/protected/private/internal/virtual/override/abstract/new/static/readonly
  • 避免在不必要时,为成员使用完全限定的名称或 this. 前缀。
  • 删除未使用的 using 语句和不必要的括号。
  • 考虑省略类型的默认初始值。
  • 考虑使用空条件运算符或类型初始化器来使代码更紧凑。
  • 当值可能会成为另一种不同的类型,请使用安全类型转换,否则使用直接类型转换。