C# コンパイラによって解釈されるその他の属性

属性 ConditionalObsoleteAttributeUsageAsyncMethodBuilderInterpolatedStringHandlerModuleInitializer は、コード内の要素に適用できます。 それによって、このような要素にセマンティックの意味が追加されます。 コンパイラでは、これらのセマンティックの意味を使用して出力が変更され、コードを使用する開発者による間違いの可能性が報告されます。

Conditional 属性

Conditional 属性を使用すると、プリプロセス識別子に依存したメソッドの実行を指定できます。 Conditional 属性は ConditionalAttribute の別名であり、メソッドまたは属性クラスに適用できます。

次の例では、Conditional は、プログラム固有の診断情報の表示を有効または無効にするメソッドに適用されています。

#define TRACE_ON
using System.Diagnostics;

namespace AttributeExamples;

public class Trace
{
    [Conditional("TRACE_ON")]
    public static void Msg(string msg)
    {
        Console.WriteLine(msg);
    }
}

public class TraceExample
{
    public static void Main()
    {
        Trace.Msg("Now in Main...");
        Console.WriteLine("Done.");
    }
}

TRACE_ON 識別子が定義されていない場合、トレース出力は表示されません。 対話型ウィンドウで自分で探してください。

多くの場合、Conditional 属性は、リリース ビルドではなく、デバッグ ビルドのトレース機能とログ機能を有効にするために DEBUG 識別子と共に使用されます。次に例を示します。

[Conditional("DEBUG")]
static void DebugMethod()
{
}

条件付きとマークされたメソッドが呼び出された場合、指定のプリプロセッサ シンボルの有無が、メソッドに対する呼び出しをコンパイラが含めるか省略するかを決定します。 シンボルが定義されている場合は呼び出しが実行され、定義されていない場合は省略されます。 条件付きメソッドは、クラスまたは構造体宣言のメソッドである必要があり、戻り値の型が void である必要があります。 Conditional を使用すると、#if…#endif ブロック内にメソッドを含めるよりも、クリーンで洗練されており、エラーが発生しにくくなります。

メソッドに複数の Conditional 属性がある場合、1 つ以上の条件付きシンボルが定義されているときにコンパイラはメソッドに対する呼び出しを含めます (シンボルは OR 演算子で論理的にリンクされます)。 次の例では、A または B が存在すると、メソッドが呼び出されます。

[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
    // ...
}

属性クラスでの Conditional の使用

Conditional 属性は、属性クラスの定義にも適用できます。 次の例では、カスタム属性 Documentation は、DEBUG が定義されている場合にのみ、情報をメタデータに追加します。

[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
    string text;

    public DocumentationAttribute(string text)
    {
        this.text = text;
    }
}

class SampleClass
{
    // This attribute will only be included if DEBUG is defined.
    [Documentation("This method displays an integer.")]
    static void DoWork(int i)
    {
        System.Console.WriteLine(i.ToString());
    }
}

Obsolete 属性

Obsolete 属性は、コード要素の使用が推奨されなくなったことを示します。 非推奨とマークされたエンティティを使用すると、警告またはエラーが生成されます。 Obsolete 属性は、1 回だけ使用できる属性であり、属性を使用できる任意のエンティティに適用できます。 ObsoleteObsoleteAttribute の別名です。

次の例では、Obsolete 属性がクラス A およびメソッド B.OldMethod に適用されています。 B.OldMethod に適用された属性コンストラクターの 2 番目の引数が true に設定されているため、このメソッドではコンパイル エラーが発生します。一方、クラス A が使用されると、警告が生成されます。 しかし、B.NewMethod の呼び出しでは、警告もエラーも生成されません。 たとえば、前の定義でこれを使用すると、次のコードで 2 つの警告と 1 つのエラーが生成されます。


namespace AttributeExamples
{
    [Obsolete("use class B")]
    public class A
    {
        public void Method() { }
    }

    public class B
    {
        [Obsolete("use NewMethod", true)]
        public void OldMethod() { }

        public void NewMethod() { }
    }

    public static class ObsoleteProgram
    {
        public static void Main()
        {
            // Generates 2 warnings:
            A a = new A();

            // Generate no errors or warnings:
            B b = new B();
            b.NewMethod();

            // Generates an error, compilation fails.
            // b.OldMethod();
        }
    }
}

最初の引数として属性コンストラクターに指定された文字列は、警告またはエラーの一部として表示されます。 クラス A の警告が 2 つ生成されます。1 つはクラス参照の宣言の警告で、もう 1 つはクラス コンストラクターの警告です。 Obsolete 属性は引数なしで使用できますが、代わりに何を使用するかの説明を含めることをお勧めします。

C# 10 では、定数文字列補間と nameof 演算子を使用して、名前を一致させることができます。

public class B
{
    [Obsolete($"use {nameof(NewMethod)} instead", true)]
    public void OldMethod() { }

