一般的な C# のコード規則

コード標準は、開発チーム内でコードの読みやすさ、一貫性、コラボレーションを維持するために不可欠です。 業界のプラクティスと確立されたガイドラインに従っているコードは、理解、保守、拡張が容易です。 ほとんどのプロジェクトでは、コード規則を通じて一貫したスタイルが適用されます。 dotnet/docs プロジェクトと dotnet/samples プロジェクトも例外ではありません。 この一連の記事では、コーディング規則と、それらを適用するために使用するツールについて説明します。 規則をそのまま使用することも、チームのニーズに合わせて変更することもできます。

次のゴールに基づいて規則を選択しました。

  1. 正確性: サンプルがコピーされ、アプリケーションに貼り付けられます。 そのため、複数の編集を行った後でも、回復性と正確性を備えたコードを作成する必要があります。
  2. 教育: サンプルの目的は、.NET と C# のすべてを教えることです。 そのため、言語機能や API に制限はありません。 代わりに、これらのサンプルでは、機能が適切な選択である場合について説明します。
  3. 整合性: 閲覧者は、コンテンツ全体で一貫性のあるエクスペリエンスを期待します。 すべてのサンプルは同じスタイルに準拠している必要があります。
  4. 導入: 新しい言語機能を使用するようにサンプルを積極的に更新します。 このプラクティスにより、新機能の認識が高くなり、すべての C# 開発者にとってより使い慣れた機能になります。

重要

これらのガイドラインは、サンプルおよびドキュメントを開発するために Microsoft によって使用されます。 これらは、.NET ランタイ、C# コーディング スタイルC# コンパイラ (roslyn) ガイドラインから採用されました。 これらのガイドラインは、数年にわたるオープンソース開発でテストされているため選択されました。 コミュニティ メンバーがランタイム およびコンパイラ プロジェクトに参加するのを支援しました。 これらは一般的な C# の規則の例を示すことが目的であり、正式なリストというわけではありません (詳しくは、「フレームワーク デザインのガイドライン」を参照してください)。

教育導入 の目標は、ドキュメントのコーディング規則がランタイムとコンパイラの規則と異なる理由です。 ランタイムとコンパイラの両方に、ホット パスの厳密なパフォーマンス メトリックがあります。 これは他の多くのアプリケーションにはありません。 私たちの 教育のゴールは、いかなるコンストラクトも禁止しないことを義務付けています。 代わりに、サンプルはコンストラクトを使用する必要があるタイミングを示します。 ほとんどの実稼働アプリケーションよりも積極的にサンプルを更新します。 導入 目標では、昨年記述したコードに変更が必要ない場合でも、今日記述する必要があるコードを示す必要があります。

この記事ではガイドラインについて説明します。 ガイドラインは時間の経過とともに進化しており、Microsoft のガイドラインに従っていないサンプルが見つかります。 これらのサンプルをコンプライアンスに取り込む PR、または更新する必要があるサンプルに注意を引く問題を歓迎します。 Microsoft のガイドラインはオープンソースであり、PR と問題を歓迎します。 ただし、提出によってこれらの推奨事項が変更される場合は、まずディスカッション用の問題を開きます。 ガイドラインを使用することも、ニーズに合わせて調整することもできます。

ツールとアナライザー

ツールは、チームが標準を適用するのに役立ちます。 コード分析を有効にして、必要なルールを適用できます。 Visual Studio でスタイル ガイドラインが自動的に適用されるように、 editorconfig を作成することもできます。 開始点として、dotnet/docs リポジトリのファイルをコピーしてスタイルを使用できます。

これらのツールを使用すると、チームが好みのガイドラインを簡単に採用できます。 Visual Studio では、スコープ内のすべての .editorconfig ファイルに規則が適用され、コードの書式が設定されます。 複数の構成を使用して、企業全体の標準、チーム標準、さらには詳細なプロジェクト標準を適用できます。

