reflectionという英単語には「反射」や「反響」といった意味、また「内省」や「熟考」などの自分自身を見つめるといった意味があります。 「自己言及」と訳される場合もあります。 プログラミングにおけるリフレクションとは、プログラムのメタデータ(=型情報などプログラム自身に埋め込まれている情報)を取得することを指します。

.NET Frameworkではプログラム(厳密にはコードのコンパイルによって生成されるモジュール)に様々なメタデータが埋め込まれます。 リフレクションによって埋め込まれたメタデータを参照することによって、型情報や属性の取得、型情報のみによるインスタンスの作成、遅延バインディング、アセンブリの動的読み込み・生成など、さまざまなことが行えるようになっています。 文字列で名前を指定してメソッドを呼び出したり、文字列で型を選択するといった手法もリフレクションによって実装することができます。

§1 リフレクションの例

§1.1 型情報の取得

リフレクションによって実行時に型自身の情報を取得することができます。 取得できる情報については後述の§.Typeクラスから取得できる情報§.メンバ情報の取得 (MemberInfo)などで解説します。

リフレクション機能を使って実行時に型情報を取得する
using System;
using System.Reflection;

class Account {
  public int ID { get; set; }
  public string Name { get; set; }

  public Account(int id, string name)
  {
    this.ID = id;
    this.Name = name;
  }
}

class Sample {
  static void Main()
  {
    // Accountクラスの型情報を取得する
    Type t = typeof(Account);

    Console.WriteLine("Name = {0}", t.Name);
    Console.WriteLine("BaseType = {0}", t.BaseType);

    // Accountクラスのすべてのメンバ情報を取得する
    // (パブリックおよび非パブリックのインスタンスメンバを取得する)
    MemberInfo[] members = t.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

    foreach (MemberInfo m in members) {
      Console.WriteLine("MemberType = {0}, Name = {1}", m.MemberType, m.Name);
    }
  }
}
実行結果例
Name = Account
BaseType = System.Object
MemberType = Method, Name = get_ID
MemberType = Method, Name = set_ID
MemberType = Method, Name = get_Name
MemberType = Method, Name = set_Name
MemberType = Method, Name = Equals
MemberType = Method, Name = Finalize
MemberType = Method, Name = GetHashCode
MemberType = Method, Name = GetType
MemberType = Method, Name = MemberwiseClone
MemberType = Method, Name = ToString
MemberType = Method, Name = obj_address
MemberType = Constructor, Name = .ctor
MemberType = Property, Name = ID
MemberType = Property, Name = Name
MemberType = Field, Name = <ID>k__BackingField
MemberType = Field, Name = <Name>k__BackingField

§1.2 インスタンスの操作

リフレクション機能を使うと、取得した型情報を使ってメソッドの呼び出しやフィールドの取得・設定などのインスタンスの操作をすることができます。 この際に、操作するメソッドやフィールド・プロパティは文字列によって指定することができます。 これにより、型に依存しない汎用的な処理を記述したり、実行時まで型が決定していないインスタンスに対する操作を行うといったことができます。

リフレクション機能を使ってインスタンスの生成・操作を行う
using System;
using System.Reflection;

class Account {
  public string Name { get; set; }
}

class Sample {
  static void Main()
  {
    // クラス名Accountの型情報を取得する
    Type t = Type.GetType("Account");

    // 型情報からインスタンスを作成する
    object inst = Activator.CreateInstance(t);

    // Nameプロパティを取得する
    PropertyInfo p = t.GetProperty("Name");

    // プロパティに値を設定する
    p.SetValue(inst, "Alice", null);

    // プロパティの値を取得して表示する
    Console.WriteLine("Name = {0}", p.GetValue(inst, null));
  }
}
実行結果
Name = Alice

このような操作については§.メンバの呼び出しで解説します。

§1.3 属性

型情報以外のメタデータとして属性も挙げられます。 .NET Frameworkでは属性を使うことでプログラムに型情報以外の追加的なメタデータを埋め込むことができ、それを実行時に取得・参照することができます。 属性にはコンパイラやランタイムが特別な操作を行うために使用されるものが存在するほか、独自に意味を定義して情報を埋め込むカスタム属性があります。

カスタム属性でメタデータを埋め込み、リフレクションにより埋め込んだメタデータを実行時に取得する
using System;
using System.Reflection;

// カスタム属性となるクラス
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class AuthorAttribute : Attribute {
  public string Name {
    get; private set;
  }

  public string Version;

  public AuthorAttribute(string name)
  {
    this.Name = name;
  }
}

class Sample {
  // カスタム属性が設定されたメソッド
  [Author("Alice", Version = "1.0")]
  [Author("Bob", Version = "1.1")]
  [Author("Charlie", Version = "1.2")]
  public static void TestMethod()
  {
  }

  static void Main()
  {
    // メソッドの情報を取得する
    MethodInfo method = typeof(Sample).GetMethod("TestMethod");

    // メソッドに設定されているカスタム属性を取得して列挙する
    foreach (AuthorAttribute attr in method.GetCustomAttributes(typeof(AuthorAttribute), false)) {
      // 属性に設定されている値を表示する
      Console.WriteLine("Author: {0}, Version: {1}", attr.Name, attr.Version);
    }
  }
}
実行結果例
Author: Charlie, Version: 1.2
Author: Bob, Version: 1.1
Author: Alice, Version: 1.0

代表的な属性として、プログラムのバージョン情報を埋め込むための属性(アセンブリのバージョン情報を取得する)や、旧式化されたAPIであることを示すObsolete属性(機能の廃止・旧式化)、オブジェクトをシリアライズする際の動作を定義する属性(シリアライズの基本)などがあります。

その他、属性について、および属性の使用方法や独自に定義してその情報を取得する方法などについて詳しくは属性とメタデータで個別に解説しています。

§1.4 リソース

型情報や属性のほかに、リソースもメタデータとして埋め込まれます。 リフレクションによってEXEやDLLに埋め込まれているリソースを利用することもできます。

リフレクションを使ったリソースの扱い方についてはリソースの埋め込みと読み込みで個別に解説しています。



§2 型情報 (Typeクラス)

リフレクションにおいて中心的な役割を果たすTypeクラスについて解説します。 このクラスは型情報を扱うクラスとなっていて、このクラスから型自体の情報を取得できるほか、メソッドやフィールドなどメンバの情報の取得、型情報からのインスタンスの生成や、メソッド呼び出し・フィールドの書き換えなどのインスタンスの操作を行う上で必要な情報を取得することができます。

§2.1 Typeクラスの取得 (typeof, GetType)

C#ではtypeof演算子、VBではGetType演算子を使うことで型情報をTypeクラスのインスタンスとして取得することができます。 また、任意の型のインスタンスでGetTypeメソッドを呼び出すことによっても取得することができます。 このメソッドはObjectクラスから継承されるため、どの型でも共通して使用することができます。

つまり、型名から直接型情報を取得したい場合にはtypeof/GetType、インスタンスからその型の型情報を取得したい場合にはGetTypeメソッドを使用します。

型情報を取得する
using System;

class Sample {
  static void Main()
  {
    Type t1 = typeof(int); // int型の型情報の取得
    Type t2 = typeof(Sample); // Sampleクラスの型情報の取得

    string str = "foo";

    Type t3 = str.GetType(); // stringインスタンスからの型情報の取得
  }
}

§2.1.1 ジェネリック型の型情報

ジェネリック型では、型引数に具体的な型が指定されているかどうかで型情報が変わります。 たとえばList<T>クラスを例にとった場合、型引数Tの型が具体的に定まっていないList<T>を特にオープンジェネリック型(あるいはジェネリック型定義)、対してList<int>List<string>など型引数Tの型が具体的な型が定まっているものをクローズジェネリック型(あるいは構築ジェネリック型構築された型)と呼びます。

typeof演算子・GetType演算子でジェネリック型の型情報を取得する場合、オープンジェネリック型とクローズジェネリック型を区別して取得することができます。 オープンジェネリック型を表す場合は型パラメータの指定を省略した型名(例:Dictionary<,>/Dictionary(Of ,))を使用し、一方クローズジェネリック型では具体的な型名を指定した型名(例:Dictionary<string, int>/Dictionary(Of String, Integer))を使用することによってそれぞれの型情報を取得することができます。

