C# のコーディング規則

コーディング規則には、次の目的があります。

  • コードの見た目が統一されるため、コードを読むときに、レイアウトではなく内容に重点を置くことができます。
  • これにより、経験に基づいて推測することで、コードをより迅速に理解することができます。
  • コードのコピー、変更、および保守が容易になります。
  • コーディング規約により、C# のベスト プラクティスがわかります。

重要

この記事のガイドラインは、サンプルおよびドキュメントを開発するために Microsoft によって使用されます。 これらは、.NET ランタイムの C# コーディング スタイル ガイドラインから採用されました。 それらを使用することも、ニーズに合わせて調整することもできます。 主な目的は、プロジェクト、チーム、組織、または会社のソース コード内での一貫性と読みやすさです。

名前付け規則

C# コードを記述するときに考慮する必要のある名前付け規則がいくつかあります。

次の例では、public とマークされた要素に関するすべてのガイダンスが、protectedprotected internal の要素の操作時にも適用されます。これらはすべて、外部の呼び出し元に表示することを意図しています。

パスカル ケース

classrecord、または struct の名前を付ける場合は、パスカル ケース ("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 メンバーに名前を付ける場合は、パスカル ケースを使用します。

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;
        // ...
    }
}

位置指定レコードを書くときは、レコードのパブリック プロパティであるパラメーターにパスカル ケースを使用します。

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

位置指定レコードの詳細については、「プロパティ定義の位置指定構文」を参照してください。

Camel 形式

private または internal のフィールドに名前を付ける場合は、キャメル ケース ("camelCasing") を使用し、_ を使用してプレフィックスを付けます。

public class DataService
{
    private IWorkerQueue _workerQueue;
}

ヒント

ステートメント補完をサポートする IDE でこれらの名前付け規則に従う C# コードを編集するときは、「_」と入力すると、オブジェクト スコープのすべてのメンバーが表示されます。

private または internal である static フィールドを使用する場合は、s_ プレフィックスを使用し、スレッド静的には t_ を使用します。

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 ディレクティブが含まれていない例では、名前空間の修飾を使用します。 プロジェクトに名前空間が既定でインポートされていることがわかっている場合は、その名前空間の各名前を完全修飾する必要はありません。 次の例に示すように、修飾名が長すぎて 1 行に収まらない場合は、ドット (.) の後で改行できます。

    var currentPerformanceCounterCategory = new System.Diagnostics.
        PerformanceCounterCategory();
    
  • 他のガイドラインに合わせて、Visual Studio デザイナーのツールを使用して作成されたオブジェクトの名前を変更する必要はありません。

レイアウト規則