有効にされているルールに違反すると、コード分析によって警告と診断が生成されます。 プロジェクトに適用するルールを構成します。 その後、各 CI ビルドは、いずれかの規則に違反したときに開発者に通知します。

診断 ID

言語ガイドライン

以降のセクションでは、コード例とサンプルを準備する際に .NET ドキュメント チームが従っている方法について説明します。 通常は、次のプラクティスに従います。

  • 可能な限り、最新の言語機能と C# バージョンを利用します。
  • 古い言語コンストラクトや期限切れの言語コンストラクトは避けてください。
  • 適切に処理できる例外のみをキャッチします。一般的な例外をキャッチしないようにします。
  • 特定の例外の種類を使用して、意味のあるエラー メッセージを提供します。
  • LINQ クエリとメソッドを使用してコレクション操作を行い、コードの読みやすさを向上させます。
  • I/O バインド操作では、async および await を使用した非同期プログラミングを使用します。
  • デッドロックに注意し、必要に応じて Task.ConfigureAwait を使用してください。
  • ランタイム型の代わりに、データ型の言語キーワードを使用します。 たとえば、 System.String の代わりに stringを使用するか、 System.Int32 の代わりに intを使用します。
  • unsigned 型ではなく int を使用します。 C# では int を使用するのが一般的です。int を使用すると、他のライブラリと対話しやすくなります。 例外は、unsigned データ型に固有のドキュメントに対するものです。
  • var は、閲覧者が式から型を推論できる場合にのみ使用します。 閲覧者は、docs プラットフォームでサンプルを表示します。 変数の種類を表示するホバーヒントやツール ヒントはありません。
  • 明確さとシンプルさを念頭に置いてコードを記述します。
  • 過度に複雑なコード ロジックは避けてください。

より具体的なガイドラインは、次のとおりです。

文字列データ

  • 次のコードに示すように、短い文字列を連結するときは文字列補間を使用します。

    string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
    
  • ループ内で文字列を追加する場合 (特に大量のテキストを処理する場合) は、System.Text.StringBuilder オブジェクトを使用します。

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

配列

  • 宣言行で配列を初期化するときは簡潔な構文を使用します。 次の例では、string[] の代わりに var を使用できません。
string[] vowels1 = { "a", "e", "i", "o", "u" };
  • 明示的なインスタンス化を使用する場合は、var を使用できます。
var vowels2 = new string[] { "a", "e", "i", "o", "u" };

代理人

  • デリゲート型を定義する代わりに Func<>Action<> を使用します。 クラスで、デリゲート メソッドを定義します。
Action<string> actionExample1 = x => Console.WriteLine($"x is: {x}");

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

Func<string, int> funcExample1 = x => Convert.ToInt32(x);

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 double ComputeDistance(double x1, double y1, double x2, double y2)
    {
        try
        {
            return Math.Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
        }
        catch (System.ArithmeticException ex)
        {
            Console.WriteLine($"Arithmetic overflow or underflow: {ex}");
            throw;
        }
    }
    
  • C# の using ステートメントを使用して、コードを簡潔にします。 try-finally ステートメントを使用するときに finally ブロックのコードが Dispose メソッドの呼び出しだけである場合は、using ステートメントを代わりに使用します。

    次の例で、try-finally ステートメントは finally ブロックでのみ Dispose を呼び出します。

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

    using ステートメントを使用して同じことを行うことができます。

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

    中かっこを必要としない新しい using 構文を使用します。

    using Font normalStyle = new Font("Arial", 10.0f);
    byte charset3 = normalStyle.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) is var result)
    {
        Console.WriteLine("Quotient: {0}", result);
    }
    else
    {
        Console.WriteLine("Attempted division by 0 ends up here.");
    }
    

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

