C# 風格指南

有一個明確定義以及一貫的程式編寫準則對於每個專案來說都很重要,而 Godot 也不例外。

本頁包含了程式編寫風格指南,Godot 的開發者與貢獻者都遵守本指南。因此,本指南旨在為想參與貢獻 Godot 專案的人提供指引,但由於本文介紹的準則與方針都被大量語言使用者所採用,因此我們建議您也考慮遵守,特別是當目前您還未遵守類似的指南時。

備註

本文章並未涵蓋如何遵守標準程式編寫準則與最佳時間。欲對於本文未涵蓋的內容有所疑慮,敬請參考更多全面的說明文件,如 C# 編碼慣例Framework 設計方針

語言規範

Godot 目前在引擎與範例程式碼中使用 7.0 版 C#。因此,在 Godot 改為使用新版本前,應避免混用 C# 7.1 版或更新版本才有的功能。

更多有關 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()
    {
    }
}

使用空白字元

下列情況應插入空白字元:

  • 二元與三元運算子周圍。

  • 左括號與 if, for, foreach, catch, while, lockusing 關鍵字之間。

  • 單行存取子區塊之前與之內。

  • 單行存取子區塊中的存取子之間。

  • 非位於行尾的逗號之後。

  • 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 (如方法、屬性、常數、事件),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 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 。但有個例外,介面 必須 Should 以大寫字母 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 條件運算子或型別初始化設定式來讓程式碼更緊湊。

  • 若數值有可能為不同型別時,使用安全型別轉換,否則就使用直接型別轉換。