Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

C# 風格指南

明確且一致的程式編寫慣例對每個專案都很重要,Godot 也不例外。

本頁包含了程式風格指南,Godot 的開發者與貢獻者都會遵循。因此,這份指南主要是給想要貢獻 Godot 專案的人。但因為本文所列的慣例與指引也是該語言最廣泛採用的寫法,我們也鼓勵你參考採用,尤其如果你還沒有一份自己的風格指南時。

備註

本文章並非一份涵蓋所有標準程式設計慣例或最佳實踐的完整指南。如果你對本文未涵蓋的部分感到疑慮,請參考更完整的說明文件,例如 C# 編碼慣例Framework 設計指引

語言規範

Godot 目前在引擎與範例程式碼中使用 C# 12.0 版,這是因為 .NET 8.0(目前的基本需求)所支援的版本。因此,在升級到新版本前,應避免混用僅在 C# 13.0 或更新版本才有的語言功能。

更多關於不同 C# 版本功能的資訊,請參考 C# 新功能

格式

通用指引

  • 換行時請使用 LF 字元,不要用 CRLF 或 CR。

  • 除了 csproj 檔案外,每個檔案結尾請用一個 LF 換行字元。

  • 請使用無 BOMUTF-8 編碼。

  • 縮排請使用 4 個空白字元,不要用 Tab 字元(這稱為「軟 Tab」)。

  • 當一行超過 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 };
        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,Private 欄位除外:

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

        public float CurrentSpeed { get; set; }

        protected int HitPoints;

        private void CalculateWeaponDamage()
        {
        }
    }
}

其他所有識別子(如區域變數、方法參數)請用 camelCase。Private 欄位請在前面加底線(_)(但方法與屬性不加,如上所述):

private Vector3 _aimingAt; // Use an `_` 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);

成員變數

如果某個變數只會在一個方法內使用,不要把它宣告成成員變數。這會讓程式碼難以追蹤。請直接在方法內宣告為區域變數。

區域變數

區域變數請盡量在首次使用的附近宣告。這樣能讓程式碼更易閱讀,也不用頻繁捲動去尋找變數的宣告位置。

隱含型別區域變數

建議可以用隱含型別(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;

其他注意事項

  • 請使用明確的存取修飾詞。

  • 請用屬性取代非 private 欄位。

  • 修飾詞的順序應為:public/protected/private/internal/virtual/override/abstract/new/static/readonly

  • 不必要時請避免對成員用完整限定名稱或 this. 前綴。

  • 請移除未使用的 using 陳述式與不必要的括號。

  • 可省略型別的預設初值。

  • 建議使用 null 條件運算子或型別初始化式讓程式碼更精簡。

  • 當數值有可能是不同型別時請用安全轉型,否則可用直接轉型。