new 演算子

  • 次の宣言に示すように、オブジェクトのインスタンス化の簡潔な形式のいずれかを使用します。

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

    上記の宣言は次の宣言と同等です。

    ExampleClass secondExample = new ExampleClass();
    
  • 次の例に示すように、オブジェクト初期化子を使用してオブジェクトの作成を簡略化します。

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

    次の例では、前の例と同じプロパティを設定しますが、初期化子を使用しません。

    var fourthExample = new ExampleClass();
    fourthExample.Name = "Desktop";
    fourthExample.ID = 37414;
    fourthExample.Location = "Redmond";
    fourthExample.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 };
    
  • クエリ変数と範囲変数の宣言で暗黙の型指定を使用します。 LINQ クエリでの暗黙的な型指定に関するこのガイダンスは、 暗黙的に型指定されたローカル変数の一般的な規則をオーバーライドします。 LINQ クエリでは、多くの場合、匿名型を作成するプロジェクションが使用されます。 他のクエリ式では、入れ子になったジェネリック型を使用して結果が作成されます。 暗黙的に型指定された変数は、多くの場合、読みやすくなります。

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

暗黙的に型指定されるローカル変数

  • 変数の型が代入の右側から明らかである場合、ローカル変数の暗黙の型指定を使用します。

    var message = "This is clearly a string.";
    var currentTemperature = 27;
    
  • 代入の右側から型が明らかではない場合、var を使用しないでください。 メソッド名から型が明らかであると想定しないでください。 変数の型は、new 演算子、明示的なキャスト、またはリテラル値への代入の場合に明らかであると見なされます。

    int numberOfIterations = Convert.ToInt32(Console.ReadLine());
    int currentMaximum = ExampleClass.ResultSoFar();
    
  • 変数の型を指定するときに変数名に頼らないでください。 変数名が正しくない場合があります。 代わりに、型を使用して型を指定し、変数名を使用して変数のセマンティック情報を示します。 次の例では、型に string を使用し、 iterations などを使用して、コンソールから読み取られた情報の意味を示す必要があります。

    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();
    
  • LINQ クエリの結果シーケンスには暗黙的な型を使用します。 LINQ のセクションでは、多くの LINQ クエリによって匿名型が生成され、暗黙的な型を使用する必要があることを説明します。 その他のクエリでは、入れ子になったジェネリック型が生成され、 var が読みやすくなります。

    メモ

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

一部のサンプルでは、式の 自然な型 を説明しています。 これらのサンプルでは、コンパイラが自然な型を選択できるように、 var を使用する必要があります。 これらの例はあまり明確ではありませんが、サンプルには var の使用が必要です。 テキストは動作を説明する必要があります。

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

スタイルのガイドライン

通常は、コードサンプルには次の形式を使用します。

  • インデントには 4 つのスペースを使用します。 タブは使用しないでください。
  • コードを一貫して配置して読みやすさを向上させます。
  • ドキュメント (特にモバイル画面) でコードの読みやすさを高めるために、行を 65 文字に制限します。
  • わかりやすくするために、長いステートメントを複数の行に分割します。
  • 中かっこには "Allman" スタイルを使用します。開きかっこと右角かっこは、独自の新しい行です。 中かっこは現在のインデント レベルに合わせて並びます。
  • 必要に応じて、2 項演算子の前に改行を行う必要があります。

コメントのスタイル

  • 簡単な説明には、1 行のコメント (//) を使います。

  • 長い説明の場合は、複数行のコメント (/* */) を避けます。 コメントはローカライズされません。 代わりに、より長い説明がコンパニオン記事に含まれています。

  • メソッド、クラス、フィールド、およびすべてのパブリック メンバーを記述するために、XML コメントを使用します。

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

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

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

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

    // The following declaration creates a query. It does not run
    // the query.
    

レイアウト規則

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

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

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

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

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

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

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

    if ((startX > endX) && (startX > previousX))
    {
        // Take appropriate action.
    }
    

例外は、サンプルで演算子または式の優先順位が説明されている場合です。

セキュリティ

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