オープンジェネリック型およびクローズジェネリック型の型情報を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Type t1 = typeof(List<>); // 型パラメータが1つのオープンジェネリック型
    Type t2 = typeof(List<int>); // 型パラメータが1つのクローズジェネリック型
    Type t3 = typeof(List<string>);

    Console.WriteLine(t1);
    Console.WriteLine(t2);
    Console.WriteLine(t3);

    Type t4 = typeof(Dictionary<,>); // 型パラメータが2つのオープンジェネリック型
    Type t5 = typeof(Dictionary<string, int>); // 型パラメータが2つのクローズジェネリック型

    Console.WriteLine(t4);
    Console.WriteLine(t5);

    Type t6 = typeof(Action<,>);
    Type t7 = typeof(Action<,,>);
    Type t8 = typeof(Action<,,,>);

    Console.WriteLine(t6);
    Console.WriteLine(t7);
    Console.WriteLine(t8);
  }
}
実行結果
System.Collections.Generic.List`1[T]
System.Collections.Generic.List`1[System.Int32]
System.Collections.Generic.List`1[System.String]
System.Collections.Generic.Dictionary`2[TKey,TValue]
System.Collections.Generic.Dictionary`2[System.String,System.Int32]
System.Action`2[T1,T2]
System.Action`3[T1,T2,T3]
System.Action`4[T1,T2,T3,T4]

オープンジェネリック型(ジェネリック型定義)の型情報では、Type.MakeGenericTypeメソッドを使用することでクローズジェネリック型(構築ジェネリック型)を取得することができます。 言い換えると、MakeGenericTypeメソッドによってジェネリック型定義から構築ジェネリック型を構築することができます。

MakeGenericTypeメソッドの引数に型引数にしたい型の型情報(Type)を指定することで、対応するクローズジェネリック型の型情報を取得できます。 例えばDictionary<,>の型情報からDictionary<string, int>の型情報を取得したい場合は次のようにします。

オープンジェネリック型からクローズジェネリック型の型情報を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Type t1 = typeof(Dictionary<,>); // オープンジェネリック型

    Console.WriteLine(t1);

    // Dictionary<,>からDictionary<string, int>の型情報を作成する
    Type t2 = t1.MakeGenericType(typeof(string), typeof(int));

    Console.WriteLine(t2);
  }
}
実行結果
System.Collections.Generic.Dictionary`2[TKey,TValue]
System.Collections.Generic.Dictionary`2[System.String,System.Int32]

逆にクローズジェネリック型(構築ジェネリック型)の型情報では、GetGenericTypeDefinitionメソッドを使用することでオープンジェネリック型(ジェネリック型定義)の型情報を取得することができます。 言い換えると、GetGenericTypeDefinitionメソッドによって構築ジェネリック型からジェネリック型定義を取得することができます。

例えばDictionary<string, int>の型情報からDictionary<,>の型情報を取得したい場合は次のようにします。

クローズジェネリック型からオープンジェネリック型の型情報を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Type t1 = typeof(Dictionary<string, int>); // クローズジェネリック型

    Console.WriteLine(t1);

    // Dictionary<string, int>からDictionary<,>の型情報を作成する
    Type t2 = t1.GetGenericTypeDefinition();

    Console.WriteLine(t2);
  }
}
実行結果
System.Collections.Generic.Dictionary`2[System.String,System.Int32]
System.Collections.Generic.Dictionary`2[TKey,TValue]

.NET Frameworkでは、ジェネリック型の型引数(型パラメータ、例えばList<T>におけるTList<int>におけるintの部分)もTypeクラスで扱います。 詳しくは後述の§.型パラメータ情報の取得で解説します。

§2.1.1.1 ジェネリック型と型名の表記

Typeクラスなどで使われる型名の多くはコード上での表記と変わりませんが、ジェネリック型では異なる表記が使われます。 例えばコード上ではList<T>/List(Of T)と記述される型にはSystem.Collections.Generic.List`1といった表記が使われます。

型名では山括弧< >で型引数名を記述する代わりに、バッククオート`の後ろに型引数の数が記述されます。 例えばDictionary<TKey, TValue>では型引数の数が2つとなるため、System.Collections.Generic.Dictionary`2という表記になります。

ジェネリック型における型名の表記
using System;
using System.Reflection;

class Sample {
  static void Main()
  {
    Console.WriteLine(typeof(System.Collections.Generic.List<>));
    Console.WriteLine(typeof(System.Collections.Generic.List<int>));
    Console.WriteLine(typeof(System.Collections.Generic.Dictionary<,>));

    Console.WriteLine(typeof(System.Action));
    Console.WriteLine(typeof(System.Action<>));
    Console.WriteLine(typeof(System.Action<,>));
    Console.WriteLine(typeof(System.Action<,,>));
  }
}
実行結果
System.Collections.Generic.List`1[T]
System.Collections.Generic.List`1[System.Int32]
System.Collections.Generic.Dictionary`2[TKey,TValue]
System.Action
System.Action`1[T]
System.Action`2[T1,T2]
System.Action`3[T1,T2,T3]

リフレクションにおいてジェネリック型の名前を文字列で指定する場合には、この表記を使用する必要があります。

§2.2 アセンブリからの型情報の取得

アセンブリとは何かを簡略に説明すると、EXEファイルあるはDLLファイルから読み込まれた実行可能コードとメタデータのセットのことです。 (より正確には、実行可能コードとメタデータのセットであるモジュールを格納したもの) 特定のEXEやDLLで定義されている型情報を取得したい場合には、まず次のようなメソッドによってアセンブリを取得する必要があります。

アセンブリを取得するためのメソッド
メソッド 概要
Assembly.GetAssembly 指定された型が宣言されているアセンブリを取得する
Assembly.GetExecutingAssembly 現在実行中のコードが存在するアセンブリを取得する
Assembly.GetCallingAssembly 現在実行中のコードを呼び出したアセンブリを取得する
Assembly.GetEntryAssembly プロセスのエントリーポイントを含むアセンブリを取得する
Assembly.Load アセンブリを読み込んで取得する
Assembly.ReflectionOnlyLoad リフレクションのみを目的としてアセンブリを読み込み、取得する

文字列で表された型名から型情報を取得したい場合はAssembly.GetTypeメソッドを使用します。 次の例では現在実行しているアセンブリから指定された名前の型情報を取得しています。 GetTypeメソッドに指定する型名は名前空間を含めた完全名を指定する必要があります。

文字列で表された型名から型情報を取得する
using System;
using System.Reflection;

namespace NS {
  class T1 {} // このクラスの完全名はNS.T1となる
}

class Sample {
  static void Main()
  {
    // 現在実行しているコードが含まれるアセンブリを取得する
    Assembly a = Assembly.GetExecutingAssembly();

    // NS名前空間のクラスT1の型情報を取得する
    Type t = a.GetType("NS.T1");
  }
}

また、入れ子になっている型(クラス内で定義されたクラスなど)の型情報を取得したい場合は、目的の型を含んでいる型名と入れ子になっている型の名前を+で連結した型名を指定します。

入れ子になっている型の型情報を取得する
using System;
using System.Reflection;

namespace NS {
  class ContainerClass {
    class InnerClass {} // このクラスの完全名はNS.ContainerClass+InnerClassとなる
  }
}

class Sample {
  static void Main()
  {
    Assembly a = Assembly.GetExecutingAssembly();

    // 入れ子になっているクラスInnerClassの型情報を取得する
    Type t = a.GetType("NS.ContainerClass+InnerClass");
  }
}

アセンブリに定義されているすべての型情報を取得したい場合はAssembly.GetTypesメソッドあるいはAssembly.GetExportedTypesメソッドを使用します。

GetTypesメソッドではアセンブリに含まれるすべての型が返されるのに対して、GetExportedTypesメソッドではアセンブリの外部から参照できる型のみが返されます。 そのためGetExportedTypesメソッドでは、パブリックではない型(アクセス修飾子internal/Friendが指定されているクラスなど)は取得できません。

アセンブリ内のすべての型情報を取得する
using System;
using System.Reflection;

public class T1 {} // アセンブリ外に公開されるクラス(public)
internal class T2 {} // アセンブリ外に公開されないクラス(internal)

class Sample { // アセンブリ外に公開されないクラス(internal)
  static void Main()
  {
    Assembly a = Assembly.GetExecutingAssembly();

    // GetTypesメソッドではすべての型が返される
    Console.WriteLine("[GetTypes]");

    foreach (var t in a.GetTypes()) {
      Console.WriteLine(t);
    }

    // GetExportedTypesメソッドではpublicな型のみが返される
    Console.WriteLine("[GetExportedTypes]");

    foreach (var t in a.GetExportedTypes()) {
      Console.WriteLine(t);
    }
  }
}
実行結果
[GetTypes]
T1
T2
Sample
[GetExportedTypes]
T1

§2.3 Typeクラスから取得できる情報

Typeクラスからは型名だけでなく、型がクラスなのかインターフェイスなのかといった型の種類や特徴に関する情報や、型で定義されているメソッド・プロパティ・フィールドなどに関する情報を取得することができます。

§2.3.1 型情報の参照

Typeクラスでは次のようなプロパティによって型の種類に関する情報を参照・取得することができます。

Typeクラスのプロパティ
プロパティ 意味
型の名称 Name 名前空間を含まない型名
(例:System.IO.StreamならStream)
Namespace 名前空間
(例:System.IO.StreamならSystem.IO)
FullName 名前空間を含む型名
(例:System.IO.StreamならSystem.IO.Stream)
型の宣言箇所 Assembly 型が宣言されているアセンブリ
Module 型が宣言されているモジュール
DeclaringType 入れ子になっている型の場合、その型を含んでいる型
型の分類 IsClass 型がクラス型かどうか
IsValueType 型が値型(構造体・列挙体・プリミティブ数値型など)かどうか (関連:値型と参照型)
IsInterface 型がインターフェイス型かどうか
IsEnum 型が列挙体かどうか
IsArray 型が配列型かどうか
IsPrimitive 型がプリミティブ型かどうか (関連:型の種類・サイズ・精度・値域)
型の属性・状態・継承関係 IsAbstract 型が抽象型(abstract/MustInherit)かどうか
IsSealed 型がシールクラス(sealed/NotInheritable)かどうか
IsPublic 型がパブリックかどうか
IsNotPublic 型が非パブリックかどうか
IsNested 型が入れ子になっているか(他の型の内部で宣言されているか)
BaseType 基底クラス(継承元)の型情報 (関連:型・インスタンスが派生クラスかどうかを調べる §.基底クラスの取得)
ジェネリック型 IsGenericType ジェネリック型かどうか
(オープン型List<>とクローズ型List<int>のどちらでもtrueとなる)
IsGenericTypeDefinition ジェネリック型定義(オープンジェネリック型)かどうか
(オープン型List<>ではtrue、クローズ型List<int>ではfalseとなる)
IsConstructedGenericType
(.NET Framework 4.5以降)
構築ジェネリック型(クローズジェネリック型)かどうか
(オープン型List<>ではfalse、クローズ型List<int>ではtrueとなる)
ContainsGenericParameters 具体的な型が指定されていない型パラメーターを含むかどうか
(List<>ではtrue、List<int>ではfalseとなる
同様にDictionary<,>.KeyCollectionではtrue、Dictionary<int,int>.KeyCollectionではfalseとなる)
IsGenericParameter 型がジェネリック型パラメータを表すかどうか
プロパティ 意味

次の例はTypeクラスのプロパティを参照して型の分類を行う例です。 この例で使用しているデリゲート型の判定に関しては後述の§.型がデリゲート型かどうか調べるを参照してください。

Typeクラスを使って型の分類を行う
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    ClassifyType(typeof(int));
    ClassifyType(typeof(string));
    ClassifyType(typeof(double[]));
    ClassifyType(typeof(DateTime));
    ClassifyType(typeof(KeyValuePair<,>));
    ClassifyType(typeof(DayOfWeek));
    ClassifyType(typeof(List<>));
    ClassifyType(typeof(ICloneable));
    ClassifyType(typeof(IEnumerable<>));
    ClassifyType(typeof(EventHandler));
    ClassifyType(typeof(Action<>));
  }

  static void ClassifyType(Type t)
  {
    Console.Write("{0}: ", t.FullName);

    if (t.IsValueType) {
      if (t.IsEnum) {
        Console.WriteLine("列挙体");
      }
      else {
        if (t.IsPrimitive) {
          Console.WriteLine("値型(プリミティブ)");
        }
        else {
          if (t.IsGenericType)
            Console.Write("ジェネリック");

          Console.WriteLine("構造体");
        }
      }
    }
    else {
      if (t.IsArray) {
        Console.WriteLine("配列型");
      }
      else {
        if (t.IsGenericType)
          Console.Write("ジェネリック");

        if (t.IsInterface) {
          Console.WriteLine("インターフェイス型");
        }
        else {
          if (IsDelegate(t))
            Console.WriteLine("デリゲート型");
          else
            Console.WriteLine("クラス型");
        }
      }
    }
  }

  static bool IsDelegate(Type t)
  {
    return t.IsSubclassOf(typeof(Delegate)) || t.Equals(typeof(Delegate));
  }
}
実行結果
System.Int32: 値型(プリミティブ)
System.String: クラス型
System.Double[]: 配列型
System.DateTime: 構造体
System.Collections.Generic.KeyValuePair`2: ジェネリック構造体
System.DayOfWeek: 列挙体
System.Collections.Generic.List`1: ジェネリッククラス型
System.ICloneable: インターフェイス型
System.Collections.Generic.IEnumerable`1: ジェネリックインターフェイス型
System.EventHandler: デリゲート型
System.Action`1: ジェネリックデリゲート型

型の種類・分類については型の種類・サイズ・精度・値域値型と参照型を合わせて参照してください。

§2.3.1.1 型がデリゲート型かどうか調べる

Typeクラスには型がデリゲート型かどうかを調べるIsDelegateのようなプロパティは用意されていません。 Typeがデリゲート型を表すかどうかを調べたい場合は以下のような方法をとることができます。

Typeクラスを使って型がデリゲート型かどうかを調べる
using System;

class Sample {
  static bool IsDelegate(Type t)
  {
    // 型がDelegate型またはDelegate型の派生クラスの場合はデリゲート型と判定する
    return t.IsSubclassOf(typeof(Delegate)) || t.Equals(typeof(Delegate));
  }

  static void Main()
  {
    foreach (var t in new[] {
      typeof(EventHandler),
      typeof(Action<,>),
      typeof(Delegate),
      typeof(MulticastDelegate),
      typeof(ICloneable),
    }) {
      Console.WriteLine("IsDelegate({0}) = {1}", t, IsDelegate(t));
    }
  }
}
実行結果
IsDelegate(System.EventHandler) = True
IsDelegate(System.Action`2[T1,T2]) = True
IsDelegate(System.Delegate) = True
IsDelegate(System.MulticastDelegate) = True
IsDelegate(System.ICloneable) = False