コードの構造を強調する書式が使用され、コードが読みやすくなっているのが、優れたレイアウトです。 マイクロソフトの例とサンプルは、次の規則に準拠しています。

  • コード エディターの既定の設定 (スマート インデント、4 文字インデント、タブを空白として保存) を使用します。 詳細については、「[オプション]、[テキスト エディター]、[C#]、[書式設定]」を参照してください。

  • 1 つの行には 1 つのステートメントのみを記述します。

  • 1 つの行には 1 つの宣言のみを記述します。

  • 継続行にインデントが自動的に設定されない場合は、1 タブ ストップ (4 つの空白) 分のインデントを設定します。

  • メソッド定義とプロパティ定義の間に少なくとも 1 行の空白行を追加します。

  • 次のコードに示すように、式に句を作成するときはかっこを使用します。

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

using ディレクティブを名前空間宣言の外側に配置する

using ディレクティブが名前空間宣言の外側にある場合、インポートされた名前空間はその完全修飾名となります。 これはより明確です。 using ディレクティブが名前空間の内側にある場合、その名前空間に対して相対的であるか、完全修飾名のどちらかである可能性があります。 これはあいまいです。

using Azure;

namespace CoolStuff.AwesomeFeature
{
    public class Awesome
    {
        public void Stuff()
        {
            WaitUntil wait = WaitUntil.Completed;
            …
        }
    }
}

WaitUntil クラスへの参照 (直接的、または間接的) があると仮定します。

ここで、少し変更してみましょう。

namespace CoolStuff.AwesomeFeature
{
    using Azure;
    
    public class Awesome
    {
        public void Stuff()
        {
            WaitUntil wait = WaitUntil.Completed;
            …
        }
    }
}

それは、今日はコンパイルされます。 そして、明日も。 しかし、来週のある日、この (手つかずの) コードは 2 つのエラーで失敗します。

- error CS0246: The type or namespace name 'WaitUntil' could not be found (are you missing a using directive or an assembly reference?)
- error CS0103: The name 'WaitUntil' does not exist in the current context

依存関係の 1 つにより、.Azure で終わる名前空間内の次のクラスが導入されています。

namespace CoolStuff.Azure
{
    public class SecretsManagement
    {
        public string FetchFromKeyVault(string vaultId, string secretId) { return null; }
    }
}

名前空間内に配置された using ディレクティブは状況依存であり、名前解決を複雑にします。 この例では、それは最初に見つかった名前空間です。

  • CoolStuff.AwesomeFeature.Azure
  • CoolStuff.Azure
  • Azure

CoolStuff.AzureCoolStuff.AwesomeFeature.Azure のどちらかに一致する新しい名前空間を追加すると、グローバルな Azure 名前空間の前に一致することになります。 これを解決するには、using 宣言に global:: 修飾子を追加します。 しかし、代わりに、名前空間の外側に using 宣言を配置する方が簡単です。

namespace CoolStuff.AwesomeFeature
{
    using global::Azure;
    
    public class Awesome
    {
        public void Stuff()
        {
            WaitUntil wait = WaitUntil.Completed;
            …
        }
    }
}

コメント規則

  • コメントは、コード行の末尾ではなく別の行に記述します。

  • コメントのテキストは大文字で開始します。

  • コメントのテキストはピリオドで終了します。

  • 次の例に示すように、コメント デリミター (//) とコメント テキストの間に空白を 1 つ挿入します。

    // 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);
    
  • dynamic の代わりに 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();
    

    注意

    反復可能コレクションの要素の型を誤って変更しないように注意してください。 たとえば、foreach ステートメントで System.Linq.IQueryable から System.Collections.IEnumerable に切り替えるのは簡単ですが、これを行うとクエリの結果が変更されます。

unsigned データ型

通常は、unsigned 型ではなく int を使用します。 C# では int を使用するのが一般的です。int を使用すると、他のライブラリと対話しやすくなります。

配列

宣言行で配列を初期化するときは簡潔な構文を使用します。 次の例では、string[] の代わりに var を使用できないことに注意してください。

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;

Func<> または Action<> デリゲートで定義されているシグネチャを使用してメソッドを呼び出します。

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 ステートメントは finally ブロックでのみ Dispose を呼び出します。

    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 ステートメント内の 2 番目の句によって実行時エラーが発生します。 ただし、最初の式が false の場合、&& 演算子はショートサーキットされます。 つまり、2 番目の式は評価されません。 & 演算子は両方を評価し、divisor が 0 の場合は実行時エラーが発生します。

new 演算子

  • 次の宣言に示すように、オブジェクトのインスタンス化の簡潔な形式のいずれかを使用します。 2 番目の例は、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;
    

イベント処理

後で削除する必要のないイベント ハンドラーを定義する場合は、ラムダ式を使用します。

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

ラムダ式では、次の従来の定義を短縮します。

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

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

静的メンバー

静的メンバーは、クラス名 ClassName.StaticMember を使用して呼び出します。 こうすることで、静的アクセスが明確になり、コードがよりわかりやすくなります。 派生クラスの名前を持つ基本クラスに定義された静的メンバーを指定しないでください。 このコードをコンパイルすると、コードが読みやすくなくなり、派生クラスに同じ名前の静的メンバーを追加すると、将来的にコードが中断する場合があります。

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;
    
  • 内部コレクションにアクセスするには、join 句ではなく複数の from 句を使用します。 たとえば、Student オブジェクトのコレクションがあり、各オブジェクトに試験の点数のコレクションが含まれているとします。 次のクエリを実行すると、90 点より高い点数とその点数を取った学生の姓が返されます。

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

セキュリティ

安全なコーディングのガイドライン」のガイドラインに従ってください。

関連項目