reflectionという英単語には「反射」や「反響」といった意味、また「内省」や「熟考」などの自分自身を見つめるといった意味があります。 「自己言及」と訳される場合もあります。 プログラミングにおけるリフレクションとは、プログラムのメタデータ(=型情報などプログラム自身に埋め込まれている情報)を実行時に取得することを指します。
.NETではプログラム(厳密にはコードのコンパイルによって生成されるモジュール)に様々なメタデータが埋め込まれます。 リフレクションによって埋め込まれたメタデータを参照することによって、型情報や属性の取得、型情報のみによるインスタンスの作成、遅延バインディング、アセンブリの動的読み込み・生成など、さまざまなことが行えるようになっています。 文字列で名前を指定してメソッドを呼び出したり、文字列で型を選択するといった手法もリフレクションによって実装することができます。
リフレクションの例
型情報の取得
リフレクションによって実行時に型自身の情報を取得することができます。 取得できる情報については後述の§.Typeクラスから取得できる情報や§.メンバ情報の取得 (MemberInfo)などで解説します。
インスタンスの操作
リフレクション機能を使うと、取得した型情報を使ってメソッドの呼び出しやフィールドの取得・設定などのインスタンスの操作をすることができます。 この際に、操作するメソッドやフィールド・プロパティは文字列によって指定することができます。 これにより、型に依存しない汎用的な処理を記述したり、実行時まで型が決定していないインスタンスに対する操作を行うといったことができます。
このような操作については§.メンバの呼び出しで解説します。
属性
型情報以外のメタデータとして属性も挙げられます。 .NETでは属性を使うことでプログラムに型情報以外の追加的なメタデータを埋め込むことができ、それを実行時に取得・参照することができます。 属性にはコンパイラやランタイムが特別な操作を行うために使用されるものが存在するほか、独自に意味を定義して情報を埋め込むカスタム属性があります。
代表的な属性として、プログラムのバージョン情報を埋め込むための属性(アセンブリのバージョン情報を設定・取得する)や、旧式化されたAPIであることを示すObsolete属性(機能の廃止・非推奨化)、オブジェクトをシリアライズする際の動作を定義する属性(シリアライズの基本)などがあります。
その他、属性について、および属性の使用方法や独自に定義してその情報を取得する方法などについて詳しくは属性とメタデータで個別に解説しています。
リソース
型情報や属性のほかに、リソースもメタデータとして埋め込まれます。 リフレクションによってEXEやDLLに埋め込まれているリソースを利用することもできます。
リフレクションを使ったリソースの扱い方についてはリソースの埋め込みと読み込みで個別に解説しています。
型情報 (Typeクラス)
リフレクションにおいて中心的な役割を果たすTypeクラスについて解説します。 このクラスは型情報を扱うクラスとなっていて、このクラスから型自体の情報を取得できるほか、メソッドやフィールドなどメンバの情報の取得、型情報からのインスタンスの生成や、メソッド呼び出し・フィールドの書き換えなどのインスタンスの操作を行う上で必要な情報を取得することができます。
Typeクラスの取得 (typeof, GetType)
C#ではtypeof
演算子、VBではGetType
演算子を使うことで型情報をTypeクラスのインスタンスとして取得することができます。 また、任意の型のインスタンスでGetTypeメソッドを呼び出すことによっても取得することができます。 このメソッドはObjectクラスから継承されるため、どの型でも共通して使用することができます。
つまり、型名から直接型情報を取得したい場合にはtypeof
/GetType
、インスタンスからその型の型情報を取得したい場合にはGetType
メソッドを使用します。
ジェネリック型の型情報
ジェネリック型では、型引数に具体的な型が指定されているかどうかで型情報が変わります。 たとえばList<T>クラスを例にとった場合、型引数T
の型が具体的に定まっていないList<T>
を特にオープンジェネリック型(あるいはジェネリック型定義)、対してList<int>
やList<string>
など型引数T
の型が具体的な型が定まっているものをクローズジェネリック型(あるいは構築ジェネリック型、構築された型)と呼びます。
typeof
演算子・GetType
演算子でジェネリック型の型情報を取得する場合、オープンジェネリック型とクローズジェネリック型を区別して取得することができます。 オープンジェネリック型を表す場合は型パラメータの指定を省略した型名(例:Dictionary<,>
/Dictionary(Of ,)
)を使用し、一方クローズジェネリック型では具体的な型名を指定した型名(例:Dictionary<string, int>
/Dictionary(Of String, Integer)
)を使用することによってそれぞれの型情報を取得することができます。
オープンジェネリック型(ジェネリック型定義)の型情報では、Type.MakeGenericTypeメソッドを使用することでクローズジェネリック型(構築ジェネリック型)を取得することができます。 言い換えると、MakeGenericTypeメソッドによってジェネリック型定義から構築ジェネリック型を構築することができます。
MakeGenericTypeメソッドの引数に型引数にしたい型の型情報(Type
)を指定することで、対応するクローズジェネリック型の型情報を取得できます。 例えばDictionary<,>
の型情報からDictionary<string, int>
の型情報を取得したい場合は次のようにします。
逆にクローズジェネリック型(構築ジェネリック型)の型情報では、GetGenericTypeDefinitionメソッドを使用することでオープンジェネリック型(ジェネリック型定義)の型情報を取得することができます。 言い換えると、GetGenericTypeDefinitionメソッドによって構築ジェネリック型からジェネリック型定義を取得することができます。
例えばDictionary<string, int>
の型情報からDictionary<,>
の型情報を取得したい場合は次のようにします。
.NETでは、ジェネリック型の型引数(型パラメータ、例えばList<T>
におけるT
、List<int>
におけるint
の部分)もTypeクラスで扱います。 詳しくは後述の§.型パラメータ情報の取得で解説します。
ジェネリック型と型名の表記
Typeクラスなどで使われる型名の多くはコード上での表記と変わりませんが、ジェネリック型では異なる表記が使われます。 例えばコード上ではList<T>
/List(Of T)
と記述される型にはSystem.Collections.Generic.List`1
といった表記が使われます。
型名では山括弧< >
で型引数名を記述する代わりに、バッククオート`
の後ろに型引数の数が記述されます。 例えばDictionary<TKey, TValue>
では型引数の数が2つとなるため、System.Collections.Generic.Dictionary`2
という表記になります。
リフレクションにおいてジェネリック型の名前を文字列で指定する場合には、この表記を使用する必要があります。
アセンブリからの型情報の取得
アセンブリとは何かを簡略に説明すると、EXEファイルあるはDLLファイルから読み込まれた実行可能コードとメタデータのセットのことです。 (より正確には、実行可能コードとメタデータのセットであるモジュールを格納したもの) 特定のEXEやDLLで定義されている型情報を取得したい場合には、まずアセンブリを表すAssemblyクラスのインスタンスを取得する必要があります。
アセンブリの取得
Assemblyクラスのインスタンスを取得するには、取得したい対象のアセンブリに応じて次のメソッドを使うことができます。
メソッド | 概要 |
---|---|
Assembly.GetAssembly | 引数で指定された型が宣言されているアセンブリを取得する |
Assembly.GetExecutingAssembly | 現在実行しているコードが存在するアセンブリを取得する |
Assembly.GetCallingAssembly | 現在実行しているコードを呼び出したアセンブリ(呼び出し元のアセンブリ)を取得する |
Assembly.GetEntryAssembly | 現在のプロセスのエントリーポイントとなっているアセンブリ(Mainメソッドがあるアセンブリ)を取得する |
Assembly.Load | 引数で指定された名前(アセンブリ名、あるいはその完全修飾名)を持つアセンブリ、あるいはメモリ上に展開されたアセンブリを読み込んで取得する |
Assembly.LoadFrom | 引数で指定されたパスにあるアセンブリファイルを読み込んで取得する |
Assembly.ReflectionOnlyLoad | Assembly.Loadと同様だが、リフレクションのみを目的として読み込み、取得する |
Assembly.ReflectionOnlyLoadFrom | Assembly.LoadFromと同様だが、リフレクションのみを目的として読み込み、取得する |
アセンブリで定義されている型情報の取得
型名を指定した型情報の取得 (GetType)
アセンブリで定義されている型情報のうち、文字列で表された型名の型情報を取得したい場合はAssembly.GetTypeメソッドを使用します。 次の例では現在実行しているアセンブリから指定された名前の型情報を取得しています。 GetTypeメソッドに指定する型名は名前空間を含めた完全名を指定する必要があります。
また、入れ子になっている型(クラス内で定義されたクラスなど)の型情報を取得したい場合は、目的の型を含んでいる型名と入れ子になっている型の名前を+
で連結した型名を指定します。
すべての型情報の取得 (GetTypes/GetExportedTypes)
アセンブリに定義されているすべての型情報を取得したい場合はAssembly.GetTypesメソッドあるいはAssembly.GetExportedTypesメソッドを使用します。
GetTypesメソッドではアセンブリに含まれるすべての型が返されるのに対して、GetExportedTypesメソッドではアセンブリの外部から参照できる型のみが返されます。 そのためGetExportedTypesメソッドでは、パブリックではない型(アクセス修飾子internal
/Friend
が指定されているクラスなど)は取得できません。
転送された型情報の取得 (GetForwardedTypes)
アセンブリに定義されている型のうち、別のアセンブリに転送された(forwarded)型を取得するには、Assembly.GetForwardedTypesメソッドを使うことができます。 このメソッドは、.NET Standard 2.1/.NET Core 2.1以降で使用できます。
このメソッドでは、属性TypeForwardedToAttribute/TypeForwardedFromAttributeによって転送された型が取得されます。 より具体的には、過去のバージョンではこのアセンブリで定義・実装されていたが、現在では他のアセンブリに実装が移された型が取得されます。
Typeクラスから取得できる情報
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 | 型がジェネリック型あるいはジェネリックメソッドの型パラメータを表すかどうか (§.型パラメータの分類) |
IsGenericTypeParameter
(.NET Standard 2.1/.NET Core 2.1以降) |
型がジェネリック型の型パラメータを表すかどうか (§.型パラメータの分類) | |
IsGenericMethodParameter
(.NET Standard 2.1/.NET Core 2.1以降) |
型がジェネリックメソッドの型パラメータを表すかどうか (§.型パラメータの分類) | |
プロパティ | 意味 |
次の例はTypeクラスのプロパティを参照して型の分類を行う例です。 この例で使用しているデリゲート型の判定に関しては後述の§.型がデリゲート型かどうか調べるを参照してください。
型の種類・分類については型の種類・サイズ・精度・値域や値型と参照型を合わせて参照してください。
型がデリゲート型かどうか調べる
Typeクラスには型がデリゲート型かどうかを調べるIsDelegate
のようなプロパティは用意されていません。 Typeがデリゲート型を表すかどうかを調べたい場合は以下のような方法をとることができます。
実装しているインターフェイスを調べる
型が実装しているすべてのインターフェイスを取得するにはGetInterfacesメソッドを使うことができます。
一方、型が特定のインターフェイスを実装しているかどうかについては、GetInterfaceメソッドでインターフェイス名を文字列で指定して調べるか、IsAssignableFromメソッドを使ってインターフェイス型への代入を行えるかどうかを調べることによって知ることができます。
ジェネリックインターフェイス型を指定する場合、オープンジェネリック型とクローズジェネリック型の違いに注意する必要があります。 ジェネリック型の文字列表記にも注意する必要があります。 詳しくは§.ジェネリック型の型情報の解説を参照してください。
型情報からインターフェイスの代入可能性を判定する方法についてはインスタンスや型が一致するか・インターフェイスやクラスから派生しているか判定するでも詳しく解説しています。
型パラメータ情報の取得
ジェネリック型の型パラメータ(例えばList<int>
における< >
内の部分)を取得したい場合はGetGenericArgumentsメソッドを使うことができます。 型パラメータの情報もTypeクラスとして取得されます。 Typeが型パラメータを表す場合、IsGenericParameterプロパティがtrueとなります。
GetGenericArgumentsメソッドは、オープンジェネリック型・クローズジェネリック型のどちらの型情報に対しても用いることができます。
ジェネリックメソッドの型パラメータも同様に取得することができます。 この場合、まず対象となるジェネリックメソッドのMethodInfoを取得し、その後MethodInfo.GetGenericArgumentsメソッドを呼び出します。
この例で使用しているGetMethodメソッドについては後述の§.メンバ情報の取得 (MemberInfo)で解説しています。 また、型パラメータを含むメソッドやジェネリックメソッドの取得に関しては§.型パラメータを指定した取得 (ジェネリック型のメソッド/ジェネリックメソッドの取得)を参照してください。
型パラメータの宣言されている型(またはメソッド)を参照する場合は次のプロパティによって取得することができます。 ジェネリック型の型パラメータを表すTypeではDeclaringTypeプロパティ、ジェネリックメソッドの型パラメータを表すTypeではDeclaringMethodプロパティを参照することにより、宣言されている型を取得できます。
型パラメータの分類
ジェネリック型の型パラメータはType.GetGenericArgumentsメソッドによって取得することができ、ジェネリックメソッドの型パラメータはType.MakeGenericMethodParameterメソッド(.NET Standard 2.1/.NET Core 2.1以降)によって作成することができます。 (詳細:§.型パラメータを指定した取得 (ジェネリック型のメソッド/ジェネリックメソッドの取得))
Typeが型パラメータを表すかどうかは、IsGenericParameterプロパティによって判別することができます。 また、ジェネリック型の型パラメータかどうかはIsGenericTypeParameterプロパティ、ジェネリックメソッドの型パラメータかどうかはIsGenericMethodParameterプロパティによって判別することができます。
メンバ情報の取得 (MemberInfo)
Typeクラスでは次のようなメソッドによって型で定義されているメンバの情報を取得することができます。 これらのメソッドを使うことによって、型情報からメソッドやフィールドなどのメンバを参照することができ、そこから更にメソッドの呼び出しやフィールドの値の取得など(詳細は§.MemberInfo派生クラスを使ったメンバの呼び出しで後述)を行うことができます。
メソッド | 動作 |
---|---|
GetMember
GetMembers |
指定された名前のメンバ、あるいはすべてのメンバを取得する。 メンバ情報はMemberInfoクラスで返される。 |
GetConstructor
GetConstructors |
指定された引数リストのコンストラクタ、あるいはすべてのメンバを取得する。 ConstructorInfoクラスが返される。 |
GetEvent
GetEvents |
指定された名前のイベント、あるいはすべてのイベントを取得する。 EventInfoクラスが返される。 |
GetField
GetFields |
指定された名前のフィールド、あるいはすべてのフィールドを取得する。 FieldInfoクラスが返される。 |
GetMethod
GetMethods |
指定された名前・引数リストのメソッド、あるいはすべてのメソッドを取得する。 MethodInfoクラスが返される。 |
GetProperty
GetProperties |
指定された名前のプロパティ、あるいはすべてのプロパティを取得する。 PropertyInfoクラスが返される。 |
戻り値として返されるMethodInfoなどのクラスはすべてMemberInfoクラスから派生したクラスとなっています。 MemberTypeプロパティを参照するとメンバの種類をMemberTypes列挙体で取得することができます。
このようにして取得したMemberInfoを使うことにより、メソッドの呼び出しやフィールドの取得・設定などの操作を行うことができます。
メンバ情報の取得とBindingFlags
GetMembersなどメンバ情報を取得するメソッドでは、特に引数を指定しない場合はパブリックなインスタンスメンバのみを返します。 非パブリックやクラスのメンバ(静的メンバ)を取得したい場合はBindingFlagsを指定する必要があります。 BindingFlagsには次のような値が用意されています。
値 | 意味 | 備考 |
---|---|---|
BindingFlags.Static | 静的メンバを対象とする | どちらか一方または両方を指定する必要があります |
BindingFlags.Instance | インスタンスメンバを対象とする | |
BindingFlags.Public | パブリックメンバを対象とする | どちらか一方または両方を指定する必要があります |
BindingFlags.NonPublic | 非パブリックメンバを対象とする | |
BindingFlags.IgnoreCase | メンバの名前を指定する際に、大文字小文字の違いを無視する | |
BindingFlags.DeclaredOnly | その型で宣言されているメンバのみを対象とする (継承されたメンバを含めない) | |
BindingFlags.FlattenHierarchy | 継承された静的メンバを対象とする | |
値 | 意味 | 備考 |
プロパティのアクセサメソッドの取得
PropertyInfoからはプロパティのget
アクセサ/set
アクセサに対応するMethodInfoをそれぞれ個別に取得することができます。 それぞれGetMethodプロパティ/SetMethodプロパティプロパティを参照することでMethodInfoとして取得できます。 読み取り専用/書き込み専用プロパティの場合はnull
/Nothing
が返されます。
引数リストを指定した取得
メソッドではオーバーロードによって同じ名前のメソッドが複数存在する場合があり、また特にコンストラクタのオーバーロードではコンストラクタは名前を持たないため、名前だけでなく引数リストを用いて対象のオーバーロードを指定する必要があります。
例えばGetMethodの場合、名前のみでメソッドを取得しようとしたときにオーバーロードが複数存在する場合は、例外AmbiguousMatchExceptionがスローされます。
このようにメンバ名だけでは単一のメンバを限定できない場合は、引数リストを指定することによって取得対象を限定することができます。
引数リストは次の例のようにTypeの配列で指定します。 引数がない場合は空の配列か、Type.EmptyTypesフィールドを指定します。 引数の型・数・順序のすべてに一致するものがなければnull
/Nothing
が返されます。
ジェネリックメソッドなど、型パラメータによってメンバを限定する必要がある場合については§.型パラメータを指定した取得 (ジェネリック型のメソッド/ジェネリックメソッドの取得)を参照してください。
型パラメータを指定した取得 (ジェネリック型のメソッド/ジェネリックメソッドの取得)
ジェネリック型のメソッドやジェネリックメソッドでは型パラメータが引数として含まれる場合があり、引数リストを指定してメンバを取得する場合はそれを考慮する必要があります。
.NET Standard 2.1/.NET Core 2.1以降のType.GetMethodメソッドでは、型パラメータの数と引数リストを指定して特定のジェネリックメソッドを取得するためのオーバーロードが追加されています。
型パラメータT
をとるジェネリックメソッドを考えたとき、このT
に対応する型を引数リストに指定する場合は、Type.MakeGenericMethodParameterメソッドを使います。 このメソッドでは、型パラメータの位置を0から始まる値で指定することにより、その位置の型パラメータを表すTypeを返します。 例えば、型パラメータU
, V
の2つをとるジェネリックメソッドであれば、位置が0
ならU
、位置が1
ならV
の型パラメータを表すTypeを返します。
さらに、ジェネリック型のジェネリックメソッドでは、ジェネリック型の型パラメータ(class C<TC>
のTC
)と、ジェネリックメソッドの型パラメータ(void M<TM>()
のTM
)の2種類が引数に含まれる場合があります。
ジェネリック型の型パラメータはType.GetGenericArgumentsメソッドで取得することができ、ジェネリックメソッドの型パラメータはType.MakeGenericMethodParameterメソッドで得られるTypeで表すことができるため、この2つを組み合わせて引数リストとして指定します。
ジェネリック型のメソッド・ジェネリックメソッドのMethodInfoを使ってメソッド呼び出しを行う方法については§.ジェネリック型のメソッド・ジェネリックメソッドを参照してください。
Typeが型パラメータを表すものかどうか、またTypeがジェネリック型の型パラメータか、ジェネリックメソッドの型パラメータかどうかを判別する方法については§.型パラメータの分類を参照してください。
Type.MakeGenericMethodParameterを使用することができない.NET Standard 2.1/.NET Core 2.1より前やそれ以外の環境では、Type.GetMethodsメソッドを使って候補となるメソッドの一覧を取得し、そこから該当するメソッドを探し出す必要があります。
インスタンスの操作
.NETではリフレクションによって型情報を取得するだけでなく、取得した型情報を使ってインスタンスを作成したり、フィールドの書き換えやメソッドの呼び出しなどインスタンスに対する操作を行うこともできるようになっています。
インスタンスの作成 (Activator.CreateInstance)
Activator.CreateInstanceメソッドを使用すると、型情報を使って動的にインスタンスを作成することができます。
このメソッドではインスタンスを生成する際、コンストラクタに渡す引数を指定することができます。 コンストラクタに渡す引数はobject
型の配列で指定します。 指定された引数に応じて適切なコンストラクタが自動的に呼び出されます。 当然、抽象クラスのインスタンスを作成しようとした場合や、引数と一致するコンストラクタがない場合は例外がスローされます。
CreateInstanceメソッドで作成したインスタンスはobject
で返されます。 そのためインスタンスを使用する際、既知の型の場合はその型にキャストするか、あるいはobject型のまま後述する方法でリフレクションによって操作を行います。
このように、型情報が取得できれば、動的にインスタンスを作成して操作することができます。 また、動的にアセンブリを読み込み、読み込んだアセンブリに含まれるインスタンスを作成するといったことも可能です。
Activator.CreateInstanceメソッドのほか、Type.InvokeMemberメソッドやConstructorInfo.Invokeメソッドを使うことによっても、型情報からインスタンスを作成することができます。
Activator.CreateInstanceとパフォーマンス
Activator.CreateInstanceによるインスタンスの作成は、コンストラクタを直接呼び出す場合と比較するとオーバーヘッドが大きいため、パフォーマンスに影響します。 インスタンス作成にかかる時間を計測して比較すると次のようになります。 コンストラクタを使ったインスタンス作成が行える場合は、そちらを優先すべきです。
インスタンスの生成に限らず、一般にリフレクションによる操作には大きなオーバーヘッドが伴います。
メンバの呼び出し
リフレクションによって型のメンバの呼び出しを行うことができます。 たとえば、メソッドの呼び出しやフィールド・プロパティの値の取得・設定ができます。 メンバの呼び出しには、Type.InvokeMemberメソッドによってメンバ名を指定して呼び出す方法と、Typeから取得したMethodInfo・PropertyInfoなどのメソッドを使って呼び出す方法があります。
Type.InvokeMemberを使ったメンバの呼び出し
Type.InvokeMemberメソッドを使うと型情報とメンバ名によってメンバの呼び出しを行うことができます。
InvokeMemberメソッドでは、メソッドの呼び出しのほか、プロパティの値の取得・設定、フィールドの値の取得・設定を行うことができます。 またコンストラクタを呼び出すことによってインスタンスを作成することもできます。 呼び出すメンバに応じて、次のBindingFlagsのいずれかを指定します。
BindingFlags | InvomeMemberメソッドの動作 |
---|---|
BindingFlags.InvokeMethod | メソッドの呼び出し 指定した名前・引数と一致するメソッドを呼び出して戻り値を取得する |
BindingFlags.SetProperty | プロパティの設定 指定した名前のプロパティに値を設定する |
BindingFlags.GetProperty | プロパティの取得 指定した名前のプロパティの値を取得する |
BindingFlags.SetField | フィールドの設定 指定した名前のフィールドに値を設定する |
BindingFlags.GetField | フィールドの取得 指定した名前のフィールドの値を取得する |
BindingFlags.CreateInstance | コンストラクタの呼び出し 指定した引数と一致するコンストラクタを呼び出してインスタンスを作成する |
これらの値に加えて、非パブリックメンバを呼び出したり、メンバ名の大文字小文字の違いを無視して呼び出せるようにするためにBindingFlags.NonPublicやBindingFlags.IgnoreCaseなどを組み合わせて指定することもできます。
InvokeMemberメソッドでは、引数targetに操作の対象となるインスタンスを指定します。 静的メンバ(static
/Shared
)の場合は、インスタンスの代わりにnull
/Nothing
を指定します。
メソッドおよびコンストラクタの呼び出し時に渡す引数や、フィールド・プロパティに設定する値は、引数argsにobject
型の配列に格納した上で指定します。 引数がない場合はnull
/Nothing
を指定することができます。
また、BindingFlags.InvokeMethod
で呼び出したメソッドの戻り値や、BindingFlags.CreateInstance
で作成したインスタンス、BindingFlags.GetProperty
およびBindingFlags.GetField
で取得したプロパティ・フィールドの値はInvokeMemberメソッドの戻り値として取得することができます。
ジェネリック型のメソッドあるいはジェネリックメソッドを呼び出す場合については、§.ジェネリック型のメソッド・ジェネリックメソッドを参照してください。
メンバ呼び出しの結果例外が発生した場合、例外はTargetInvocationExceptionにラップされた上でスローされます。 TargetInvocationExceptionから実際にスローされた例外を取得する方法については§.TargetInvocationException、TargetInvocationExceptionにラップせずにスローさせる方法については§.BindingFlags.DoNotWrapExceptionsを参照してください。
MemberInfo派生クラスを使ったメンバの呼び出し
MethodInfoやPropertyInfoなどのMemberInfo派生クラスを取得し、以下のメソッドを使うことでもメンバの呼び出しを行うことができます。 MethodInfoやPropertyInfoの取得にはGetMethodやGetPropertyなどのメソッドを使用します。
メソッド | 動作 |
---|---|
MethodInfo.Invoke | 指定した引数と一致するメソッドを呼び出して戻り値を取得する |
PropertyInfo.SetValue | プロパティに値を設定する |
PropertyInfo.GetValue | プロパティの値を取得する |
FieldInfo.SetValue | フィールドに値を設定する |
FieldInfo.GetValue | フィールドの値を取得する |
ConstructorInfo.Invoke | 指定した引数と一致するコンストラクタを呼び出してインスタンスを作成する |
コンストラクタ呼び出しの場合を除き、Type.InvokeMemberメソッドによるメンバ呼び出しの場合と同様に操作の対象となるインスタンスを引数objで指定します。 静的メンバ(static
/Shared
)の場合はインスタンスを指定する代わりにnull
/Nothing
を指定します。
また、メソッドおよびコンストラクタの呼び出し時に渡す引数は、object
型の配列に格納して指定します。 フィールド・プロパティに設定する値をobject
型の配列に格納して指定するInvokeMemberメソッドとは異なり、PropertyInfo.SetValueメソッド・FieldInfo.SetValueメソッドでは設定する値を直接引数として指定することができます。
これらのメソッドでは、メンバの呼び出しを行うインスタンスや設定する値はすべてobject
型の引数に渡します。 この際、構造体など値型インスタンスのフィールド・プロパティを設定する際には問題が生じる場合があります。
この問題と回避方法に関してはリフレクションを使って構造体フィールドに値を設定する(FieldInfo.SetValue)で解説しているので、合わせてご覧ください。
MethodInfoを使ったメソッド呼び出し
ref/ByRefパラメータ
メソッドの引数にout
/ref
修飾子、ByRef
修飾子が設定されている場合、引数は参照渡しとなり、メソッドは引数の値を別の値に置き換えることができます。 MethodInfo.Invokeメソッドを使ってメソッドを呼び出す場合、参照渡しによって置き換えられた値は引数parametersに渡した配列に格納されます。
ジェネリック型のメソッド・ジェネリックメソッド
ジェネリック型のメソッドやジェネリックメソッドを呼び出す場合、引数リストの型はすべて具体的な型に型付けされている必要があります。 型付けされていない状態で呼び出した場合、例外InvalidOperationExceptionがスローされます。 そのため、引数リストにジェネリック型の型パラメータおよびジェネリックメソッドの型パラメータを含む場合は、呼び出す時点で何らかの具体的な型に型付け(置き換え)されている必要があります。
ジェネリック型(Type)ではType.MakeGenericTypeメソッド(関連:§.ジェネリック型の型情報)、ジェネリックメソッド(MethodInfo)ではMethodInfo.MakeGenericMethodメソッドを呼び出すことによって、型パラメータを具体的な型に置き換え、構築された(型付けされた)ジェネリック型・ジェネリックメソッドを取得することができます。
すべての型パラメータが具体的な型に型付けされた状態のMethodInfoでは、通常のMethodInfoと同様にInvokeメソッドで呼び出すことができます。
Type.InvokeMemberメソッドを使った呼び出しでは、具体的な型に型付けされないジェネリックメソッドを呼び出すと、例外MissingMethodExceptionがスローされます。
すべての型パラメータが型付けされているジェネリック型(ContainsGenericParametersプロパティがfalseのType)における、非ジェネリックなメソッド(型パラメータを取らないメソッド)は、通常のメソッドと同様に呼び出すことができます。
PropertyInfoを使ったプロパティ・インデクサの操作
PropertyInfo.GetValue/SetValueメソッドを使うとプロパティの値の取得・設定を行うことができます。 インデクサ(既定のプロパティ、引数を持つプロパティ)に対する値の設定・取得もこのメソッドで行います。
.NET Framework 4.0以前の場合、PropertyInfo.GetValue/SetValueメソッドでは引数objの他に引数indexも指定する必要があります。 この引数はプロパティがインデクサの場合にインデックスとして使用される値を指定するためのものです。 プロパティがインデクサでない場合は引数indexにnull
/Nothing
を指定します。
.NET Framework 4.5以降では引数indexを必要としないオーバーロードが用意されているため、インデクサではないプロパティではインデックスの指定を省略することができます。
PropertyInfoを使ってインデクサの値を取得・設定する場合は、インデックスを指定する必要がある以外はプロパティの場合と同様です。
ただし、C#では特に指定しない場合、インデクサの名前はデフォルトでItem
となりますが、IndexerName属性によって変更可能である点に注意してください。
プロパティ・インデクサの参照の結果例外が発生した場合、例外はTargetInvocationExceptionにラップされた上でスローされます。 TargetInvocationExceptionから実際にスローされた例外を取得する方法については§.TargetInvocationException、TargetInvocationExceptionにラップせずにスローさせる方法については§.BindingFlags.DoNotWrapExceptionsを参照してください。
ConstructorInfoを使ったインスタンスの作成
ConstructorInfoを使ってコンストラクタを呼び出し、インスタンスを作成・初期化するには、Invokeメソッドを呼び出します。 呼び出しに際してインスタンスを指定する必要がない点、作成されたインスタンスは戻り値として返される点を除けば、MethodInfo.Invokeメソッドと変わりありません。
コンストラクタ呼び出しの結果例外が発生した場合、例外はTargetInvocationExceptionにラップされた上でスローされます。 TargetInvocationExceptionから実際にスローされた例外を取得する方法については§.TargetInvocationException、TargetInvocationExceptionにラップせずにスローさせる方法については§.BindingFlags.DoNotWrapExceptionsを参照してください。
MethodInfoからデリゲートを作成する (Delegate.CreateDelegate)
Delegate.CreateDelegateメソッドを使うとMethodInfoをデリゲートに変換することができ、デリゲートを介してメソッドの呼び出しを行えるようになります。
CreateDelegateメソッドの引数には、取得したいデリゲートの型を指定します。 デリゲートの型とMethodInfoが表すメソッドのシグネチャは一致している必要があります。 また、インスタンスメソッドの場合は呼び出し対象のインスタンスを指定します。 クラスメソッド(静的メソッド)の場合はインスタンスを指定する必要はありません。 CreateDelegateメソッドの戻り値はDelegateなので、作成したデリゲートを呼び出す場合は適切な型にキャストしてから呼び出す必要があります。
デリゲートからMethodInfoを取得する (Delegate.Method/MulticastDelegate.GetInvocationList)
デリゲート型ではMethodプロパティを参照することで呼び出し対象となるメソッドのMethodInfoを取得することができます。
他のデリゲートと連結されたデリゲート(マルチキャストデリゲート)では、GetInvocationListメソッドを使って連結されているすべてのデリゲートを取得してから、各デリゲートのMethodプロパティを参照することで個々のメソッドに対応するMethodInfoを取得できます。
デリゲートと呼び出されるメソッドに関してはデリゲートの機能 §.呼び出されるメソッド・インスタンスの取得 (Method, Target, GetInvocationList)でも解説しています。
EventInfoを使ったイベントの発行
EventInfoクラスには、直接イベントを発行するメソッドは用意されていません。 そのためイベント発行時の動作と同等の処理を独自に記述する必要があります。 EventInfoクラスからは以下のような手順をとることでイベントの発行を行うことができます。
- イベントハンドラのデリゲートを格納しているフィールドのFieldInfoを取得する
- 取得したFieldInfoからイベントハンドラを格納しているMulticastDelegateを取得する
- MulticastDelegate.GetInvocationListメソッドを使ってイベントハンドラとして割り当てられているメソッドのリスト(MethodInfoの配列)を取得する
- 取得したMethodInfoひとつずつに対してメソッドの呼び出しを行う
C#とVBではイベントハンドラを格納しているフィールド名が異なる点に注意が必要です。 例えばイベント名がClick
の場合、C#ではフィールドClick
、VBではフィールドClickEvent
にイベントハンドラが格納されます。
上記の処理を具体的に実装すると次のようになります。
MulticastDelegate.GetInvocationListメソッドについてはデリゲートの機能 §.呼び出されるメソッド・インスタンスの取得 (Method, Target, GetInvocationList)で解説しています。
静的コンストラクタの呼び出し (Type.TypeInitializer)
Type.TypeInitializerプロパティを参照すると静的コンストラクタ(クラスコンストラクタ)のConstructorInfoを取得することができます。 (BindingFlags.Static
とBindingFlags.NonPublic
を指定してGetConstructorメソッドを呼び出しても静的コンストラクタを取得することができます)
通常、静的コンストラクタは自動的に一度だけ呼び出されるのみで、ユーザーコードからは呼び出すことはできませんが、TypeInitializerプロパティから取得したConstructorInfoを使うことで静的コンストラクタを任意のタイミングで呼び出すことができます。
実行結果からも分かるとおり、静的コンストラクタが自動的に呼び出された分と、ConstructorInfoを使って呼び出した分の二回分が表示されています。 つまり、ConstructorInfoを使って静的コンストラクタを呼び出したことにより、クラスが再初期化されたことになります。
ほとんどの場合、このような再初期化を行う必要性はありません。 そもそも、静的コンストラクタは通常、型の初期化時に一度だけ呼び出されることを前提とした実装となっていることが多いことから、それ以外のタイミングで静的コンストラクタを呼び出すことはクラスの状態の破壊・不整合を引き起こす可能性があるため、そういった操作の安全性が明確に保証されている場合以外では、すべきではありません。
メンバ呼び出しによる例外の処理
リフレクションによらない通常のメンバ呼び出しでは例外が発生する場合があります。 リフレクションによる呼び出しの場合も、呼び出しの結果として例外が発生する場合があります。 ここではメンバ呼び出しの結果として発生する例外の処理について解説します。
TargetInvocationException
例外TargetInvocationExceptionは、Type.InvokeMemberメソッドやMethodInfo.InvokeメソッドなどMemberInfo派生クラスを介したメンバ呼び出しの際に、呼び出し先で例外が発生した場合にスローされる例外です。
呼び出したメンバで発生した例外は、TargetInvocationExceptionにラップされた上で再スローされます。 実際にメンバがスローした例外は、キャッチした例外のInnerExceptionプロパティを参照することで取得することができます。
MethodInfo.Invokeのほかにも、PropertyInfo.GetValue/SetValueやConstructorInfo.Invokeによってプロパティやコンストラクタなどを呼び出した結果例外が発生した場合も、同様にTargetInvocationExceptionがスローされます。
なお、BindingFlags.DoNotWrapExceptionsを指定することにより、例外が発生した場合でもTargetInvocationExceptionにラップさせず、発生した例外そのものをキャッチすることもできます。
BindingFlags.DoNotWrapExceptions
特にオプションを指定せずにType.InvokeMemberメソッドやMethodInfo.Invokeメソッドなどによってメンバを呼び出し、呼び出した先で例外が発生した場合、その例外はTargetInvocationExceptionにラップされた上でスローされます。
BindingFlags.DoNotWrapExceptionsを指定することにより、発生した例外をTargetInvocationExceptionにラップしないようにすることができます。 これにより、発生した例外そのものを捕捉できるようになります。 DoNotWrapExceptions
は.NET Standard 2.1/.NET Core 2.1以降で使用することができます。
動的な生成
リフレクションでは実行時における情報の取得だけでなく、実行時に型情報やメソッドの実装を動的に生成することもできます。 ここではそれらの方法について簡単に解説します。
System.Reflection.Emit
System.Reflection.Emit名前空間のTypeBuilderやMethodBuilderなどのクラスを使うと型情報やメソッドの実装を動的に生成し、実行することができます。 ただし、メソッドの実装にはILGeneratorクラスを使ってILコード(intermediate language, 中間言語コード)を記述する必要があるため、ILに関する知識が必要となります。
以下の例ではSystem.Reflection.Emit名前空間のクラスを使って以下のようなクラスを動的に生成し、作成したクラスのメソッドを呼び出しています。
System.Linq.Expressions
System.Linq.Expressions名前空間のクラスを使って式木(expression tree, 式ツリーとも)を作成することによっても実行可能なコードを動的に生成することができます。 .NET Framework 4以降では、式だけでなく条件分岐やループなどのブロック構文も扱うことができるようになっています。 式木では生成したコードをデリゲートに変換(コンパイル)し、任意に呼び出すことができます。