属性は、宣言型の方法で情報をコードに関連付ける方法を提供します。 また、さまざまなターゲットに適用できる再利用可能な要素を提供することもできます。 ObsoleteAttributeについて考えてみましょう。 クラス、構造体、メソッド、コンストラクターなどにも適用できます。 要素 が 古いものであることが宣言されています。 その後、C# コンパイラがこの属性を検索し、応答で何らかのアクションを実行する必要があります。
このチュートリアルでは、コードに属性を追加する方法、独自の属性を作成して使用する方法、および .NET に組み込まれているいくつかの属性を使用する方法について説明します。
[前提条件]
.NET を実行するようにマシンを設定する必要があります。 インストール手順については、 .NET ダウンロード ページを参照してください。 このアプリケーションは、Windows、Ubuntu Linux、macOS、または Docker コンテナーで実行できます。 お気に入りのコード エディターをインストールする必要があります。 次の説明では、オープンソースのクロスプラットフォーム エディターである Visual Studio Code を使用します。 ただし、使い慣れたツールは何でも使用できます。
アプリを作成する
すべてのツールをインストールしたので、新しい .NET コンソール アプリを作成します。 コマンド ライン ジェネレーターを使用するには、お気に入りのシェルで次のコマンドを実行します。
dotnet new console
このコマンドは、ベアボーンの .NET プロジェクト ファイルを作成します。
dotnet restore
を実行して、このプロジェクトのコンパイルに必要な依存関係を復元します。
復元を必要とするすべてのコマンド (dotnet restore
、dotnet new
、dotnet build
、dotnet run
、dotnet test
、dotnet publish
など) によって暗黙的に実行されるため、dotnet pack
を実行する必要がなくなりました。 暗黙的な復元を無効にするには、--no-restore
オプションを使用します。
dotnet restore
などの、明示的な復元が意味のある一部のシナリオや、復元が行われるタイミングを明示的に制御する必要があるビルド システムでは、dotnet restore
は引き続き有用なコマンドです。
NuGet フィードの管理方法については、dotnet restore
のドキュメントをご覧ください。
プログラムを実行するには、 dotnet run
を使用します。 コンソールに "Hello, World" という出力が表示されます。
コードに属性を追加する
C# では、属性は Attribute
基底クラスから継承するクラスです。
Attribute
から継承するクラスは、他のコードの "タグ" の一種として使用できます。 たとえば、 ObsoleteAttribute
という属性があります。 この属性は、コードが古く、使用されなくなったことを示します。 たとえば、角かっこを使用して、クラスにこの属性を配置します。
[Obsolete]
public class MyClass
{
}
クラスは ObsoleteAttribute
呼び出されますが、コードで [Obsolete]
を使用するだけで済みます。 ほとんどの C# コードは、この規則に従います。 選択した場合は、フル ネーム [ObsoleteAttribute]
を使用できます。
クラスを古い形式に設定する場合は、そのクラスが古い 理由 や、代わりに使用 する内容 に関する情報を提供することをお勧めします。 この説明を提供するには、Obsolete 属性に文字列パラメーターを含めます。
[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]
public class ThisClass
{
}
文字列は、ObsoleteAttribute
を記述しているかのように、var attr = new ObsoleteAttribute("some string")
コンストラクターに引数として渡されています。
属性コンストラクターのパラメーターは、単純型/リテラル ( bool, int, double, string, Type, enums, etc
とそれらの型の配列) に制限されます。
式や変数は使用できません。 位置指定パラメーターまたは名前付きパラメーターは自由に使用できます。
独自の属性を作成する
属性を作成するには、 Attribute
基底クラスから継承する新しいクラスを定義します。
public class MySpecialAttribute : Attribute
{
}
上記のコードでは、 [MySpecial]
(または [MySpecialAttribute]
) をコード ベースの他の場所の属性として使用できます。
[MySpecial]
public class SomeOtherClass
{
}
ObsoleteAttribute
などの .NET 基本クラス ライブラリ内の属性は、コンパイラ内の特定の動作をトリガーします。 ただし、作成した属性はメタデータとしてのみ機能し、属性クラス内のコードは実行されません。 コード内の他の場所で、そのメタデータに対してアクションを実行する必要があります。
注意が必要な 'gotcha' がここにあります。 前述のように、属性を使用する場合は、特定の型のみを引数として渡すことができます。 ただし、属性型を作成する場合、C# コンパイラによってこれらのパラメーターの作成が停止されることはありません。 次の例では、正しくコンパイルされるコンストラクターを持つ属性を作成しました。
public class GotchaAttribute : Attribute
{
public GotchaAttribute(Foo myClass, string str)
{
}
}
ただし、このコンストラクターを属性構文と共に使用することはできません。
[Gotcha(new Foo(), "test")] // does not compile
public class AttributeFail
{
}
上記のコードでは、次のようなコンパイラ エラーが発生します。 Attribute constructor parameter 'myClass' has type 'Foo', which is not a valid attribute parameter type
属性の使用を制限する方法
属性は、次の "ターゲット" で使用できます。 上記の例では、クラス上でそれらを示していますが、次の場合にも使用できます。
- 集会
- クラス
- コンストラクタ
- 委任
- 列挙型
- 出来事
- フィールド
- ジェネリックパラメータ
- インターフェイス
- メソッド
- モジュール
- パラメーター
- プロパティ
- ReturnValue
- 構造体
属性クラスを作成する場合、既定では、C# では、使用可能な任意の属性ターゲットでその属性を使用できます。 属性を特定のターゲットに制限する場合は、属性クラスの AttributeUsageAttribute
を使用して行うことができます。 つまり、属性に属性を設定します。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
{
}
上記の属性をクラスや構造体ではないものに配置しようとすると、次のようなコンパイラ エラーが発生します Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type. It is only valid on 'class, struct' declarations
public class Foo
{
// if the below attribute was uncommented, it would cause a compiler error
// [MyAttributeForClassAndStructOnly]
public Foo()
{ }
}
コード要素にアタッチされた属性を使用する方法
属性はメタデータとして機能します。 外向きの力がなければ、実際には何もしません。
属性を検索して操作するには、リフレクションが必要です。 リフレクションを使用すると、他のコードを調べるコードを C# で記述できます。 たとえば、Reflection を使用してクラスに関する情報を取得できます (コードの先頭に using System.Reflection;
を追加します)。
TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();
Console.WriteLine("The assembly qualified name of MyClass is " + typeInfo.AssemblyQualifiedName);
次のような内容が出力されます。 The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
TypeInfo
オブジェクト (またはMemberInfo
、FieldInfo
、またはその他のオブジェクト) を取得したら、GetCustomAttributes
メソッドを使用できます。 このメソッドは、 Attribute
オブジェクトのコレクションを返します。
GetCustomAttribute
を使用して属性の種類を指定することもできます。
GetCustomAttributes
クラス (前の例で MemberInfo
属性を適用したクラス)の MyClass
インスタンスに対して [Obsolete]
を使用する例を以下に示します。
var attrs = typeInfo.GetCustomAttributes();
foreach(var attr in attrs)
Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);
これにより Attribute on MyClass: ObsoleteAttribute
がコンソールに出力されます。 他の属性を MyClass
に追加してみてください。
これらの Attribute
オブジェクトは遅延でインスタンス化されることに注意してください。 つまり、 GetCustomAttribute
または GetCustomAttributes
を使用するまでインスタンス化されません。 また、毎回インスタンスが作成されます。
GetCustomAttributes
を行に 2 回呼び出すと、ObsoleteAttribute
の 2 つの異なるインスタンスが返されます。
ランタイムの一般的な属性
属性は、多くのツールとフレームワークで使用されます。 NUnit は、NUnit テスト ランナーによって使用される [Test]
や [TestFixture]
などの属性を使用します。 ASP.NET MVC は、 [Authorize]
などの属性を使用し、MVC アクションに対して横断的な懸念事項を実行するためのアクション フィルター フレームワークを提供します。
PostSharp では、属性構文を使用して、C# でのアスペクト指向プログラミングが可能になります。
.NET Core 基本クラス ライブラリに組み込まれている注目すべき属性をいくつか次に示します。
-
[Obsolete]
。 これは上記の例で使用され、System
名前空間に存在します。 コード ベースの変更に関する宣言型ドキュメントを提供すると便利です。 メッセージは文字列の形式で指定でき、別のブール型パラメーターを使用してコンパイラ警告からコンパイラ エラーにエスカレートできます。 -
[Conditional]
。 この属性は、System.Diagnostics
名前空間にあります。 この属性は、メソッド (または属性クラス) に適用できます。 コンストラクターに文字列を渡す必要があります。 その文字列が#define
ディレクティブと一致しない場合、C# コンパイラはそのメソッドへの呼び出しを削除します (メソッド自体は削除されません)。 通常、この手法はデバッグ (診断) の目的で使用します。 -
[CallerMemberName]
。 この属性はパラメーターで使用でき、System.Runtime.CompilerServices
名前空間に存在します。CallerMemberName
は、別のメソッドを呼び出しているメソッドの名前を挿入するために使用される属性です。 これは、さまざまな UI フレームワークで INotifyPropertyChanged を実装するときに、"マジック文字列" を排除する方法です。 例
public class MyUIClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyName = default!)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string? _name;
public string? Name
{
get { return _name;}
set
{
if (value != _name)
{
_name = value;
RaisePropertyChanged(); // notice that "Name" is not needed here explicitly
}
}
}
}
上記のコードでは、リテラル "Name"
文字列を持つ必要はありません。
CallerMemberName
を使用すると、入力ミスに関連するバグが回避され、リファクタリングや名前の変更がスムーズになります。 属性は C# に宣言型の力をもたらしますが、これらはメタデータ形式のコードであり、単独では動作しません。
.NET