コーディング ガイドライン — MRTK2

このドキュメントでは、MRTK にコードを提供するときに従うべきコーディングの原則と規則について簡単に取り上げています。


基本方針

簡潔でわかりやすくする

最もシンプルな解決策がベストであることは少なくありません。 これらのガイドラインにおいて、それは最も優先される目標であり、あらゆるコーディング作業が目指すべき指針です。 簡潔であること、そして既存のコードとの一貫性があることが、シンプルであることの重要な要素になります。 コードはできるだけシンプルに保つようにしてください。

有益な情報を提供する成果物以外は、読み手に見せるべきではありません。 たとえば、明らかな事柄を繰り返し記述しても、特別な情報は得られないばかりか、注目に値しない情報が相対的に増えてしまいます。

コードのロジックには、シンプルさを保ちます。 ここで述べているのは、行数を極力少なくしたり、識別子名の文字数を最小限にしたり、ブレース スタイルを適用したりすることではなく、概念の数を減らし、慣れ親しまれているパターンを通してそれらを最大限に可視化する、ということである点に注意してください。

一貫した読みやすいコードを書く

コードの読みやすさと欠陥率の低さには相関性があります。 読みやすいコードを作成するよう心がけてください。 できるだけロジックをシンプルにし、既存のコンポーネントを再利用するようにします。それが正確さを担保することにもつながります。

正確さという最も基本的な事柄から一貫したスタイルと書式に至るまで、皆さんが作り出すコードの細部がすべて重要です。 たとえ自分の好みに合わなくても、コーディング スタイルは常に既存のスタイルに合わせるようにしてください。 そうすることでコードベース全体の可読性が向上します。

エディター使用時と実行時のどちらでもコンポーネントを構成できるようにする

MRTK は多様なユーザーに対応しています。たとえば、Unity エディターでコンポーネントを構成してプレハブをロードすることを好む人もいれば、実行時にオブジェクトをインスタンス化して構成しなければならない人もいます。

保存されたシーンの中で GameObject にコンポーネントを追加する場合であれ、コード内でそのコンポーネントをインスタンス化する場合であれ、すべてのコードが正しく動作する必要があります。 テストには、プレハブをインスタンス化する場合と実行時にコンポーネントをインスタンス化して構成する場合との両方のテスト ケースが含まれている必要があります。

Play-In-Editor が、最初にクリアすべき主要なターゲット プラットフォーム

Unity でイテレーションを行う最速の手段は Play-In-Editor です。 イテレーションをすばやく行う手段が備わっているため、ユーザーは、ソリューションを迅速に開発できるだけでなく、より多くのアイデアを試すことができます。 つまり、イテレーションの速度を最大化することが、開発者が実現できることを増やす原動力になっています。

まず、すべての機能がエディターで動作するようにし、その後、他のプラットフォームでも動作するようにしてください。 エディターで正しく動作する状態を保ちます。 Play-In-Editor に新しいプラットフォームを追加するのは簡単です。 デバイスでしかアプリが動かないと、Play-In-Editor を動かすのはきわめて困難です。

新しいパブリック フィールド、プロパティ、メソッド、シリアル化したプライベート フィールドは慎重に追加する

パブリックのメソッド、フィールド、プロパティを追加すると、そのたびに、それが MRTK のパブリック API サーフェスに追加されます。 [SerializeField] でマークされたプライベート フィールドもやはり、エディターに公開され、パブリック API サーフェスに追加されます。 そのパブリック メソッドは、他のだれかが使用するかもしれません。そのパブリック フィールドを使用してカスタム プレハブを構成したり、そのパブリック フィールドへの依存関係を設定したりする可能性があります。

新しいパブリック メンバーは、慎重に吟味する必要があります。 パブリック フィールドは、将来にわたって保守する必要があります。 パブリック フィールド (またはシリアル化されたプライベート フィールド) の型が変更されたり MonoBehaviour から削除されたりした場合、他の人々に支障が生じる可能性があることを忘れないでください。 リリースにあたっては、まずこのフィールドを非推奨にし、そのフィールドに依存するコードの作成者のために、変更を移行するためのコードを提供する必要があります。

テストの作成を優先する

