Menu
Coddy logo textTech

属性とリフレクション

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出力を制御し、テストフレームワークはこれらを使用してテストメソッドを検出し、依存関係注入コンテナはこれらを使用してサービスを自動的に接続します。

challenge icon

チャレンジ

簡単

カスタム属性とリフレクションを使用して、プラグインとしてマークされたクラスを自動的に検出し、その情報を表示するプラグイン検出システムを構築しましょう。これは、実際のフレームワークが実行時にコンポーネント、テストメソッド、または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})
        
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン