C# 編碼慣例

編碼慣例有下列用途:

  • 建立外觀一致的程式碼,讓讀者可以專注於內容,而非版面配置。
  • 讓讀者可以依據先前的經驗做出假設,更快速地了解程式碼。
  • 有助於複製、變更及維護程式碼。
  • 示範 C# 的最佳作法。

重要

本文中的指導方針是由 Microsoft 用來開發範例和檔。 其採用自 .NET 執行時間、C# 編碼樣式 指導方針。 您可以使用它們,或調整它們以符合您的需求。 主要目標是專案、小組、組織或公司原始程式碼內的一致性和可讀性。

命名規範

撰寫 C# 程式碼時,需要考慮幾個命名慣例。

在下列範例中,與標示 public 之元素相關的任何指導方針也適用于使用 protectedprotected internal 元素時,所有指導方針都是供外部呼叫端看見。

Pascal 命名法的大小寫

在為 、 recordstruct 命名 class 時,請使用 pascal 大小寫 (「PascalCasing」) 。

public class DataService
{
}
public record PhysicalAddress(
    string Street,
    string City,
    string StateOrProvince,
    string ZipCode);
public struct ValueCoordinate
{
}

命名 interface 時,除了將名稱 I 加上 前置詞之外,請使用 pascal 大小寫。 這清楚地向取用者指出它是 interface

public interface IWorkerQueue
{
}

為型別的成員命名 public 時,例如欄位、屬性、事件、方法和本機函式,請使用 pascal 大小寫。

public class ExampleEvents
{
    // A public field, these should be used sparingly
    public bool IsValid;

    // An init-only property
    public IWorkerQueue WorkerQueue { get; init; }

    // An event
    public event Action EventProcessing;

    // Method
    public void StartEventProcessing()
    {
        // Local function
        static int CountQueueItems() => WorkerQueue.Count;
        // ...
    }
}

撰寫位置記錄時,請針對參數使用 pascal 大小寫,因為它們是記錄的公用屬性。

public record PhysicalAddress(
    string Street,
    string City,
    string StateOrProvince,
    string ZipCode);

如需位置記錄的詳細資訊,請參閱 屬性定義的位置語法

Camel 大小寫

在命名 privateinternal 欄位時使用 camel 大小寫 (「camelCasing」) ,並以 作為前置詞 _

public class DataService
{
    private IWorkerQueue _workerQueue;
}

提示

在支援語句完成的 IDE 中編輯遵循這些命名慣例的 C# 程式碼時,輸入 _ 會顯示所有物件範圍的成員。

使用 或 的欄位時,請使用 s_ 前置詞和 執行緒靜態使用 t_privateinternalstatic

public class DataService
{
    private static IWorkerQueue s_workerQueue;

    [ThreadStatic]
    private static TimeSpan t_timeSpan;
}

撰寫方法參數時,請使用 camel 大小寫。

public T SomeMethod<T>(int someNumber, bool isValid)
{
}

如需 C# 命名慣例的詳細資訊,請參閱 C# 編碼樣式

其他命名慣例

  • 不包含 using 指示詞的範例,請使用命名空間限定性。 如果您知道專案預設會匯入命名空間,就不需要完整限定該命名空間的名稱。 如果限定的名稱太長無法顯示在同一行,則可以在點 (.) 之後中斷名稱,如下範例所示。

    var currentPerformanceCounterCategory = new System.Diagnostics.
        PerformanceCounterCategory();
    
  • 您不需要變更使用 Visual Studio 設計工具建立的物件名稱,使其符合其他指導方針。

版面配置慣例

好的版面配置使用格式設定,來強調程式碼的結構,並讓程式碼更易於閱讀。 Microsoft 範例遵循以下慣例:

  • 使用預設程式碼編輯器設定 (智慧型縮排、四個字元縮排、定位點儲存為空格)。 如需詳細資訊,請參閱選項、文字編輯器、C#、格式

  • 每行只撰寫一個陳述式。

  • 每行只撰寫一個宣告。

  • 如果連續行不會自動縮排,則縮排一個定位停駐點 (四個空格)。

  • 在方法定義與屬性定義之間新增至少一個空白行。

  • 使用括號清楚分隔運算式中的子句,如下列程式碼所示。

    if ((val1 > val2) && (val1 > val3))
    {
        // Take appropriate action.
    }
    