MRTK はコミュニティ プロジェクトであり、さまざまな共同作成者によって変更が加えられます。 共同作成者は、皆さんのバグ修正や機能の詳細を知らないので、皆さんが担当している機能を誤って壊してしまう可能性があります。 プル要求を完了する前に、MRTK によって継続的インテグレーション テストが実行されます。 テストを通過しない変更はチェックインできません。 そのため、自分の機能が他の作成者によって壊されないようにする最善の方法はテストです。

バグを修正するときは、その後の改悪につながらないようテストを作成します。 機能を追加する場合は、目的の機能が正しく動作することを確認するテストを作成します。 これは、試験的な機能を除くすべての UX 機能で必要になります。

C# のコーディング規則

スクリプトのライセンス情報ヘッダー

新しいファイルを提供する Microsoft の従業員はすべて、新しいファイルの上部に、次のスタンダードライセンス ヘッダーを追加する必要があります。以下の例に忠実に従ってください。

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

関数またはメソッドのサマリー ヘッダー

MRTK に投稿されるすべてのパブリック クラス、構造体、列挙型、関数、プロパティ、フィールドは、次に示すように、その目的と用途について説明する必要があります。

/// <summary>
/// The Controller definition defines the Controller as defined by the SDK / Unity.
/// </summary>
public struct Controller
{
    /// <summary>
    /// The ID assigned to the Controller
    /// </summary>
    public string ID;
}

こうすることで、すべてのクラス、メソッド、プロパティのドキュメントが適切に生成されて提供されます。

適切な summary タグなしで送信されたスクリプト ファイルはすべて拒否されます。

MRTK の名前空間の規則

Mixed Reality Toolkit では、機能ベースの名前空間モデルが使用され、その基礎を成す名前空間はすべて "Microsoft.MixedReality.Toolkit" で始まります。 一般に、ツールキット レイヤー (Core、Providers、Services など) を名前空間に指定する必要はありません。

現在定義されている名前空間は次のとおりです。

  • Microsoft.MixedReality.Toolkit
  • Microsoft.MixedReality.Toolkit.Boundary
  • Microsoft.MixedReality.Toolkit.Diagnostics
  • Microsoft.MixedReality.Toolkit.Editor
  • Microsoft.MixedReality.Toolkit.Input
  • Microsoft.MixedReality.Toolkit.SpatialAwareness
  • Microsoft.MixedReality.Toolkit.Teleport
  • Microsoft.MixedReality.Toolkit.Utilities

多数の型が名前空間に存在する場合は、範囲を絞り込みやすくするために、限られた数のサブ名前空間を作成してもかまいません。

インターフェイス、クラス、データ型の名前空間を省略すると、変更がブロックされる原因となります。

新しい MonoBehaviour スクリプトを追加する

プル要求で新しい MonoBehaviour スクリプトを追加するときは、該当するすべてのファイルに AddComponentMenu 属性を適用してください。 そうすることで、エディターの [コンポーネントの追加] ボタンの下からコンポーネントが見つけやすくなります。 抽象クラスなど、エディターに表示されないコンポーネントであれば、この属性フラグは必要ありません。

以下の例の Package here は、コンポーネントのパッケージの場所に置き換える必要があります。 MRTK/SDK フォルダーに項目を配置する場合、パッケージは SDK になります。

[AddComponentMenu("Scripts/MRTK/{Package here}/MyNewComponent")]
public class MyNewComponent : MonoBehaviour

新しい Unity インスペクター スクリプトを追加する

一般に、MRTK コンポーネントのカスタム インスペクター スクリプトを作成することは避けてください。 Unity エンジンが担うコードベースのオーバーヘッドと管理が増大する可能性があります。

インスペクター クラスが必要な場合は、Unity の DrawDefaultInspector() の使用を検討してください。 インスペクター クラスを単純化することにもなり、作業の多くが Unity によって処理されます。

public override void OnInspectorGUI()
{
    // Do some custom calculations or checks
    // ....
    DrawDefaultInspector();
}

インスペクター クラスでカスタム レンダリングが必要な場合は、SerializedPropertyEditorGUILayout.PropertyField の利用を検討してください。 入れ子になったプレハブや変更された値のレンダリングが、Unity によって正しく処理されます。

カスタム ロジックの要件上、EditorGUILayout.PropertyField を使用できない場合は、使用範囲全体を EditorGUI.PropertyScope でラップしてください。 そうすることで、入れ子になったプレハブや変更された値のインスペクターが、指定されたプロパティを使用して正しくレンダリングされます。

