属性とリフレクション
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 44/70。
属性 (アトリビュート) は、クラス、メソッド、プロパティなどのコード要素に付加するメタデータタグです。これらはコードの実行方法を直接変更するものではありませんが、実行時に読み取ることができる情報を提供します。リフレクション は、このメタデータを読み取るためのメカニズムです。
属性を適用するには、対象となる要素の上の角括弧内に配置します。
[Obsolete("Use NewMethod instead")]
public void OldMethod() { }
[Serializable]
public class Person
{
public string Name { get; set; }
}Attributeを継承することで、カスタム属性を作成できます:
public class AuthorAttribute : Attribute
{
public string Name { get; }
public AuthorAttribute(string name) => Name = name;
}
[Author("John")]
public class Calculator { }リフレクションを使用すると、System.Reflection 名前空間を使用して、実行時に型を検査したり属性を読み取ったりすることができます:
Type type = typeof(Calculator);
var attr = type.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine(attr.Name); // Johnメソッドやプロパティを検出し、それらを動的に呼び出すこともできます:
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
Console.WriteLine(method.Name);属性とリフレクションは、多くのフレームワークを支えています。シリアライズライブラリはこれらを使用してJSON出力を制御し、テストフレームワークはこれらを使用してテストメソッドを検出し、依存関係注入コンテナはこれらを使用してサービスを自動的に接続します。
チャレンジ
簡単カスタム属性とリフレクションを使用して、プラグインとしてマークされたクラスを自動的に検出し、その情報を表示するプラグイン検出システムを構築しましょう。これは、実際のフレームワークが実行時にコンポーネント、テストメソッド、またはAPIエンドポイントを検出する方法と似ています。
コードは3つのファイルに分けて構成します:
PluginAttribute.cs:PluginSystem名前空間にPluginAttributeというカスタム属性を作成します。この属性は以下の要件を満たす必要があります:Attributeを継承していること- 2つの読み取り専用プロパティ、
Name(string) とVersion(string) を持つこと - コンストラクタを通じて両方の値を受け取ること
Plugins.cs:PluginSystem名前空間にいくつかのプラグインクラスを作成します。いくつかはPluginAttributeで装飾し、1つは装飾しないようにします:ImageProcessor-[Plugin("Image Processor", "1.0")]でマークするDataExporter-[Plugin("Data Exporter", "2.5")]でマークするHelperUtility- 属性でマークしない(これは単なる通常のクラスです)
Program.cs: メインファイルで、リフレクションを使用してPluginAttributeを持つすべてのクラスを検出します。検出された各プラグインについて、その情報を出力します。以下の手順が必要です:- 現在のアセンブリからすべての型を取得する
- 各型に
PluginAttributeが付いているかチェックする - 属性の
NameプロパティとVersionプロパティを抽出して表示する
型の検査とカスタム属性の取得にはSystem.Reflectionを使用します。GetCustomAttribute<T>()メソッドは、属性が存在しない場合にnullを返すため、これを利用してどのクラスが実際のプラグインであるかをフィルタリングできます。
検出された各プラグインを、1行に1つずつ次の形式で出力してください:
Plugin: {Name} (v{Version})プラグインは、ImageProcessorを最初に出力し、次にDataExporterを出力する順序にしてください。HelperUtilityクラスは属性を持っていないため、出力に表示されないようにします。
期待される出力:
Plugin: Image Processor (v1.0)
Plugin: Data Exporter (v2.5)このチャレンジは、フレームワークが属性をマーカーとして使用し、リフレクションを使用してそれらを検出する方法を示しています。この手法は、依存関係注入(DI)コンテナ、シリアル化ライブラリ、テストフレームワークなどを支える技術と同じです!
チートシート
属性(アトリビュート)は、コード要素の上の角括弧内に配置されるメタデータタグです。これらは実行時に読み取ることができる情報を提供します。
[Obsolete("Use NewMethod instead")]
public void OldMethod() { }
[Serializable]
public class Person
{
public string Name { get; set; }
}Attribute を継承することで、カスタム属性を作成できます。
public class AuthorAttribute : Attribute
{
public string Name { get; }
public AuthorAttribute(string name) => Name = name;
}
[Author("John")]
public class Calculator { }リフレクションを使用すると、System.Reflection 名前空間を使用して、実行時に型を検査したり属性を読み取ったりできます。
Type type = typeof(Calculator);
var attr = type.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine(attr.Name); // Johnメソッドやプロパティを動的に検出します。
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
Console.WriteLine(method.Name);GetCustomAttribute<T>() メソッドは、属性が存在しない場合に null を返します。これにより、属性の有無に基づいて型をフィルタリングできます。
自分で試してみよう
using System;
using System.Reflection;
using PluginSystem;
class Program
{
public static void Main(string[] args)
{
// TODO: 現在のアセンブリからすべての型を取得する
// ヒント: Assembly.GetExecutingAssembly().GetTypes() を使用します
// TODO: 各型をループし、PluginAttribute が付与されているか確認する
// ヒント: type.GetCustomAttribute<PluginAttribute>() を使用します
// TODO: 属性が存在する場合、次の形式でプラグイン情報を出力する:
// Plugin: {Name} (v{Version})
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
5ポリモーフィズムとインターフェース
コンパイル時 vs 実行時ポリモーフィズムインターフェース vs 抽象クラス複数のインターフェース明示的なインターフェースアップキャストとダウンキャストまとめ - 図形計算機8オブジェクト指向の高度な概念
継承よりコンポジションジェネリクス (クラスとメソッド)デリゲートとイベント属性とリフレクションIDisposable と using ステートメント依存性の注入 (DI) の基礎