§2.3.1.2 実装しているインターフェイスを調べる

型が実装しているすべてのインターフェイスを取得するにはGetInterfacesメソッドを使うことができます。

Typeクラスを使って型が実装しているインターフェイスを調べる
using System;
using System.Collections.Generic;
using System.Reflection;

class Sample {
  static void Main()
  {
    var t = typeof(List<int>);

    // List<int>クラスが実装するインターフェイスをすべて取得して表示する
    foreach (var ti in t.GetInterfaces()) {
      Console.WriteLine(ti);
    }
    Console.WriteLine();
  }
}
実行結果
System.Collections.Generic.IList`1[System.Int32]
System.Collections.Generic.IReadOnlyList`1[System.Int32]
System.Collections.ICollection
System.Collections.Generic.ICollection`1[System.Int32]
System.Collections.IEnumerable
System.Collections.Generic.IEnumerable`1[System.Int32]
System.Collections.Generic.IReadOnlyCollection`1[System.Int32]
System.Collections.IList

一方、型が特定のインターフェイスを実装しているかどうかについては、GetInterfaceメソッドでインターフェイス名を文字列で指定して調べるか、IsAssignableFromメソッドを使ってインターフェイス型への代入を行えるかどうかを調べることによって知ることができます。

Typeクラスを使って型が特定のインターフェイスを実装しているか・代入可能かどうか調べる
using System;
using System.Collections.Generic;
using System.Reflection;

class Sample {
  static void Main()
  {
    var t = typeof(List<int>);

    // List<int>がIEnumerable<T>インターフェイスを実装しているか調べる
    Console.WriteLine(t.GetInterface("System.Collections.Generic.IEnumerable`1") != null);

    // List<int>がIEnumerable<int>インターフェイスに代入可能かを調べる
    Console.WriteLine(typeof(IEnumerable<int>).IsAssignableFrom(t));
  }
}
実行結果
True
True

ジェネリックインターフェイス型を指定する場合、オープンジェネリック型とクローズジェネリック型の違いに注意する必要があります。 ジェネリック型の文字列表記にも注意する必要があります。 詳しくは§.ジェネリック型の型情報の解説を参照してください。

型情報からインターフェイスの代入可能性を判定する方法については型・インスタンスが派生クラスかどうかを調べるでも詳しく解説しています。

§2.3.1.3 型パラメータ情報の取得

ジェネリック型の型パラメータ(例えばList<int>における< >内の部分)を取得したい場合はGetGenericArgumentsメソッドを使うことができます。 型パラメータの情報もTypeクラスとして取得されます。 Typeが型パラメータを表す場合、IsGenericParameterプロパティがtrueとなります。

GetGenericArgumentsメソッドは、オープンジェネリック型・クローズジェネリック型のどちらの型情報に対しても用いることができます。

ジェネリック型における型パラメータの型情報を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    ClassifyGenericType(typeof(List<>));
    ClassifyGenericType(typeof(List<int>));
    ClassifyGenericType(typeof(List<string>));
    ClassifyGenericType(typeof(Dictionary<,>));
    ClassifyGenericType(typeof(Dictionary<string, int>));
    ClassifyGenericType(typeof(Action<,,,>));
    ClassifyGenericType(typeof(Action<int, float, double, string>));
    ClassifyGenericType(typeof(int));
  }

  static void ClassifyGenericType(Type t)
  {
    Console.Write("{0}: ", t.Name);

    if (t.IsGenericType) {
      // 型がジェネリック型の場合、ジェネリック型定義か構築ジェネリック型か調べる
      if (t.IsGenericTypeDefinition)
        Console.Write("ジェネリック型定義; ");
      else
        Console.Write("構築ジェネリック型; ");

      // ジェネリック型の型パラメータの型情報を取得して表示する
      foreach (var ta in t.GetGenericArguments()) {
        Console.Write("{0}, ", ta.Name);
      }
      Console.WriteLine();
    }
    else {
      Console.WriteLine("(非ジェネリック型)");
    }
  }
}
実行結果
List`1: ジェネリック型定義; T, 
List`1: 構築ジェネリック型; Int32, 
List`1: 構築ジェネリック型; String, 
Dictionary`2: ジェネリック型定義; TKey, TValue, 
Dictionary`2: 構築ジェネリック型; String, Int32, 
Action`4: ジェネリック型定義; T1, T2, T3, T4, 
Action`4: 構築ジェネリック型; Int32, Single, Double, String, 
Int32: (非ジェネリック型)

ジェネリックメソッドの型パラメータも同様に取得することができます。 この場合、まず対象となるジェネリックメソッドのMethodInfoを取得し、その後MethodInfo.GetGenericArgumentsメソッドを呼び出します。

ジェネリックメソッドの型パラメータを取得する
using System;
using System.Reflection;

class C {
  public void M<TArg1, TArg2>(TArg1 arg1, TArg2 arg2)
  {
  }
}

class Sample {
  static void Main()
  {
    // メソッドC.M<T>のメソッド情報を取得する
    MethodInfo m = typeof(C).GetMethod("M");

    Console.Write("{0}: ", m.Name);

    foreach (var ta in m.GetGenericArguments()) {
      Console.Write("{0}, ", ta.Name);
    }
    Console.WriteLine();
  }
}
実行結果
M: TArg1, TArg2,

この例で使用しているGetMethodメソッドについては後述の§.メンバ情報の取得 (MemberInfo)で解説しています。


型パラメータの宣言されている型(またはメソッド)を参照する場合は次のプロパティによって取得することができます。 ジェネリック型の型パラメータを表すTypeではDeclaringTypeプロパティ、ジェネリックメソッドの型パラメータを表すTypeではDeclaringMethodプロパティを参照することにより、宣言されている型を取得できます。

型パラメータが宣言されている型あるいはメソッドを取得する
using System;
using System.Collections.Generic;
using System.Reflection;

class C {
  public void M<TArg1, TArg2>(TArg1 arg1, TArg2 arg2)
  {
  }
}

class Sample {
  static void Main()
  {
    // ジェネリック型List<T>の型パラメータTの型情報を取得する
    Type arg1 = typeof(List<>).GetGenericArguments()[0];

    // 型パラメータが宣言されている型を表示する
    Console.WriteLine("{0} -> {1}", arg1, arg1.DeclaringType);

    // ジェネリックメソッドC.M<T>の型パラメータTの型情報を取得する
    Type arg2 = typeof(C).GetMethod("M").GetGenericArguments()[0];

    // 型パラメータが宣言されているメソッドを表示する
    Console.WriteLine("{0} -> {1}", arg2, arg2.DeclaringMethod);
  }
}
実行結果
T -> System.Collections.Generic.List`1[T]
TArg1 -> Void M[TArg1,TArg2](TArg1, TArg2)

§2.3.2 メンバ情報の取得 (MemberInfo)

Typeクラスでは次のようなメソッドによって型で定義されているメンバの情報を取得することができます。 これらのメソッドを使うことによって、型情報からメソッドやフィールドなどのメンバを参照することができ、そこから更にメソッドの呼び出しやフィールドの値の取得など(詳細は§.MemberInfo派生クラスを使ったメンバの呼び出しで後述)を行うことができます。

Typeクラスのメソッド
メソッド 動作
GetMember
GetMembers
指定された名前のメンバ、あるいはすべてのメンバを取得する。 メンバ情報はMemberInfoクラスで返される。
GetConstructor
GetConstructors
指定された引数リストのコンストラクタ、あるいはすべてのメンバを取得する。 ConstructorInfoクラスが返される。
GetEvent
GetEvents
指定された名前のイベント、あるいはすべてのイベントを取得する。 EventInfoクラスが返される。
GetField
GetFields
指定された名前のフィールド、あるいはすべてのフィールドを取得する。 FieldInfoクラスが返される。
GetMethod
GetMethods
指定された名前・引数リストのメソッド、あるいはすべてのメソッドを取得する。 MethodInfoクラスが返される。
GetProperty
GetProperties
指定された名前のプロパティ、あるいはすべてのプロパティを取得する。 PropertyInfoクラスが返される。

戻り値として返されるMethodInfoなどのクラスはすべてMemberInfoクラスから派生したクラスとなっています。 MemberTypeプロパティを参照するとメンバの種類をMemberTypes列挙体で取得することができます。

クラスの型情報からすべてのメンバを取得する
using System;
using System.Reflection;

class C {
  public void M() {} // メソッド
  public int P { get; set; } // プロパティ
  public string F = null; // フィールド
}

class Sample {
  static void Main()
  {
    var t = typeof(C);

    // クラスCのすべてのメンバを取得して表示する
    foreach (MemberInfo m in t.GetMembers()) {
      Console.WriteLine("{0}\t{1}", m.MemberType, m);
    }
    Console.WriteLine();

    // 名前がFのフィールドを取得する
    FieldInfo f = t.GetField("F");

    Console.WriteLine(f);
  }
}
実行結果
Method	Int32 get_P()
Method	Void set_P(Int32)
Method	Void M()
Method	Boolean Equals(System.Object)
Method	Int32 GetHashCode()
Method	System.Type GetType()
Method	System.String ToString()
Constructor	Void .ctor()
Property	Int32 P
Field	System.String F

System.String F

このようにして取得したMemberInfoを使うことにより、メソッドの呼び出しやフィールドの取得・設定などの操作を行うことができます。

§2.3.2.1 メンバ情報の取得とBindingFlags

GetMembersなどメンバ情報を取得するメソッドでは、特に引数を指定しない場合はパブリックなインスタンスメンバのみを返します。 非パブリックやクラスのメンバ(静的メンバ)を取得したい場合はBindingFlagsを指定する必要があります。 BindingFlagsには次のような値が用意されています。

メンバ情報の取得とBindingFlags
意味 備考
BindingFlags.Static 静的メンバを対象とする どちらか一方または両方を指定する必要があります
BindingFlags.Instance インスタンスメンバを対象とする
BindingFlags.Public パブリックメンバを対象とする どちらか一方または両方を指定する必要があります
BindingFlags.NonPublic 非パブリックメンバを対象とする
BindingFlags.IgnoreCase メンバの名前を指定する際に、大文字小文字の違いを無視する
BindingFlags.DeclaredOnly その型で宣言されているメンバのみを対象とする (継承されたメンバを含めない)
BindingFlags.FlattenHierarchy 継承された静的メンバを対象とする
意味 備考
クラスの型情報から非パブリックなメンバのみを取得して表示する
using System;
using System.Reflection;

class C {
  public void M1() {}
  protected void M2() {}
  private void M3() {}
  private static void M4() {}

  public string F1 = null;
  private string F2 = null;
  private static string F3 = null;
}

class Sample {
  static void Main()
  {
    var t = typeof(C);

    // 非パブリックなインスタンスメンバのみを取得して列挙する
    foreach (MemberInfo m in t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance)) {
      Console.WriteLine("{0}\t{1}", m.MemberType, m);
    }
  }
}
実行結果
Method	Void M2()
Method	Void M3()
Method	Void Finalize()
Method	System.Object MemberwiseClone()
Field	System.String F2
継承を除外して対象のクラスで宣言されているメンバのみを取得する
using System;
using System.Reflection;

class C1 {
  public void M1() {}
}

class C2 : C1 {
  public void M2() {}
}

class Sample {
  static void Main()
  {
    var t = typeof(C2);

    // C2クラスで宣言されているメンバのみを取得して列挙する
    // (C2クラスが継承しているメンバを除外して取得する)
    foreach (MemberInfo m in t.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance)) {
      Console.WriteLine("{0}\t{1}", m.MemberType, m);
    }
  }
}
実行結果
Method	Void M2()
Constructor	Void .ctor()

§2.3.2.2 プロパティのアクセサメソッドの取得

PropertyInfoからはプロパティのgetアクセサ/setアクセサに対応するMethodInfoをそれぞれ個別に取得することができます。 それぞれGetMethodプロパティ/SetMethodプロパティプロパティを参照することでMethodInfoとして取得できます。 読み取り専用/書き込み専用プロパティの場合はnull/Nothingが返されます。

PropertyInfoからプロパティのアクセサメソッドを取得する
using System;
using System.Reflection;

class C {
  public int P {get; set;}
}

class Sample {
  static void Main()
  {
    var t = typeof(C);

    PropertyInfo p = t.GetProperty("P"); // プロパティPのPropertyInfoを取得

    MethodInfo getter = p.GetMethod; // プロパティPのgetアクセサメソッドを取得
    MethodInfo setter = p.SetMethod; // プロパティPのsetアクセサメソッドを取得

    Console.WriteLine(getter);
    Console.WriteLine(setter);
  }
}
実行結果
Int32 get_P()
Void set_P(Int32)

§2.3.2.3 引数リストを指定した取得

メソッドではオーバーロードによって同じ名前のメソッドが複数存在する場合があり、またコンストラクタでは名前を持たないため名前で区別することができません。 このようにオーバーロードが存在し、メンバ名だけでは特定のメンバを限定できない場合は、引数リストを指定することによって取得対象を限定することができます。

引数リストは次の例のようにTypeの配列で指定します。 引数がない場合は空の配列か、Type.EmptyTypesフィールドを指定します。 引数の型・数・順序のすべてに一致するものがなければnull/Nothingが返されます。

引数リストを指定してメソッドのオーバーロードを限定して取得する
using System;
using System.Reflection;

class C {
  public void M(int x) {}
  public void M(int x, int y) {}
}

class Sample {
  static void Main()
  {
    var t = typeof(C);

    // int型の引数を1つとるメソッドMのオーバーロードを取得する
    MethodInfo m1 = t.GetMethod("M", new[] {typeof(int)});

    // int型の引数を2つとるメソッドMのオーバーロードを取得する
    MethodInfo m2 = t.GetMethod("M", new[] {typeof(int), typeof(int)});

    Console.WriteLine(m1);
    Console.WriteLine(m2);
  }
}
実行結果
Void M(Int32)
Void M(Int32, Int32)
引数リストを指定して一致するコンストラクタを取得する
using System;
using System.Reflection;

class C {
  public C(int x) {}
  public C(string x, int y) {}
  public C() {}
}

class Sample {
  static void Main()
  {
    var t = typeof(C);

    // int型の引数を一つとるコンストラクタを取得する
    ConstructorInfo ctor1 = t.GetConstructor(new[] {typeof(int)});

    // string型とint型の引数をとるコンストラクタを取得する
    ConstructorInfo ctor2 = t.GetConstructor(new[] {typeof(string), typeof(int)});

    // 引数のないコンストラクタを取得する
    ConstructorInfo ctor3 = t.GetConstructor(Type.EmptyTypes);

    Console.WriteLine(ctor1);
    Console.WriteLine(ctor2);
    Console.WriteLine(ctor3);
  }
}
実行結果
Void .ctor(Int32)
Void .ctor(String, Int32)
Void .ctor()

§3 インスタンスの操作

.NET Frameworkではリフレクションによって型情報を取得するだけでなく、取得した型情報を使ってインスタンスを作成したり、フィールドの書き換えやメソッドの呼び出しなどインスタンスに対する操作を行うこともできるようになっています。

§3.1 インスタンスの作成 (Activator.CreateInstance)

Activator.CreateInstanceメソッドを使用すると、型情報を使って動的にインスタンスを作成することができます。 このメソッドではインスタンスを生成する際、コンストラクタに渡す引数を指定することができます。 コンストラクタに渡す引数はobject型の配列で指定します。 指定された引数に応じて適切なコンストラクタが自動的に呼び出されます。 当然、抽象クラスのインスタンスを作成しようとした場合や、引数と一致するコンストラクタがない場合は例外がスローされます。

Activator.CreateInstanceを使って型情報からインスタンスを作成する
using System;
using System.Reflection;

class C {
  private int f;

  public C()
  {
    this.f = 0;
  }

  public C(int f)
  {
    this.f = f;
  }

  public override string ToString()
  {
    return string.Format("f = {0}", f);
  }
}

class Sample {
  static void Main()
  {
    object inst;

    // デフォルト(引数なし)のコンストラクタを使ってクラスCのインスタンスを作成する
    inst = Activator.CreateInstance(typeof(C));

    Console.WriteLine(inst);

    // int型の引数を1つとるコンストラクタを使ってクラスCのインスタンスを作成する
    object[] args = new object[] {42};

    inst = Activator.CreateInstance(typeof(C), BindingFlags.CreateInstance, null, args, null);

    Console.WriteLine(inst);
  }
}
実行結果
f = 0
f = 42

CreateInstanceメソッドで作成したインスタンスはobjectで返されます。 そのためインスタンスを使用する際、既知の型の場合はその型にキャストするか、あるいはobject型のまま後述する方法でリフレクションによって操作を行います。

Activator.CreateInstanceで作成したインスタンスをリフレクションによって操作する
using System;
using System.Reflection;

class C {
  public void M()
  {
    Console.WriteLine("Hello, world!");
  }
}

class Sample {
  static void Main()
  {
    Type t = typeof(C);

    object inst = Activator.CreateInstance(t);

    // InvokeMemberメソッドを使って作成したインスタンスのメソッドMを呼び出す
    t.InvokeMember("M", BindingFlags.InvokeMethod, null, inst, null);

    // インスタンスをキャストして直接メソッドMを呼び出す
    C c = (C)inst;

    c.M();
  }
}
実行結果
Hello, world!
Hello, world!

このように、型情報が取得できれば動的にインスタンスを作成して操作することができます。 また、動的にアセンブリを読み込み、読み込んだアセンブリに含まれるインスタンスを作成するといったことも可能です。

Activator.CreateInstanceメソッドのほか、後述するType.InvokeMemberメソッドConstructorInfo.Invokeメソッドを使うことによって型情報からインスタンスを作成することもできます。

§3.1.1 Activator.CreateInstanceとパフォーマンス

Activator.CreateInstanceによるインスタンスの作成は、コンストラクタを直接呼び出す場合と比較するとオーバーヘッドが大きいため、パフォーマンスに影響します。 インスタンス作成にかかる時間を計測して比較すると次のようになります。 コンストラクタを使ったインスタンスが行える場合は、そちらを優先すべきです。

Windows 7 + .NET Framework 4.5での比較結果
               new(): 00:00:00.0213256
      CreateInstance: 00:00:00.1682794
               new(): 00:00:00.0206288
      CreateInstance: 00:00:00.1625131
               new(): 00:00:00.0195052
      CreateInstance: 00:00:00.1609271
               new(): 00:00:00.0203098
      CreateInstance: 00:00:00.1609503
               new(): 00:00:00.0205671
      CreateInstance: 00:00:00.1606695
Ubuntu 14.04 + Mono 3.10での比較結果
               new(): 00:00:00.0152867
      CreateInstance: 00:00:00.4069424
               new(): 00:00:00.0102610
      CreateInstance: 00:00:00.4079876
               new(): 00:00:00.0118173
      CreateInstance: 00:00:00.4059866
               new(): 00:00:00.0105222
      CreateInstance: 00:00:00.4039274
               new(): 00:00:00.0103997
      CreateInstance: 00:00:00.4043431
計測に用いたコード
using System;
using System.Diagnostics;

public class C {
  public C() {}
}

public class Test {
  static void Main()
  {
    for (var act = 0; act < 5; act++) {
      var sw1 = Stopwatch.StartNew();

      for (var i = 0; i < 1000 * 1000; i++) {
        new C();
      }

      sw1.Stop();

      Console.WriteLine("{0, 20}: {1}", "new()", sw1.Elapsed);

      var t = typeof(C);
      var sw2 = Stopwatch.StartNew();

      for (var i = 0; i < 1000 * 1000; i++) {
        Activator.CreateInstance(t);
      }

      sw2.Stop();

      Console.WriteLine("{0, 20}: {1}", "CreateInstance", sw2.Elapsed);
    }
  }
}

インスタンスの生成に限らず、一般にリフレクションによる操作には大きなオーバーヘッドが伴います。

§3.2 メンバの呼び出し

リフレクションによって型のメンバの呼び出しを行うことができます。 たとえば、メソッドの呼び出しやフィールド・プロパティの値の取得・設定ができます。 メンバの呼び出しには、Type.InvokeMemberメソッドによってメンバ名を指定して呼び出す方法と、Typeから取得したMethodInfo・PropertyInfoなどのメソッドを使って呼び出す方法があります。

§3.2.1 Type.InvokeMemberを使ったメンバの呼び出し

Type.InvokeMemberメソッドを使うと型情報とメンバ名を使ってメンバの呼び出しを行うことができます。 InvokeMemberメソッドでは、メソッドの呼び出しのほか、プロパティの値の取得・設定、フィールドの値の取得・設定を行うことができます。 またコンストラクタを呼び出すことによってインスタンスを作成することもできます。 呼び出すメンバに応じて、次のBindingFlagsのいずれかを指定します。

InvokeMemberメソッドとBindingFlags
BindingFlags InvomeMemberメソッドの動作
BindingFlags.InvokeMethod 指定した名前・引数と一致するメソッドを呼び出して戻り値を取得する
BindingFlags.SetProperty 指定した名前のプロパティに値を設定する
BindingFlags.GetProperty 指定した名前のプロパティの値を取得する
BindingFlags.SetField 指定した名前のフィールドに値を設定する
BindingFlags.GetField 指定した名前のフィールドの値を取得する
BindingFlags.CreateInstance 指定した引数と一致するコンストラクタを呼び出してインスタンスを作成する

これらの値に加えて、非パブリックメンバを呼び出したり、メンバ名の大文字小文字の違いを無視して呼び出せるようにするためにBindingFlags.NonPublicやBindingFlags.IgnoreCaseなどを組み合わせて指定することもできます。

InvokeMemberメソッドでは、第4引数に操作の対象となるインスタンスを指定します。 静的メンバ(static/Shared)の場合はインスタンスを指定する代わりにnull/Nothingを指定します。 メソッドおよびコンストラクタの呼び出し時に渡す引数や、フィールド・プロパティに設定する値は、object型の配列に格納して第5引数に指定します。 引数がない場合はnull/Nothingを指定します。

また、BindingFlags.InvokeMethodで呼び出したメソッドの戻り値や、BindingFlags.CreateInstanceで作成したインスタンス、BindingFlags.GetPropertyおよびBindingFlags.GetFieldで取得したプロパティ・フィールドの値はInvokeMemberメソッドの戻り値として取得することができます。

Type.InvokeMemberを使ってメンバの呼び出しを行う
using System;
using System.Reflection;

class C {
  public string M(string s)
  {
    return s.ToUpper();
  }

  public int P { get; set; }

  public double F = 0.0;
}

class Sample {
  static void Main()
  {
    Type t = typeof(C);

    object inst = Activator.CreateInstance(t);

    // インスタンスのメソッドMを呼び出す
    object ret = t.InvokeMember("M", BindingFlags.InvokeMethod, null, inst, new object[] {"Hello, world!"});

    Console.WriteLine(ret); // メソッドの戻り値を表示する

    // プロパティPに値42を設定する
    t.InvokeMember("P", BindingFlags.SetProperty, null, inst, new object[] {42});

    // プロパティPの値を取得する
    object p = t.InvokeMember("P", BindingFlags.GetProperty, null, inst, null);

    Console.WriteLine("P = {0}", p);

    // フィールドFに値3.14を設定する
    t.InvokeMember("F", BindingFlags.SetField, null, inst, new object[] {3.14});

    // フィールドFの値を取得する
    object f = t.InvokeMember("F", BindingFlags.GetField, null, inst, null);

    Console.WriteLine("F = {0}", f);
  }
}
実行結果
HELLO, WORLD!
P = 42
F = 3.14
Type.InvokeMemberを使ってインスタンスの作成を行う
using System;
using System.Reflection;

class C {
  private string str = "Hello, world!";

  public C()
  {
  }

  public C(string str)
  {
    this.str = str;
  }

  public override string ToString()
  {
    return str;
  }
}

class Sample {
  static void Main()
  {
    Type t = typeof(C);

    object inst;

    // コンストラクタに渡す引数を指定せずにインスタンスを作成する
    // (引数のないコンストラクタを使ってインスタンスを作成する)
    inst = t.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);

    Console.WriteLine(inst);

    // コンストラクタに渡す引数を指定してインスタンスを作成する
    // (引数のあるコンストラクタを使ってインスタンスを作成する)
    inst = t.InvokeMember(null, BindingFlags.CreateInstance, null, null, new object[] {"Hello, instance!"});

    Console.WriteLine(inst);
  }
}
実行結果
Hello, world!
Hello, instance!

メンバ呼び出しの結果例外が発生した場合、TargetInvocationExceptionがスローされます。 実際にスローされた例外を取得する方法などについて詳細は§.呼び出し時の例外 (TargetInvocationException)を参照してください。

§3.2.2 MemberInfo派生クラスを使ったメンバの呼び出し

MethodInfoやPropertyInfoなどのMemberInfo派生クラスを取得し、以下のメソッドを使うことでもメンバの呼び出しを行うことができます。 MethodInfoやPropertyInfoの取得にはGetMethodやGetPropertyなどのメソッドを使用します。

メンバの呼び出しを行うメソッド
メソッド 動作
MethodInfo.Invoke 指定した引数と一致するメソッドを呼び出して戻り値を取得する
PropertyInfo.SetValue プロパティに値を設定する
PropertyInfo.GetValue プロパティの値を取得する
FieldInfo.SetValue フィールドに値を設定する
FieldInfo.GetValue フィールドの値を取得する
ConstructorInfo.Invoke 指定した引数と一致するコンストラクタを呼び出してインスタンスを作成する

Type.InvokeMemberメソッドによるメンバ呼び出しの場合と同様、操作の対象となるインスタンスを引数で指定します。 静的メンバ(static/Shared)の場合はインスタンスを指定する代わりにnull/Nothingを指定します。

また、メソッドおよびコンストラクタの呼び出し時に渡す引数は、object型の配列に格納して指定します。 フィールド・プロパティに設定する値をobject型の配列に格納して指定するInvokeMemberメソッドとは異なり、PropertyInfo.SetValueメソッド・FieldInfo.SetValueメソッドでは設定する値を直接引数として指定することができます。

MemberInfoを使ってメンバの呼び出しを行う
using System;
using System.Reflection;

class C {
  public string M(string s)
  {
    return s.ToUpper();
  }

  public double F = 0.0;
}

class Sample {
  static void Main()
  {
    Type t = typeof(C);

    object inst = Activator.CreateInstance(t);

    // メソッドMのMethodInfoを取得する
    MethodInfo m = t.GetMethod("M");

    // メソッドを呼び出す
    object ret = m.Invoke(inst, new object[] {"Hello, world!"});

    Console.WriteLine(ret); // メソッドの戻り値を表示する

    // フィールドFのFieldInfoを取得する
    FieldInfo f = t.GetField("F");

    // フィールドに値3.14を設定する
    f.SetValue(inst, 3.14);

    // フィールドの値を取得する
    Console.WriteLine("F = {0}", f.GetValue(inst));
  }
}
実行結果
HELLO, WORLD!
F = 3.14

これらのメソッドでは、メンバの呼び出しを行うインスタンスや設定する値はすべてobject型の引数に渡します。 この際、構造体など値型インスタンスのフィールド・プロパティを設定する際には問題が生じる場合があります。 この問題と回避方法に関してはリフレクションを使って構造体フィールドに値を設定する(FieldInfo.SetValue)で解説しているので、合わせてご覧ください。

§3.2.2.1 MethodInfoを使ったメソッド呼び出し

§3.2.2.1.1 ref/ByRefパラメータ

メソッドの引数にout/ref修飾子、ByRef修飾子が設定されている場合、引数は参照渡しとなり、引数の値を別の値に置き換えることができます。 MethodInfo.Invokeメソッドを使ってメソッドを呼び出す場合、参照渡しによって置き換えられた値は引数parametersに渡した配列に格納されます。

MethodInfoでのメソッド呼び出しで参照渡しの引数を参照する
using System;
using System.Reflection;

class C {
  public void M(int x, ref int y)
  {
    Console.WriteLine("x = {0}", x);
    Console.WriteLine("y = {0}", y);

    x = 3; // 値渡し引数の値を変更する (呼び出し元には反映されない)
    y = 3; // 参照渡し引数の値を変更する (呼び出し元に反映される)
  }
}

class Sample {
  static void Main()
  {
    Type t = typeof(C);

    object inst = Activator.CreateInstance(t);

    MethodInfo m = t.GetMethod("M");

    // メソッド呼び出しに使用する引数の配列
    object[] args = new object[2] {42, 42};

    m.Invoke(inst, args);

    // 置き換えられた値を参照する
    Console.WriteLine(args[0]);
    Console.WriteLine(args[1]);
  }
}
実行結果
x = 42
y = 42
42
3

§3.2.2.1.2 呼び出し時の例外 (TargetInvocationException)

MethodInfo.Invokeメソッドによって呼び出したメソッドが例外をスローした場合、TargetInvocationExceptionがスローされます。 呼び出したメソッド内で発生した例外はTargetInvocationExceptionにラップされるため、実際にメソッドがスローした例外はInnerExceptionプロパティを参照することで取得することができます。

MethodInfoでのメソッド呼び出しでメソッドがスローした例外をキャッチする
using System;
using System.Reflection;

class C {
  public void M()
  {
    throw new NotImplementedException();
  }
}

class Sample {
  static void Main()
  {
    Type t = typeof(C);

    object inst = Activator.CreateInstance(t);

    MethodInfo m = t.GetMethod("M");

    try {
      m.Invoke(inst, null);
    }
    // 呼び出したメソッドで例外が発生した場合、TargetInvocationExceptionがスローされる
    catch (TargetInvocationException ex) {
      // メソッドがスローした例外を取得する
      Console.WriteLine(ex.InnerException);
    }
  }
}
実行結果
System.NotImplementedException: メソッドまたは操作は実装されていません。
   場所 C.M()