さらに、カスタム インスペクター クラスは、CanEditMultipleObjects で修飾するようにします。 このタグによって、シーン内のこのコンポーネントを含む複数のオブジェクトを選択し、まとめて変更できるようになります。 新しいインスペクター クラスのコードが、シーン内のこの状況で正しく動作することをテストする必要があります。

    // Example inspector class demonstrating usage of SerializedProperty & EditorGUILayout.PropertyField
    // as well as use of EditorGUI.PropertyScope for custom property logic
    [CustomEditor(typeof(MyComponent))]
    public class MyComponentInspector : UnityEditor.Editor
    {
        private SerializedProperty myProperty;
        private SerializedProperty handedness;

        protected virtual void OnEnable()
        {
            myProperty = serializedObject.FindProperty("myProperty");
            handedness = serializedObject.FindProperty("handedness");
        }

        public override void OnInspectorGUI()
        {
            EditorGUILayout.PropertyField(destroyOnSourceLost);

            Rect position = EditorGUILayout.GetControlRect();
            var label = new GUIContent(handedness.displayName);
            using (new EditorGUI.PropertyScope(position, label, handedness))
            {
                var currentHandedness = (Handedness)handedness.enumValueIndex;

                handedness.enumValueIndex = (int)(Handedness)EditorGUI.EnumPopup(
                    position,
                    label,
                    currentHandedness,
                    (value) => {
                        // This function is executed by Unity to determine if a possible enum value
                        // is valid for selection in the editor view
                        // In this case, only Handedness.Left and Handedness.Right can be selected
                        return (Handedness)value == Handedness.Left
                        || (Handedness)value == Handedness.Right;
                    });
            }
        }
    }

新しい ScriptableObject を追加する

新しい ScriptableObject スクリプトを追加するときは、該当するすべてのファイルに CreateAssetMenu 属性を適用してください。 そうすることで、エディターのアセット作成メニューを介して、コンポーネントを容易に見つけることができます。 抽象クラスなど、エディターに表示されないコンポーネントであれば、この属性フラグは必要ありません。

以下の例の Subfolder は、MRTK のサブフォルダーに置き換える必要があります (該当する場合)。 MRTK/Providers フォルダーに項目を配置する場合、パッケージは Providers になります。 MRTK/Core フォルダーに項目を配置する場合は、これを "Profiles" に設定します。

以下の例の MyNewService | MyNewProvider は、新しいクラスの名前に置き換える必要があります (該当する場合)。 MixedRealityToolkit フォルダーに項目を配置する場合は、この文字列を省略してください。

[CreateAssetMenu(fileName = "MyNewProfile", menuName = "Mixed Reality Toolkit/{Subfolder}/{MyNewService | MyNewProvider}/MyNewProfile")]
public class MyNewProfile : ScriptableObject

ログ記録

新しい機能を追加するときや既存の機能を更新するときは、対象コードに DebugUtilities.LogVerbose ログを追加することを検討してください。将来のデバッグに役立つ可能性があります。 ログの追加にはトレードオフが存在し、ログの量を多くすればノイズが増え、逆に少なくすれば診断が難しくなります。

ログが役立ち、また有益なペイロードが得られる興味深い例があります。

DebugUtilities.LogVerboseFormat("RaiseSourceDetected: Source ID: {0}, Source Type: {1}", source.SourceId, source.SourceType);

このタイプのログを使用すると、https://github.com/microsoft/MixedRealityToolkit-Unity/issues/8016 のような問題を捕捉できます。この問題は、Source Detected イベントと Source Lost イベントのミスマッチが原因でした。

すべてのフレームで発生するデータとイベントを対象にログを追加することは避けてください。個別のユーザー入力によって引き起こされた "関心のある" イベントをログの対象にするのが理想的です。たとえば、特定のユーザーによる "クリック" や、そこから生じる一連の変化とイベントはログに記録する価値があります。 "ユーザーのジェスチャがまだ続いている" という継続する状態をすべてのフレームでログに記録するのは意味がないうえに、膨大なログが記録されてしまいます。

この詳細ログは、既定では無効になっていることに注意してください。[診断システム] の設定で有効にする必要があります。

スペースとタブ