    public void NewMethod() { }
}

SetsRequiredMembers 属性

この SetsRequiredMembers 属性は、コンストラクターがそのクラスまたは構造体ですべての required メンバーを設定することをコンパイラに通知します。 コンパイラは、System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute 属性を持つ任意のコンストラクターがすべての required メンバーを初期化することを前提としています。 このようなコンストラクターを呼び出すコードには、必要なメンバーを設定するためにオブジェクト初期化子は必要ありません。 これは主に位置指定レコードとプライマリ コンストラクターに役立ちます。

AttributeUsage 属性

AttributeUsage 属性によって、カスタム属性クラスの使用方法が決まります。 AttributeUsageAttribute は、カスタム属性定義に適用する属性です。 AttributeUsage 属性を使用すると、以下を制御できます。

  • 適用できるプログラム要素属性。 使用法を制限しない限り、属性は以下のプログラム要素のいずれかに適用できます。
    • アセンブリ
    • モジュール
    • フィールド
    • Event
    • Method
    • Param
    • プロパティ
    • 戻り値
    • Type
  • 1 つのプログラム要素に属性を複数回適用できるかどうか。
  • 属性が派生クラスに継承されるかどうか。

明示的に適用すると、既定の設定は次の例のようになります。

[AttributeUsage(AttributeTargets.All,
                   AllowMultiple = false,
                   Inherited = true)]
class NewAttribute : Attribute { }

この例で NewAttribute クラスはサポートされている任意のプログラム要素に適用できます。 ただし、各エンティティに適用できるのは 1 回のみです。 基底クラスに適用すると、属性は派生クラスに継承されます。

AllowMultiple 引数と Inherited 引数は省略できるので、次のコードは同じ効果を持ちます。

[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }

最初の AttributeUsageAttribute 引数は、AttributeTargets 列挙型の 1 つまたは複数の要素でなければなりません。 次の例のように、複数のターゲット型を OR 演算子で 1 つにまとめることができます。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }

プロパティ、または自動実装バッキング フィールドに属性を適用できるようになりました。 属性に field 指定子を指定しない限り、属性はプロパティに適用されます。 その両方の例を次に示します。

class MyClass
{
    // Attribute attached to property:
    [NewPropertyOrField]
    public string Name { get; set; } = string.Empty;

    // Attribute attached to backing field:
    [field: NewPropertyOrField]
    public string Description { get; set; } = string.Empty;
}

AllowMultiple 引数が true の場合、次の例のように、結果の属性を 1 つのエンティティに複数回適用できます。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }

[MultiUse]
[MultiUse]
class Class1 { }

[MultiUse, MultiUse]
class Class2 { }

この例では、AllowMultipletrue に設定されているので、MultiUseAttribute を繰り返し適用できます。 示されているどちらの形式でも、複数の属性を適用できます。

Inheritedfalse の場合、属性は属性クラスから派生したクラスに継承されません。 次に例を示します。

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }

[NonInherited]
class BClass { }

class DClass : BClass { }

この例で、NonInheritedAttribute は継承によって DClass に適用されません。

これらのキーワードを使用して、属性を適用する場所を指定することもできます。 たとえば、field: 指定子を使用すれば、自動実装プロパティのバッキング フィールドに属性を追加することができます。 また、field:property:、または param: 指定子を使用すれば、位置指定レコードから生成された要素のいずれかに属性を適用することができます。 例については、「プロパティ定義の位置指定構文」を参照してください。

AsyncMethodBuilder 属性

System.Runtime.CompilerServices.AsyncMethodBuilderAttribute 属性は、非同期の戻り値の型として可能な型に属性を追加します。 この属性は、指定した型が非同期メソッドから返されるときに、非同期メソッドの実装を構築する型を指定します。 AsyncMethodBuilder 属性は、次のような型に適用できます。

AsyncMethodBuilder 属性のコンストラクターが、関連付けられているビルダーの種類を指定します。 ビルダーは、次のアクセス可能なメンバーを実装する必要があります。

  • ビルダーの型を返す静的な Create() メソッド。

  • 非同期の戻り値の型を返す読み取り可能な Task プロパティ。

  • タスクが失敗したときに例外を設定する void SetException(Exception) メソッド。

  • タスクを完了としてマークし、必要に応じてタスクの結果を設定する void SetResult() または void SetResult(T result) メソッド

  • 次の API シグネチャを持つ Start メソッド。

    void Start<TStateMachine>(ref TStateMachine stateMachine)
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • 次のシグネチャを持つ AwaitOnCompleted メソッド。

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion
        where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • 次のシグネチャを持つ AwaitUnsafeOnCompleted メソッド。

          public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
              where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    

非同期メソッド ビルダーの詳細については、.NET に用意されている次のビルダーに関するページを参照してください。

C# 10 以降では、AsyncMethodBuilder 属性を非同期メソッドに適用して、その型のビルダーをオーバーライドできます。