PropertyInfo.GetValue/SetValueおよびConstructorInfo.Invokeによってプロパティ・コンストラクタを呼び出した結果例外が発生した場合も、同様にTargetInvocationExceptionがスローされます。

§3.2.2.2 PropertyInfoを使ったプロパティ・インデクサの操作

PropertyInfo.GetValue/SetValueメソッドを使うとプロパティの値の取得・設定を行うことができます。 インデクサ(既定のプロパティ、引数を持つプロパティ)に対する値の設定・取得もこのメソッドで行います。

.NET Framework 4.0以前の場合、PropertyInfo.GetValue/SetValueメソッドでは引数objの他に引数indexも指定する必要があります。 この引数はプロパティがインデクサの場合にインデックスとして使用される値を指定するためのものです。 プロパティがインデクサでない場合は引数indexnull/Nothingを指定します。

.NET Framework 4.5以降では引数indexを必要としないオーバーロードが用意されているため、インデクサではないプロパティではインデックスの指定を省略することができます。

PropertyInfoを使ってプロパティの値を取得・設定する
using System;
using System.Reflection;

class C {
  public int P { get; set; }
}

class Sample {
  static void Main()
  {
    Type t = typeof(C);

    object inst = Activator.CreateInstance(t);

    // プロパティPのPropertyInfoを取得する
    PropertyInfo p = t.GetProperty("P");

    // プロパティに値42を設定する
    p.SetValue(inst, 42); // .NET Framework 4.5以降の場合
    //p.SetValue(inst, 42, null); // .NET Framework 4.0以前の場合

    // プロパティの値を取得する
    Console.WriteLine("P = {0}", p.GetValue(inst)); // .NET Framework 4.5以降の場合
    //Console.WriteLine("P = {0}", p.GetValue(inst, null)); // .NET Framework 4.0以前の場合
  }
}
実行結果
P = 42

プロパティ参照の結果例外が発生した場合、TargetInvocationExceptionがスローされます。 実際にスローされた例外を取得する方法などについて詳細は§.呼び出し時の例外 (TargetInvocationException)を参照してください。


以下はPropertyInfoを使ってインデクサの値を取得・設定する例です。 C#では特に指定しない場合、インデクサの名前はデフォルトでItemとなりますが、IndexerName属性によって変更可能である点に注意してください。

PropertyInfoを使ってインデクサの値を取得・設定する
using System;
using System.Reflection;

class C {
  //[System.Runtime.CompilerServices.IndexerName("Item")]
  public int this[int index] {
    get { return arr[index]; }
    set { arr[index] = value; }
  }

  private int[] arr = new int[2];
}

class Sample {
  static void Main()
  {
    C inst = new C();

    inst[0] = 42;
    inst[1] = 0;

    Type t = inst.GetType();

    // インデクサItemのPropertyInfoを取得する
    PropertyInfo p = t.GetProperty("Item");

    // インデクサのインデックス1に値3を設定する
    p.SetValue(inst, 3, new object[] {1});

    // インデクサからインデックス0と1の値を取得する
    Console.WriteLine(p.GetValue(inst, new object[] {0}));
    Console.WriteLine(p.GetValue(inst, new object[] {1}));
  }
}
実行結果
42
3

§3.2.2.3 ConstructorInfoを使ったインスタンスの作成

以下はConstructorInfoを使ってコンストラクタを呼び出し、インスタンスを作成・初期化する例です。 ConstructorInfo.Invokeメソッドを呼び出す際にはインスタンスを指定する必要がない点、作成されたインスタンスは戻り値として返される点を除けば、MethodInfo.Invokeメソッドと変わりありません。

ConstructorInfoを使ってコンストラクタを呼び出し、インスタンスを作成する
using System;
using System.Reflection;

class C {
  private string str = "Hello, world!";

  public C()
  {
  }

  public C(string str)
  {
    this.str = str;
  }

  public override string ToString()
  {
    return str;
  }
}

class Sample {
  static void Main()
  {
    Type t = typeof(C);

    object inst;

    // 引数を持たないコンストラクタのConstructorInfoを取得する
    ConstructorInfo c1 = t.GetConstructor(Type.EmptyTypes);

    // コンストラクタを呼び出してインスタンスを作成する
    inst = c1.Invoke(null);

    Console.WriteLine(inst);

    // string型の引数を1つとるコンストラクタのConstructorInfoを取得する
    ConstructorInfo c2 = t.GetConstructor(new Type[] {typeof(string)});

    // コンストラクタを呼び出してインスタンスを作成する
    inst = c2.Invoke(new object[] {"Hello, instance!"});

    Console.WriteLine(inst);
  }
}
実行結果
Hello, world!
Hello, instance!

コンストラクタ呼び出しの結果例外が発生した場合、TargetInvocationExceptionがスローされます。 実際にスローされた例外を取得する方法などについて詳細は§.呼び出し時の例外 (TargetInvocationException)を参照してください。

§3.2.2.4 MethodInfoからデリゲートを取得する

Delegate.CreateDelegateメソッドを使うとMethodInfoをデリゲートに変換することができ、デリゲートを介してメソッドの呼び出しを行えるようになります。

CreateDelegateメソッドの引数には、取得したいデリゲートの型を指定します。 デリゲートの型とMethodInfoが表すメソッドのシグネチャは一致している必要があります。 また、インスタンスメソッドの場合は呼び出し対象のインスタンスを指定します。 クラスメソッド(静的メソッド)の場合はインスタンスを指定する必要はありません。 CreateDelegateメソッドの戻り値はDelegateなので、作成したデリゲートを呼び出す場合は適切な型にキャストしてから呼び出す必要があります。

MethodInfoからデリゲートを作成し、デリゲートを介してメソッドを呼び出す
using System;
using System.Reflection;

class C {
  public void M1(string s)
  {
    Console.WriteLine(s);
  }

  public static void M2(string s)
  {
    Console.WriteLine(s);
  }
}

class Sample {
  static void Main()
  {
    C inst = new C();

    Type t = typeof(C);

    // インスタンスメソッドM1のMethodInfoからデリゲートを作成する
    MethodInfo m1 = t.GetMethod("M1");
    Action<string> a1 = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), inst, m1);

    a1("Hello, world!"); // 作成したデリゲートを介してメソッドを呼び出す

    // クラスメソッドM2のMethodInfoからデリゲートを作成する
    MethodInfo m2 = t.GetMethod("M2");
    Action<string> a2 = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), m2);

    a2("Hello, world!");
  }
}
実行結果
Hello, world!
Hello, world!

§3.2.2.5 デリゲートからMethodInfoを取得する

デリゲート型ではMethodプロパティを参照することで呼び出し対象となるメソッドのMethodInfoを取得することができます。

デリゲートからMethodInfoを取得し、対象となっているメソッドを呼び出す
using System;

class C {
  public void M1() {}
  public static void M2() {}
}

class Sample {
  static void Main()
  {
    C inst = new C();

    // インスタンスメソッドM1のデリゲート
    Action a1 = (Action)inst.M1;

    // クラスメソッドM2のデリゲート
    Action a2 = (Action)C.M2;

    Console.WriteLine(a1.Method);
    Console.WriteLine(a2.Method);
  }
}
実行結果
Void M1()
Void M2()

他のデリゲートと連結されたデリゲート(マルチキャストデリゲート)では、GetInvocationListメソッドを使って連結されている個々のデリゲートを取得してからMethodプロパティを参照します。

マルチキャストデリゲートから個々のメソッドのMethodInfoを取得する
using System;
using System.Reflection;

class C {
  public void M1() {}
  public static void M2() {}
}

class Sample {
  static void Main()
  {
    C inst = new C();

    Action a = (Action)inst.M1;

    // デリゲートの連結
    a += C.M2;

    // 連結されたデリゲートから個々のデリゲートを列挙する
    foreach (Delegate d in a.GetInvocationList()) {
      Console.WriteLine(d.Method);
    }
  }
}
実行結果
Void M1()
Void M2()

デリゲートと呼び出されるメソッドに関してはデリゲートの機能 §.呼び出されるメソッド・インスタンスの取得 (Method, Target, GetInvocationList)でも解説しています。

§3.2.2.6 EventInfoを使ったイベントの発行

EventInfoクラスにはイベントを発行するメソッドは用意されていません。 そのためイベント発行時の動作と同等の処理を独自に記述する必要があります。 EventInfoクラスからは以下のような手順をとることでイベントの発行を行うことができます。

  1. イベントハンドラのデリゲートを格納しているフィールドのFieldInfoを取得する
  2. 取得したFieldInfoからイベントハンドラを格納しているMulticastDelegateを取得する
  3. MulticastDelegate.GetInvocationListメソッドを使ってイベントハンドラとして割り当てられているメソッドのリスト(MethodInfoの配列)を取得する
  4. 取得したMethodInfoひとつずつに対してメソッドの呼び出しを行う

