[C#][dynamic] リフレクション Q&A
「Hokuriku.NET C# メタプログラミング ~リフレクション~」に参加してきた。
Hokuriku.NET C# メタプログラミング ~リフレクション~ | |
---|---|
日時 | 2013年6月29日 |
会場 | 海みらい図書館 (石川県金沢市) |
関連記事 |
その中で話題になったことから、何点かQ&A形式でご紹介したい。
■ Q. ジェネリックの型情報って取れるの?
A. 取れる。
例えば System.Collections.Generic.Dictionary の型情報を取ってみよう。
using System; using System.Collections.Generic; class Program { static void Main() { // Dictionary<,> の型情報を取得 Type typeOfDictionary = typeof(Dictionary<,>); Console.WriteLine(typeOfDictionary); } }
実行結果は、次のようになる。
System.Collections.Generic.Dictionary`2[TKey,TValue]
この型情報から、型引数を指定した場合の型情報を得るには、次のように MakeGenericType を使う。
using System; using System.Collections.Generic; class Program { static void Main() { // Dictionary<,> の型情報を取得 Type typeOfDictionary = typeof(Dictionary<,>); // typeOfDictionary に型引数を指定して型情報を取得 Type typeOfDictionaryOfStringAndInt = typeOfDictionary.MakeGenericType(new Type[] { typeof(string), typeof(int) }); Console.WriteLine(typeOfDictionaryOfStringAndInt); } }
実行結果:
System.Collections.Generic.Dictionary`2[System.String,System.Int32]
勿論、始めから型引数を指定した場合の型情報と同じになる。
using System; using System.Collections.Generic; class Program { static void Main() { // Dictionary<string, int> の型情報を取得 Type typeOfDictionaryOfStringAndInt = typeof(Dictionary<string, int>); Console.WriteLine(typeOfDictionaryOfStringAndInt); } }
実行結果:
System.Collections.Generic.Dictionary`2[System.String,System.Int32]
■ Q. プロパティって内部的にはメソッドなの?
A. その通り。プロパティでもある。
リフレクションを使って、プロパティを持ったクラスのメンバーを調べてみよう。
using System; using System.Reflection; // プロパティ Name を持つクラス Person (Object クラスから派生) class Person // : Object { public string Name { get; set; } } class Program { static void Main() { // Person の型情報を取得 Type type = typeof(Person); // Person の型情報から、Person の全メンバーの情報を取得 MemberInfo[] memberInfos = type.GetMembers(); // 各メンバー情報の名前と種類を表示 foreach (MemberInfo member in memberInfos) Console.WriteLine("Name: {0}, MemberType: {1}", member.Name, member.MemberType); } }
Type.GetMembers メソッドを使い、全メンバーの情報を取得し、各メンバー情報の名前と種類を表示させてみる。
実行してみると、次のように Person とそのベースクラスである Object の、public なメンバーがリストアップされる:
Name: get_Name, MemberType: Method Name: set_Name, MemberType: Method Name: ToString, MemberType: Method Name: Equals, MemberType: Method Name: GetHashCode, MemberType: Method Name: GetType, MemberType: Method Name: .ctor, MemberType: Constructor Name: Name, MemberType: Property
プロパティとして Name、そのアクセサー メソッドとして get_Name と set_Name があるのが判る。
次のように、プロパティ Name から、アクセサー メソッドを取り出してみることもできる。
using System; using System.Reflection; // プロパティ Name を持つクラス Person class Person { public string Name { get; set; } } class Program { static void Main() { // Person の型情報を取得 Type type = typeof(Person); // Person の型情報から、プロパティ Name の情報を取得 PropertyInfo propertyInfo = type.GetProperty("Name"); Console.WriteLine(propertyInfo); // プロパティ Name の情報から、アクセサー メソッド (getter と setter) の情報を取得 MethodInfo[] methodInfos = propertyInfo.GetAccessors(); // アクセサー メソッド (getter と setter) の情報を表示 foreach (MethodInfo methodInfo in methodInfos) Console.WriteLine(methodInfo); } }
実行結果:
System.String Name System.String get_Name() Void set_Name(System.String)
つまり、プロパティ Name があるときには、内部的にアクセサーとして get_Name と set_Name メソッドを持つことになる。
これは、単に get_Name と set_Name メソッドを持つのとは異なる。Name というプロパティも持つことになる。
class Person { public string Name { get; set; } // プロパティ Name (アクセサーとして get_Name と set_Name メソッドも持つことになる) }
と異なり、
class Person { public string get_Name( ) { return null; } public void set_Name(string name) { } }
では、get_Name と set_Name メソッドを持つことは同じだが、Name プロパティを持たない。
■ Q. Name プロパティがあるときに内部的にアクセサーとして get_Name と set_Name メソッドを持つのだとすると、Name プロパティがあるときは、get_Name という名前や set_Name という名前のメソッドは作れない?
A. そう、作れない。コンパイル エラーになる。
次のコードをコンパイルしてみると、コンパイル エラーになる。
class Person { public string Name { get; set; } // プロパティ Name public string get_Name( ) { return null; } public void set_Name(string name) { } }
コンパイル結果:
- エラー 1 型 'Person' は、'get_Name' と呼ばれるメンバーを同じパラメーターの型で既に予約しています。
- エラー 2 型 'Person' は、'set_Name' と呼ばれるメンバーを同じパラメーターの型で既に予約しています。
■ Q. プロパティが内部的にメソッドだとすると、リフレクションでその内部的なメソッドを実行してもプロパティの値の設定/取得ができる?
A. できる。
先ずは、リフレクションを使って、プロパティに値を設定/取得してみる。
using System; using System.Reflection; // プロパティ Name を持つクラス Person class Person { public string Name { get; set; } } class Program { static void Main() { // Person の型情報を取得 Type type = typeof(Person); var person = new Person(); PropertyInfo propertyInfo = type.GetProperty("Name"); // リフレクションを使って、person の Name の値を設定 propertyInfo.SetValue(person, "明智光秀", null); // .NET 4.5 未満の場合 // .NET 4.5 以上の場合は propertyInfo.SetValue(person, "明智光秀"); でも良い // リフレクションを使って、person の Name の値を取得 string name = (string)propertyInfo.GetValue(person, null); // .NET 4.5 未満の場合 // .NET 4.5 以上の場合は string name = (string)propertyInfo.GetValue(person); でも良い Console.WriteLine(name); } }
実行結果:
明智光秀
次に、リフレクションを使い、内部的な set_Name メソッドで値を設定し、Name プロパティで値を取得してみる。
using System; using System.Reflection; // プロパティ Name を持つクラス Person class Person { public string Name { get; set; } } class Program { static void Main() { // Person の型情報を取得 Type type = typeof(Person); var person = new Person(); // リフレクションを使って、person の set_Name を呼び出す MethodInfo methodInfo = type.GetMethod("set_Name"); methodInfo.Invoke(person, new object[] { "織田信長" }); // リフレクションを使って、person の Name の値を取得 PropertyInfo propertyInfo = type.GetProperty("Name"); string name = (string)propertyInfo.GetValue(person, null); // .NET 4.5 未満の場合 // .NET 4.5 以上の場合は string name = (string)propertyInfo.GetValue(person); でも良い Console.WriteLine(name); } }
実行結果:
織田信長
このように、プロパティの内部的なメソッドをリフレクションを使って実行することで、プロパティの値の設定/取得ができる。
勿論、リフレクションを使わずに、直接 set_Name や get_Name を呼び出すことはできない。
using System; using System.Reflection; // プロパティ Name を持つクラス Person class Person { public string Name { get; set; } } class Program { static void Main() { var person = new Person(); person.set_Name("石田光成"); // コンパイル エラーになる } }
コンパイル結果:
- エラー 1 'Person.Name.set': 演算子またはアクセサーを明示的に呼び出すことはできません。
■ Q. リフレクションで static なメンバーや private なメンバーにもアクセスできる?
A. できる。
次の static メンバーや private メンバーを持つクラス Singleton で、試してみよう。
using System; using System.Reflection; // static メンバーや private メンバーを持つクラス class Singleton { private static Singleton instance = new Singleton(); public static Singleton GetInstance() { return instance; } private Singleton() {} } class Program { static void Main() { // Singleton の型情報を取得 Type type = typeof(Singleton); // BindingFlags.NonPublic | BindingFlags.Instance を指定することで、public でも static でもないコンストラクターの情報を取得 ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { }, null); // リフレクションを使うことで、private なコンストラクターが実行できる Singleton singleton = (Singleton)constructorInfo.Invoke(null); // BindingFlags.NonPublic | BindingFlags.Static を指定することで、public でなく static なフィールドの情報を取得 FieldInfo fieldInfo = type.GetField("instance", BindingFlags.NonPublic | BindingFlags.Static); // リフレクションを使うことで、private で static なフィールドにアクセスできる fieldInfo.SetValue(null, singleton); } }
■ Q. const なフィールドって static なフィールドとしてリフレクションでアクセスできる? まさかと思うけど値を書き換えられる?
A. const なフィールドは static なフィールドとしてリフレクションで値を取得できるが、書き換えられる訳ではない。
試してみよう:
using System; using System.Reflection; class ConstantSample { public const double PI = 3.14159265358979 ; // const なフィールド } class Program { static void Main() { // Constant の型情報を取得 Type type = typeof(ConstantSample); // Constant の型情報から、const なフィールド PI の情報を取得 FieldInfo piFieldInfo = type.GetField("PI"); // インスタンスを指定せずに static フィールドとして値を取得してみる var pi = (double)piFieldInfo.GetValue(null); Console.WriteLine(pi); // インスタンスを指定せずに static フィールドとして値を設定してみる piFieldInfo.SetValue(null, 3.0); } }
実行してみると、次のように、PI の値を取得することはできるが、設定ではエラーになる:
3.14159265358979 ハンドルされていない例外: System.FieldAccessException: 定数フィールドを設定できません。
■ Q. じゃあ readonly なフィールドは?
A. readonly なフィールドの場合は、const なフィールドの場合と異なり、static としてなければインスタンス フィールドになる。また、値を取得するだけでなく設定することもできる。
readonly なフィールドの場合について試してみよう。
using System; using System.Reflection; class ReadonlySample { public readonly int DayOfYear = DateTime.Now.DayOfYear; // readonly なフィールド } class Program { static void Main() { // Readonly の型情報を取得 Type type = typeof(ReadonlySample); // Readonly の型情報から、readonly なフィールド DayOfYear の情報を取得 FieldInfo dayOfYearFieldInfo = type.GetField("DayOfYear"); // インスタンスを指定せずに static フィールドとして値を取得してみると、これは実行時エラーとなり取得できない //var dayOfYear = (int)dayOfYearFieldInfo.GetValue(null); // 実行時エラー // Readonly のインスタンスを生成 var readonlySample = new ReadonlySample(); // インスタンスを指定してインスタンス フィールドとして値を取得してみる var dayOfYear = (int)dayOfYearFieldInfo.GetValue(readonlySample); Console.WriteLine(dayOfYear); // インスタンスを指定してインスタンス フィールドとして値を設定してみる dayOfYearFieldInfo.SetValue(readonlySample, 0); // 設定されたか確認 Console.WriteLine(readonlySample.DayOfYear); } }
実行結果は次の通り:
182 0
取得だけでなく、設定も可能だ。
ディスカッション
コメント一覧
readonlyフィールドが書き換えられるのは意外でした。
とても興味深いです