InterpolatedStringHandler 属性および InterpolatedStringHandlerArguments 属性

C# 10 以降では、これらの属性を使用して型が "補間された文字列ハンドラー" であることを指定します。 .NET 6 ライブラリには、string パラメーターの引数として補間された文字列を使用するシナリオ用に、System.Runtime.CompilerServices.DefaultInterpolatedStringHandler が既に含まれています。 補間された文字列の処理方法を制御したい他のケースがあるかもしれません。 ご自分のハンドラーを実装する型に System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute を適用できます。 その型のコンストラクターのパラメーターに System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute を適用できます。

補間された文字列ハンドラーの作成について詳しくは、補間された文字列の改善のための C# 10 機能仕様を参照してください。

ModuleInitializer 属性

C# 9 以降では、ModuleInitializer 属性は、アセンブリの読み込み時にランタイムが呼び出すメソッドをマークします。 ModuleInitializerModuleInitializerAttribute の別名です。

ModuleInitializer 属性は、次のメソッドにのみ適用できます。

  • 静的。
  • パラメーターなし。
  • void を返します。
  • 含まれているモジュール、つまり internal または public からアクセスできる。
  • ジェネリック メソッドではない。
  • ジェネリック クラスに含まれていない。
  • ローカル関数ではない。

ModuleInitializer 属性は、複数のメソッドに適用できます。 その場合、ランタイムが呼び出す順序は決定的ですが、指定されていません。

次の例は、複数のモジュール初期化子メソッドの使用方法を示しています。 Init1Init2 のメソッドは、Main の前に実行されます。各メソッドは Text プロパティに文字列を追加します。 そのため Main を実行するときには、Text プロパティに、両方の初期化子メソッドの文字列が既に含まれています。

using System;

internal class ModuleInitializerExampleMain
{
    public static void Main()
    {
        Console.WriteLine(ModuleInitializerExampleModule.Text);
        //output: Hello from Init1! Hello from Init2!
    }
}
using System.Runtime.CompilerServices;

internal class ModuleInitializerExampleModule
{
    public static string? Text { get; set; }

    [ModuleInitializer]
    public static void Init1()
    {
        Text += "Hello from Init1! ";
    }

    [ModuleInitializer]
    public static void Init2()
    {
        Text += "Hello from Init2! ";
    }
}

ソース コード ジェネレーターでは、初期化コードの生成が必要になる場合があります。 そのコードを格納するための標準の場所は、モジュール初期化子が提供します。 その他のほとんどの場合は、モジュール初期化子ではなく、静的コンストラクターを記述する必要があります。

SkipLocalsInit 属性

C# 9 以降では、SkipLocalsInit 属性は、メタデータへの出力時にコンパイラによって .locals init フラグが設定されないようにします。 SkipLocalsInit 属性は、単一使用属性であり、メソッド、プロパティ、クラス、構造体、インターフェイス、またはモジュールに適用できますが、アセンブリには適用できません。 SkipLocalsInitSkipLocalsInitAttribute の別名です。

.locals init フラグを使用すると、CLR によってメソッドで宣言されたすべてのローカル変数が既定値に初期化されます。 コンパイラでも、値を割り当てる前に変数が使用されないようにするため、.locals init は通常は必要ありません。 ただし、stackalloc を使用してスタックに配列を割り当てる場合など、一部のシナリオでは、追加のゼロ初期化がパフォーマンスに重大な影響を与える可能性があります。 そのような場合は、SkipLocalsInit 属性を追加できます。 属性は、メソッドに直接適用すると、そのメソッドと、ラムダやローカル関数を含むすべての入れ子になった関数に影響します。 型またはモジュールに適用すると、内部で入れ子になっているすべてのメソッドに影響します。 この属性は抽象メソッドには影響しませんが、実装用に生成されたコードには影響します。

この属性には、AllowUnsafeBlocks コンパイラ オプションが必要です。 この要件により、場合によってはコードで未割り当てメモリを表示できる可能性があることが通知されます (たとえば、初期化されていないスタックに割り当てられたメモリからの読み取りなど)。

次の例は、stackalloc を使用するメソッドに対する SkipLocalsInit 属性の効果を示しています。 このメソッドは、整数の配列が割り当てられたときに、メモリ内のものをすべて表示します。

[SkipLocalsInit]
static void ReadUninitializedMemory()
{
    Span<int> numbers = stackalloc int[120];
    for (int i = 0; i < 120; i++)
    {
        Console.WriteLine(numbers[i]);
    }
}
// output depends on initial contents of memory, for example:
//0
//0
//0
//168
//0
//-1271631451
//32767
//38
//0
//0
//0
//38
// Remaining rows omitted for brevity.

このコードをご自分で試される場合は、 .csproj ファイルで AllowUnsafeBlocks コンパイラ オプションを設定します。

<PropertyGroup>
  ...
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

関連項目