C#とVBではイベントハンドラを格納しているフィールド名が異なる点に注意が必要です。 例えばイベント名がClickの場合、C#ではフィールドClick、VBではフィールドClickEventにイベントハンドラが格納されます。

上記の処理を具体的に実装すると次のようになります。

EventInfoを使ってイベントの発行を行う
using System;
using System.Reflection;

class C {
  public event EventHandler<EventArgs> E;
}

class Sample {
  static void Handler1(object sender, EventArgs e)
  {
    Console.WriteLine("Handler1");
  }

  static void Handler2(object sender, EventArgs e)
  {
    Console.WriteLine("Handler2");
  }

  static void Main()
  {
    C inst = new C();

    // イベントEにハンドラを割り当てる
    inst.E += Handler1;
    inst.E += Handler2;

    // イベントEのEventInfoを取得する
    EventInfo ev = inst.GetType().GetEvent("E");

    // EventInfoを使ってイベントを発行する
    Raise(inst, ev, EventArgs.Empty);
  }

  static void Raise<TEventArgs>(object sender, EventInfo ev, TEventArgs e)
  {
    var f = sender.GetType().GetField(ev.Name, BindingFlags.Instance | BindingFlags.NonPublic);
    var handler = (MulticastDelegate)f.GetValue(sender);

    if (handler == null)
      return;

    foreach (var h in handler.GetInvocationList()) {
      h.Method.Invoke(h.Target, new object[] {sender, e});
    }
  }
}
実行結果
Handler1
Handler2

MulticastDelegate.GetInvocationListメソッドについてはデリゲートの機能 §.呼び出されるメソッド・インスタンスの取得 (Method, Target, GetInvocationList)で解説しています。

§3.2.2.7 静的コンストラクタの呼び出し

Type.TypeInitializerプロパティを参照すると静的コンストラクタ(クラスコンストラクタ)のConstructorInfoを取得することができます。 (BindingFlags.StaticBindingFlags.NonPublicを指定してGetConstructorメソッドを呼び出しても静的コンストラクタを取得することができます)

通常、静的コンストラクタは自動的に一度だけ呼び出されるのみで、ユーザーコードからは呼び出すことはできませんが、TypeInitializerプロパティから取得したConstructorInfoを使うことで静的コンストラクタを任意のタイミングで呼び出すことができます。

Type.TypeInitializerプロパティから静的コンストラクタを取得して呼び出す
using System;
using System.Reflection;

class C {
  // 静的コンストラクタ
  static C()
  {
    Console.WriteLine("initialized");
  }
}

class Sample {
  static void Main()
  {
    Type t = typeof(C);

    // 静的コンストラクタのConstructorInfoを取得する
    ConstructorInfo cctor = t.TypeInitializer;

    Console.WriteLine(cctor);

    // 静的コンストラクタを呼び出す
    cctor.Invoke(null, null);
  }
}
実行結果
Void .cctor()
initialized
initialized

実行結果からも分かるとおり、静的コンストラクタが自動的に呼び出された分と、ConstructorInfoを使って呼び出した分の二回分が表示されています。 つまり、ConstructorInfoを使って静的コンストラクタを呼び出したことにより、クラスが再初期化されたことになります。

ほとんどの場合、このような最初期化を行う必要性はありません。 また静的コンストラクタは一度だけ呼び出されることを前提としていることから、静的コンストラクタを最初の初期化以外で呼び出すことはクラスの状態を破壊する可能性もあるため、推奨されません。

§4 動的な生成

リフレクションでは実行時における情報の取得だけでなく、実行時に型情報やメソッドの実装を動的に生成することもできます。 ここではそれらの方法について簡単に解説します。

§4.1 System.Reflection.Emit

System.Reflection.Emit名前空間TypeBuilderMethodBuilderなどのクラスを使うと型情報やメソッドの実装を動的に生成し、実行することができます。 ただし、メソッドの実装にはILGeneratorクラスを使ってILコード(intermediate language, 中間言語コード)を記述する必要があるため、ILに関する知識が必要となります。

以下の例ではSystem.Reflection.Emit名前空間のクラスを使って以下のようなクラスを動的に生成し、作成したクラスのメソッドを呼び出しています。

生成されるクラスのイメージ
public class HelloWorld {
  public void Print()
  {
    Console.WriteLine("Hello, world!");
  }
}
System.Reflection.Emit名前空間のクラスを使って動的にクラスとメソッドを生成する
using System;
using System.Reflection;
using System.Reflection.Emit;

class Sample {
  static void Main()
  {
    // アセンブリ名"DynamicAssmembly"で動的にアセンブリを生成する
    AssemblyName assmName = new AssemblyName("DynamicAssmembly");

    AssemblyBuilder assm = AppDomain.CurrentDomain.DefineDynamicAssembly(assmName, AssemblyBuilderAccess.Run);

    // 作成したアセンブリ内にモジュールを宣言する
    ModuleBuilder module = assm.DefineDynamicModule("DynamicModule");

    // 作成したモジュール内にクラス(object型を基底クラスとした型)を宣言する
    TypeBuilder type = module.DefineType("HelloWorld", TypeAttributes.Public, typeof(object));

    // 作成したクラス内に戻り値がvoid(なし)、引数なしのパブリックメソッドを宣言する
    MethodBuilder method = type.DefineMethod("Print", MethodAttributes.Public, typeof(void), Type.EmptyTypes);

    // メソッドの実装となるILコードを生成する
    ILGenerator generator = method.GetILGenerator();

    generator.Emit(OpCodes.Ldstr, "Hello, world!");
    generator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] {typeof(string)}));
    generator.Emit(OpCodes.Ret);

    // 生成した型情報を使ってインスタンスを作成し、メソッドを呼び出す
    Type t = type.CreateType();

    object inst = Activator.CreateInstance(t);

    t.InvokeMember("Print", BindingFlags.InvokeMethod, null, inst, null);
  }
}
実行結果
Hello, world!

§4.2 System.Linq.Expressions

System.Linq.Expressions名前空間のクラスを使って式木(expression tree, 式ツリーとも)を作成することによっても実行可能なコードを動的に生成することができます。 .NET Framework 4以降では、式だけでなく条件分岐やループなどのブロック構文も扱うことができるようになっています。 式木では生成したコードをデリゲートに変換(コンパイル)し、任意に呼び出すことができます。

System.Linq.Expressions名前空間のクラスを使って単純な式木を構築して呼び出す
using System;
using System.Linq.Expressions;

class Sample {
  static void Main()
  {
    // 以下のラムダ式と同様の式木を生成する
    // Action<string> a = s => Console.WriteLine(s.ToUpper());

    // 式木に渡されるパラメータ(string型)
    var s = Expression.Parameter(typeof(string), "s");

    // Console.WriteLine(param.ToUpper())に相当する式木を生成する
    var printUpperCase = Expression.Call(
      typeof(Console).GetMethod("WriteLine", new[] {typeof(string)}),
      Expression.Call(s, typeof(string).GetMethod("ToUpper", Type.EmptyTypes))
    );

    // 式木を構成してラムダ式に変換する
    var lambda = Expression.Lambda<Action<string>>(printUpperCase, new[] {s});

    // デリゲートに変換する
    var a = lambda.Compile();

    // 変換したデリゲートに引数を渡して呼び出す
    a("Hello, world!");
  }
}
実行結果
Hello, world!
System.Linq.Expressions名前空間のクラスを使って複雑な構文を含む式木を構築して呼び出す
using System;
using System.Linq.Expressions;

class Sample {
  static void Main()
  {
    // 以下のラムダ式と同様の式木を生成する
    /*
    Func<int, int> f = n => {
      int sum = 0;
      for (;;) {
        if (n <= 0)
          break;
        sum += n;
        n -= 1;
      }
      return sum;
    };
    */

    var n = Expression.Parameter(typeof(int), "n");
    var sum = Expression.Parameter(typeof(int), "sum");
    var brk = Expression.Label("break");

    var body = Expression.Block(typeof(int), new[] {sum},
      Expression.Assign(sum, Expression.Constant(0)),
      Expression.Loop(
        Expression.Block(
          Expression.IfThen(
            Expression.LessThanOrEqual(n, Expression.Constant(0)),
            Expression.Break(brk)
          ),
          Expression.AddAssign(sum, n),
          Expression.SubtractAssign(n, Expression.Constant(1))
        ),
        brk
      ),
      sum
    );

    var lambda = Expression.Lambda<Func<int, int>>(body, new[] {n});

    var f = lambda.Compile();

    Console.WriteLine(f(0));
    Console.WriteLine(f(1));
    Console.WriteLine(f(2));
    Console.WriteLine(f(3));
    Console.WriteLine(f(4));
  }
}
実行結果
0
1
3
6
10