このプロジェクトの共同作成者としてコードを提供する際は、タブではなく 4 つのスペースを使用してください。

間隔

角かっこと丸かっこの間に余分なスペースは追加しないでください。

次のことは行わないでください

private Foo()
{
    int[ ] var = new int [ 9 ];
    Vector2 vector = new Vector2 ( 0f, 10f );
}

次のことを行います

private Foo()
{
    int[] var = new int[9];
    Vector2 vector = new Vector2(0f, 10f);
}

名前付け規則

プロパティには常に PascalCase を使用してください。 static readonly フィールドと const フィールド (PascalCase を使用します) を除く、ほとんどのフィールドには camelCase を使用します。 フィールドを JsonUtility でシリアル化する必要のあるデータ構造体の場合は、その限りではありません。

次のことは行わないでください

public string myProperty; // <- Starts with a lowercase letter
private string MyField; // <- Starts with an uppercase letter

次のことを行います

public string MyProperty;
protected string MyProperty;
private static readonly string MyField;
private string myField;

アクセス修飾子

すべてのフィールド、プロパティ、メソッドには必ずアクセス修飾子を宣言してください。

  • Unity の API メソッドは、既定ではすべて private にする必要があります。ただし、派生クラスでオーバーライドする必要がある場合は例外です。 その場合は、protected を使用する必要があります。

  • フィールドは常に private とし、プロパティ アクセサーは public または protected とする必要があります。

  • 可能な限り、式形式メンバー自動プロパティを使用します。

次のことは行わないでください

// protected field should be private
protected int myVariable = 0;

// property should have protected setter
public int MyVariable => myVariable;

// No public / private access modifiers
void Foo() { }
void Bar() { }

次のことを行います

public int MyVariable { get; protected set; } = 0;

private void Foo() { }
public void Bar() { }
protected virtual void FooBar() { }

中かっこを使用する

各ステートメント ブロックの後には必ず中かっこを使用します。中かっこは次の行に配置してください。

非推奨

private Foo()
{
    if (Bar==null) // <- missing braces surrounding if action
        DoThing();
    else
        DoTheOtherThing();
}

次のことは行わないでください

private Foo() { // <- Open bracket on same line
    if (Bar==null) DoThing(); <- if action on same line with no surrounding brackets
    else DoTheOtherThing();
}

次のことを行います

private Foo()
{
    if (Bar==true)
    {
        DoThing();
    }
    else
    {
        DoTheOtherThing();
    }
}

パブリックのクラス、構造体、列挙型はすべて専用のファイルに記述する

クラス、構造体、列挙型をプライベートにできるのであれば、同じファイルに含めてもかまいません。 Unity に関するコンパイルの問題の防止につながり、コードの抽象化が適切に行われます。また、コードに変更が生じたときでも、競合や破壊的変更が少なくなります。

次のことは行わないでください

public class MyClass
{
    public struct MyStruct() { }
    public enum MyEnumType() { }
    public class MyNestedClass() { }
}

次のことを行います

 // Private references for use inside the class only
public class MyClass
{
    private struct MyStruct() { }
    private enum MyEnumType() { }
    private class MyNestedClass() { }
}

推奨

MyStruct.cs

// Public Struct / Enum definitions for use in your class.  Try to make them generic for reuse.
public struct MyStruct
{
    public string Var1;
    public string Var2;
}

MyEnumType.cs

public enum MuEnumType
{
    Value1,
    Value2 // <- note, no "," on last value to denote end of list.
}

MyClass.cs

public class MyClass
{
    private MyStruct myStructReference;
    private MyEnumType myEnumReference;
}

列挙型を初期化する

すべての列挙型を 0 から始まるよう正しく初期化するために、.NET には、整然としたショートカットが用意されています。列挙型は、1 番目の (スターター) 値さえ追加すれば、後は自動的に初期化されるようになっています。 たとえば、「Value 1 = 0」とだけ指定すれば、他の値を指定する必要はありません。

次のことは行わないでください

public enum Value
{
    Value1, <- no initializer
    Value2,
    Value3
}

次のことを行います

public enum ValueType
{
    Value1 = 0,
    Value2,
    Value3
}

将来の拡張に備えて列挙値の順序を考慮する