批註慣例

  • 將註解置於單獨的一行,不在程式碼行結尾處。

  • 以大寫字母開始註解文字。

  • 以句號結束註解文字。

  • 註解分隔符號 (//) 與註解文字之間插入一個空格,如下列範例所示。

    // The following declaration creates a query. It does not run
    // the query.
    
  • 請勿在批註周圍建立星號的格式區塊。

  • 請確定所有公用成員都有必要的 XML 批註,並提供其行為的適當描述。

語言指導方針

下列各節說明 C# 小組在準備程式碼範例時應遵循的作法。

字串資料類型

  • 使用字串內插補點串連短字串,如下列程式碼所示。

    string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
    
  • 若要在迴圈中附加字串,特別是當您使用大量文字時,請使用 StringBuilder 物件。

    var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
    var manyPhrases = new StringBuilder();
    for (var i = 0; i < 10000; i++)
    {
        manyPhrases.Append(phrase);
    }
    //Console.WriteLine("tra" + manyPhrases);
    

隱含型別區域變數

  • 如果區域變數的類型明顯來自指派的右側,或精確類型並不重要,請針對該變數使用隱含類型

    var var1 = "This is clearly a string.";
    var var2 = 27;
    
  • 當指派右側的型別不明顯時,請勿使用 var 。 請勿假設類型已從方法名稱中清除。 如果變數類型是 new 運算子或明確轉換,則會視為清楚。

    int var3 = Convert.ToInt32(Console.ReadLine()); 
    int var4 = ExampleClass.ResultSoFar();
    
  • 請勿依賴變數名稱來指定變數的類型。 有可能會不正確。 在下列範例中,變數名稱 inputInt 會誤導。 它是字串。

    var inputInt = Console.ReadLine();
    Console.WriteLine(inputInt);
    
  • 避免使用 var 取代 dynamic。 當您想要執行時間類型推斷時使用 dynamic 。 如需詳細資訊,請參閱 使用動態 (C# 程式設計手冊)

  • 使用隱含類型來判斷迴圈變數的類型 for

    下列範例在 for 陳述式中使用隱含類型。

    var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
    var manyPhrases = new StringBuilder();
    for (var i = 0; i < 10000; i++)
    {
        manyPhrases.Append(phrase);
    }
    //Console.WriteLine("tra" + manyPhrases);
    
  • 請勿使用隱含類型來判斷迴圈中 foreach 迴圈變數的類型。 在大部分情況下,集合中的專案類型並不明顯。 集合的名稱不應該只依賴它來推斷其元素的類型。

    下列範例會在 語句中使用 foreach 明確輸入。

    foreach (char ch in laugh)
    {
        if (ch == 'h')
            Console.Write("H");
        else
            Console.Write(ch);
    }
    Console.WriteLine();
    

    注意

    請小心不要意外變更可反覆運算集合的元素類型。 例如,從 切換 System.Linq.IQueryableSystem.Collections.IEnumerableforeach 語句很容易,這會變更查詢的執行。

不帶正負號的資料類型

一般而言,請使用 int 而非不帶正負號的類型。 int 的使用在 C# 中非常普遍,當您使用 int 時,可更易於與其他程式庫互動。

陣列

當您在宣告行上初始化陣列時,請使用簡潔的語法。 在下列範例中,請注意,您無法使用 var 而非 string[]

string[] vowels1 = { "a", "e", "i", "o", "u" };

如果您使用明確具現化,您可以使用 var

var vowels2 = new string[] { "a", "e", "i", "o", "u" };

委派

使用Func<>Action<> ,而不是定義委派類型。 在類別中,定義委派方法。

public static Action<string> ActionExample1 = x => Console.WriteLine($"x is: {x}");

public static Action<string, string> ActionExample2 = (x, y) => 
    Console.WriteLine($"x is: {x}, y is {y}");

public static Func<string, int> FuncExample1 = x => Convert.ToInt32(x);

public static Func<int, int, int> FuncExample2 = (x, y) => x + y;

使用 或 Action<> 委派所 Func<> 定義的簽章呼叫 方法。

ActionExample1("string for x");

ActionExample2("string for x", "string for y");

Console.WriteLine($"The value is {FuncExample1("1")}");

Console.WriteLine($"The sum is {FuncExample2(1, 2)}");

如果您建立委派類型的實例,請使用簡潔的語法。 在類別中,定義委派類型和具有相符簽章的方法。

public delegate void Del(string message);

public static void DelMethod(string str)
{
    Console.WriteLine("DelMethod argument: {0}", str);
}

建立委派類型的實例,並加以呼叫。 下列宣告顯示壓縮語法。

Del exampleDel2 = DelMethod;
exampleDel2("Hey");

下列宣告使用完整語法。

Del exampleDel1 = new Del(DelMethod);
exampleDel1("Hey");

try-catch 例外狀況處理中的 和 using 語句

  • 針對大部分例外狀況處理,請使用 try-catch 陳述式。

    static string GetValueFromArray(string[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (System.IndexOutOfRangeException ex)
        {
            Console.WriteLine("Index is out of range: {0}", index);
            throw;
        }
    }
    
  • 使用 C# using 陳述式,可簡化程式碼。 如果您有 try-finally 陳述式,而其中 finally 區塊內唯一的程式碼是 Dispose 方法的呼叫,則請改用 using 陳述式。

    在下列範例中 try-finally ,語句只會呼叫 Dispose 區塊中的 。 finally

    Font font1 = new Font("Arial", 10.0f);
    try
    {
        byte charset = font1.GdiCharSet;
    }
    finally
    {
        if (font1 != null)
        {
            ((IDisposable)font1).Dispose();
        }
    }
    

    您可以使用 語句來執行相同的動作 using

    using (Font font2 = new Font("Arial", 10.0f))
    {
        byte charset2 = font2.GdiCharSet;
    }
    

    使用不需要大括弧的新using 語法

    using Font font3 = new Font("Arial", 10.0f);
    byte charset3 = font3.GdiCharSet;
    

&&|| 運算子

若要略過不必要的比較來避免例外狀況並提升效能,請在執行比較時使用 && 而非 &||| ,如下列範例所示。

Console.Write("Enter a dividend: ");
int dividend = Convert.ToInt32(Console.ReadLine());

Console.Write("Enter a divisor: ");
int divisor = Convert.ToInt32(Console.ReadLine());

if ((divisor != 0) && (dividend / divisor > 0))
{
    Console.WriteLine("Quotient: {0}", dividend / divisor);
}
else
{
    Console.WriteLine("Attempted division by 0 ends up here.");
}

如果除數為 0,語句中的 if 第二個子句會造成執行階段錯誤。 但是當 && 第一個運算式為 false 時,運算子會縮短。 也就是說,它不會評估第二個運算式。 運算子 & 會評估這兩者,當 為 0 時 divisor ,會產生執行階段錯誤。

new 運算子

  • 使用其中一種簡潔的物件具現化形式,如下列宣告所示。 第二個範例顯示從 C# 9 開始可用的語法。

    var instance1 = new ExampleClass();
    
    ExampleClass instance2 = new();
    

    上述宣告相當於下列宣告。

    ExampleClass instance2 = new ExampleClass();
    
  • 使用物件初始化運算式簡化物件建立,如下列範例所示。

    var instance3 = new ExampleClass { Name = "Desktop", ID = 37414,
        Location = "Redmond", Age = 2.3 };
    

    下列範例會設定與上述範例相同的屬性,但不會使用初始化運算式。

    var instance4 = new ExampleClass();
    instance4.Name = "Desktop";
    instance4.ID = 37414;
    instance4.Location = "Redmond";
    instance4.Age = 2.3;
    

事件處理

如果您要定義稍後不需要移除的事件處理常式,請使用 Lambda 運算式。

public Form2()
{
    this.Click += (s, e) =>
        {
            MessageBox.Show(
                ((MouseEventArgs)e).Location.ToString());
        };
}

Lambda 運算式會縮短下列傳統定義。

public Form1()
{
    this.Click += new EventHandler(Form1_Click);
}

void Form1_Click(object? sender, EventArgs e)
{
    MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}

靜態成員

使用類別名稱 ClassName.StaticMember,呼叫 static 成員。 這種作法可讓靜態存取更加清晰,從而讓程式碼更易於閱讀。 請勿使用衍生類別的名稱來限定基類中定義的靜態成員。 編譯該程式碼時,如果將具有相同名稱的靜態成員加入衍生類別,則會破壞程式碼的清楚程度,且程式碼之後可能會在中斷。

LINQ 查詢

  • 請為查詢變數使用有意義的名稱。 下列範例對位於西雅圖的客戶,使用 seattleCustomers

    var seattleCustomers = from customer in customers
                           where customer.City == "Seattle"
                           select customer.Name;
    
  • 使用別名,確保匿名類型的屬性名稱使用 Pascal 大小寫慣例,大寫正確。

    var localDistributors =
        from customer in customers
        join distributor in distributors on customer.City equals distributor.City
        select new { Customer = customer, Distributor = distributor };
    
  • 當結果中的屬性名稱可能會造成混淆時,請重新命名屬性。 例如,如果查詢傳回了客戶名稱與經銷商 ID,但沒有在結果在將它們保留為 NameID,請對它們重新命名,以釐清 Name 是客戶的名稱,而 ID 是經銷商的 ID。

    var localDistributors2 =
        from customer in customers
        join distributor in distributors on customer.City equals distributor.City
        select new { CustomerName = customer.Name, DistributorID = distributor.ID };
    
  • 在查詢變數和範圍變數的宣告中使用隱含類型。

    var seattleCustomers = from customer in customers
                           where customer.City == "Seattle"
                           select customer.Name;
    
  • 對齊 子句底下的 from 查詢子句,如先前範例所示。

  • 在其他查詢子句之前使用 where 子句,以確保稍後的查詢子句會在精簡篩選的資料集上運作。

    var seattleCustomers2 = from customer in customers
                            where customer.City == "Seattle"
                            orderby customer.Name
                            select customer;
    
  • 使用多個 from 子句而非 join 子句來存取內部集合。 例如,Student 物件的集合可能每一個都包含測驗分數的集合。 執行下列查詢時,會傳回每個超過 90 的分數,以及取得該分數的學生姓氏。

    var scoreQuery = from student in students
                     from score in student.Scores!
                     where score > 90
                     select new { Last = student.LastName, score };
    

安全性

請遵循安全程式碼撰寫方針中的指引。

另請參閱