列挙型を将来拡張する可能性がある場合、既定値を列挙型の先頭に置くことがきわめて重要です。そうすれば、新たに値が追加されても、列挙型のインデックスが影響を受けることはありません。

次のことは行わないでください

public enum SDKType
{
    WindowsMR,
    OpenVR,
    OpenXR,
    None, <- default value not at start
    Other <- anonymous value left to end of enum
}

次のことを行います

/// <summary>
/// The SDKType lists the VR SDKs that are supported by the MRTK
/// Initially, this lists proposed SDKs, not all may be implemented at this time (please see ReleaseNotes for more details)
/// </summary>
public enum SDKType
{
    /// <summary>
    /// No specified type or Standalone / non-VR type
    /// </summary>
    None = 0,
    /// <summary>
    /// Undefined SDK.
    /// </summary>
    Other,
    /// <summary>
    /// The Windows 10 Mixed reality SDK provided by the Universal Windows Platform (UWP), for Immersive MR headsets and HoloLens.
    /// </summary>
    WindowsMR,
    /// <summary>
    /// The OpenVR platform provided by Unity (does not support the downloadable SteamVR SDK).
    /// </summary>
    OpenVR,
    /// <summary>
    /// The OpenXR platform. SDK to be determined once released.
    /// </summary>
    OpenXR
}

ビットフィールドの列挙型の使用法を確認する

列挙型が値として複数の状態を必要とする可能性がある場合 (例: Handedness = Left & Right)。 列挙型を正しく使用できるようビットフラグで正しく修飾する必要があります。

その具体的な実装を含んだ Handedness.cs ファイルを次に示します。

次のことは行わないでください

public enum Handedness
{
    None,
    Left,
    Right
}

次のことを行います

[Flags]
public enum Handedness
{
    None = 0 << 0,
    Left = 1 << 0,
    Right = 1 << 1,
    Both = Left | Right
}

ファイル パスのハードコーディング

文字列のファイル パスを生成するとき、特に、ハードコーディングされた文字列パスを記述する際は、次のようにしてください。

  1. 可能な限り、C# の Path API を使用します (Path.CombinePath.GetFullPath など)。
  2. \ や \\ ではなく / または Path.DirectorySeparatorChar を使用します。

この手順に従うことで、Windows ベースのシステムでも Unix ベースのシステムでも確実に MRTK が動作します。

次のことは行わないでください

private const string FilePath = "MyPath\\to\\a\\file.txt";
private const string OtherFilePath = "MyPath\to\a\file.txt";

string filePath = myVarRootPath + myRelativePath;

次のことを行います

private const string FilePath = "MyPath/to/a/file.txt";
private const string OtherFilePath = "folder{Path.DirectorySeparatorChar}file.txt";

string filePath = Path.Combine(myVarRootPath,myRelativePath);

// Path.GetFullPath() will return the full length path of provided with correct system directory separators
string cleanedFilePath = Path.GetFullPath(unknownSourceFilePath);

ベストプラクティス (Unity の推奨事項を含む)

このプロジェクトでは、パフォーマンスを考慮する必要のあるターゲット プラットフォームも中には存在します。 この点を踏まえ、タイトなアップデート ループまたはアルゴリズムで頻繁に呼び出されるコードでは常に慎重にメモリを割り当てるようにしてください。

カプセル化

クラスや構造体の外からフィールドにアクセスする必要がある場合は、常にプライベート フィールドとパブリック プロパティを使用します。 プライベート フィールドとパブリック プロパティは必ず同じ場所に記述するようにしてください。 どれがプロパティのバッキング フィールドで、そのフィールドがスクリプトから変更可能かどうかが判別しやすくなります。

Note

フィールドを JsonUtility でシリアル化する必要のあるデータ構造体の場合は、その限りではありません。この場合、シリアル化が正しく機能するためには、データ クラスのフィールドをすべてパブリックにする必要があります。

次のことは行わないでください

private float myValue1;
private float myValue2;

public float MyValue1
{
    get{ return myValue1; }
    set{ myValue1 = value }
}

public float MyValue2
{
    get{ return myValue2; }
    set{ myValue2 = value }
}

次のことを行います

// Enable field to be configurable in the editor and available externally to other scripts (field is correctly serialized in Unity)
[SerializeField]
[ToolTip("If using a tooltip, the text should match the public property's summary documentation, if appropriate.")]
private float myValue; // <- Notice we co-located the backing field above our corresponding property.

/// <summary>
/// If using a tooltip, the text should match the public property's summary documentation, if appropriate.
/// </summary>
public float MyValue
{
    get => myValue;
    set => myValue = value;
}

/// <summary>
/// Getter/Setters not wrapping a value directly should contain documentation comments just as public functions would
/// </summary>
public float AbsMyValue
{
    get
    {
        if (MyValue < 0)
        {
            return -MyValue;
        }

        return MyValue
    }
}

シーンやプレハブでは可能な限り、値をキャッシュしてシリアル化する

HoloLens を考慮して、パフォーマンスを最適化し、シーンやプレハブ内の参照をキャッシュして、実行時のメモリ割り当てを制限することをお勧めします。

次のことは行わないでください

void Update()
{
    gameObject.GetComponent<Renderer>().Foo(Bar);
}

次のことを行います

[SerializeField] // To enable setting the reference in the inspector.
private Renderer myRenderer;

private void Awake()
{
    // If you didn't set it in the inspector, then we cache it on awake.
    if (myRenderer == null)
    {
        myRenderer = gameObject.GetComponent<Renderer>();
    }
}

private void Update()
{
    myRenderer.Foo(Bar);
}

素材への参照はキャッシュし、都度 ".material" を呼び出すことは避ける

Unity では、".material" を使用するたびに新しい素材が作成され、適切にクリーンアップにされなかった場合、メモリ リークを引き起こします。

次のことは行わないでください

public class MyClass
{
    void Update()
    {
        Material myMaterial = GetComponent<Renderer>().material;
        myMaterial.SetColor("_Color", Color.White);
    }
}

次のことを行います

// Private references for use inside the class only
public class MyClass
{
    private Material cachedMaterial;

    private void Awake()
    {
        cachedMaterial = GetComponent<Renderer>().material;
    }

    void Update()
    {
        cachedMaterial.SetColor("_Color", Color.White);
    }

    private void OnDestroy()
    {
        Destroy(cachedMaterial);
    }
}

Note

または、Unity の "SharedMaterial" プロパティを使用してください。このプロパティであれば、参照するたびに新しい素材が作成されることはありません。

他のプラットフォームでのビルドに支障が生じないよう、プラットフォーム依存コンパイルを使用する

  • Unity のものではない UWP 固有の API を使用するには、WINDOWS_UWP を使用します。 Editor やサポート対象外のプラットフォームでの実行を未然に防ぐことができます。 これは UNITY_WSA && !UNITY_EDITOR に相当し、優先的に使用する必要があります。
  • UWP 固有の Unity API を使用するには、UNITY_WSA を使用します (UnityEngine.XR.WSA 名前空間など)。 これは Editor (プラットフォームが UWP に設定されている場合) およびビルドされた UWP アプリで動作します。

この表を参考に、実際のユース ケースと想定するビルド設定に応じて、使用すべき #if を判断してください。

プラットフォーム UWP IL2CPP UWP .NET エディター
UNITY_EDITOR False False True
UNITY_WSA True True True
WINDOWS_UWP True True False
UNITY_WSA && !UNITY_EDITOR True True False
ENABLE_WINMD_SUPPORT True True False
NETFX_CORE False True False

DateTime.Now よりも DateTime.UtcNow を優先する

DateTime.UtcNow の方が DateTime.Now よりも高速です。 以前実施したパフォーマンス調査の結果、DateTime.Now を使用すると、特に Update() ループで使用した場合に、著しいオーバーヘッドが生じることがわかりました。 同じ問題は、他でも報告されています

ローカライズされた時刻が実際に必要な場合 (現在時刻をユーザーのタイム ゾーンで表示したい場合など) 以外は、DateTime.UtcNow の方を使用してください。 相対的な時間 (最終更新時刻と現在時刻の差分) を扱う場合には、タイムゾーン変換のオーバーヘッドを回避するために、DateTime.UtcNow を使用するのが最良の選択肢です。

PowerShell のコーディング規則

MRTK コードベースのサブセットでは、パイプライン インフラストラクチャや各種のスクリプトおよびユーティリティに PowerShell が使用されます。 新しい PowerShell コードは、Poshcode スタイルに従う必要があります。

関連項目

C# のコーディング規則 (MSDN)