概要

リフレクションってのは、我々が慣れ親しんだ方法とは違う方法……リフレクション API を使って……クラスだとかメソッドだとかにアクセスする機能のことだ。へー。流し読むぞ。

 

流し読みノート

めたでーた

  • アセンブリには、型のメンバや実装の詳細が含まれたメタデータが入っている。
  • そのメタデータのおかげで CLR はコンパイルやガベージコレクションができるってわけよ。
  • メタデータは CLR だけが使えるわけじゃない。ぼくらも「リフレクション API」(System.Reflection 名前空間のクラス)を使って型の情報にアクセスできるのだ。

 

リフレクション API ってどんなの

こんなプロパティや型の階層構造になってる。
Assembly
└── Module
    └── TypeInfo
        ├── FieldInfo
        ├── MethodInfo
        ├── ConstructorInfo
        ├── PropertyInfo
        └── EventInfo
などなど。これらは双方向にたどれる。
  • Assembly プロパティとか Module はみたまんま。アセンブリやモジュールの情報をもつ。
  • Module はあんま利用しない。だってアセンブリの大部分はひとつのモジュールから構成されているから。
  • TypeInfo クラスはもちろん型を表す。これ以下はぜんぶ MemberInfo を継承してる。ぜんぶなにかの型のメンバだから。
  • FieldInfoTypeInfo じゃなくて Type オブジェクトから取得するのが一般的。
    • いや、なんで TypeInfoType があるねんややこしすぎるわ。
    • まあ使い分けとしては、 Type は型を同定したり基本的な情報を知りたいとき使う。 Type は軽量クラスとも表現されてるみたい。リフレクションが必要なときは TypeInfo を使う。ジェネリック型のサポートによりこれらの役割はさらにわかりにくくなった。メソッドにはこういう2種類の表現はない。型だけが2種類もってる。
  • PropertyInfoTypeInfo オブジェクトの GetDeclaredProperty() とか DeclaredProperties プロパティで取得。こいつはアクセシビリティに関するプロパティがない。だってアクセシビリティは個々の get set メソッドのレベルに設定されているからね。プロパティの型は PropertyType プロパティから取得。
  • EventInfo ↑と同じように GetDeclareEvent()DeclaredEvent で取得できて、アクセシビリティも個々の add remove にあるので本体はアクセシビリティに関するプロパティはもたない。 add remove については GetAddMethod GetRemoveMethodMethodInfo として取得できる。

 

Assembly プロパティのうんちく

  • Assembly.GetEntryAssemblyAssembly.GetExecutingAssemblyAssembly.GetCallingAssembly あたりで Assembly オブジェクトを取得できる。
  • ただ現在のアセンブリがほしいときは Assembly.GetExecutingAssembly より typeof(Program).GetTypeInfo().Assembly のほうが速い。
  • Assembly クラスの DefinedTypes プロパティでアセンブリ内のすべての型を取得できる。 ExportedTypes プロパティなら public な型だけ絞って取得。
  • てかなんならこの Assembly プロパティから「型のインスタンス」を生成できる。
object o = asm.CreateInstance(
    "MyApp.WithConstructor",
    false,    // 名前のマッチを厳密に(大文字小文字を区別して)行う。
    BindingFlags.Public | BindingFlags.Instance,  // public なインスタンスコンストラクタを使う。
    null,     // Binder オブジェクトを指定しないことにより、与える引数が正確に型マッチすることを期待する。
    new object[] { "コンストラクタの引数" },
    null,     // CultureInfo を渡すところ。
    null);    // Remote activation のようなまれなケースに対応するための引数。(しらん)
  • ↑の BindingFlags について。リフレクション API の多くはこれを引数にとる。どのメンバを取得するか選ぶときに使うんだって。 BindingFlags.Public とか BindingFlags.NonPublic とかを見ればわかりやすいね。
  • えーと? クラス自体を作れるんじゃなくて、実在する WithConstructor クラスのインスタンスを作っている……のか? 「型のインスタンス」って言い方わかりづらい。

 

Type と TypeInfo のうんちく

// Type の取得。
Type t1 = typeof(string);
Type t2 = typeof(IDisposable);
// TypeInfo を使って、型互換性のテストをしてみる。
// string -> object にできるけど、逆はできない。
TypeInfo stringType = typeof(string).GetTypeInfo();
TypeInfo objectType = typeof(object).GetTypeInfo();
Console.WriteLine(objectType.IsAssignableFrom(stringType));  // True。 string -> object 可。
Console.WriteLine(stringType.IsAssignableFrom(objectType));  // False。 object -> string 不可。
  • ConstructorInfoMethodInfo はどっちもメソッドなんで MethodBase から派生している。
  • アクセシビリティに対応して IsPublic(public)、 IsPrivate(private)、 IsAssembly(internal)、 IsFamily(protected)、 IsFamilyOrAssembly(protected internal)なんつープロパティがある。 protected って「ファミリー」なんだ。へー。
    • あと IsFamilyAndAssembly も……たぶん CTS(Common Type System)に……あるんだけど、これは C# には無い。

 

冒頭の「リフレクション API でメソッドにアクセスする」実例

// InvokeMember を使ってメソッドを呼び出す。
public static object CreateAndInvokeMethod(string typeName, string member, params object[] args)
{
    Type t = Type.GetType(typeName);
    object instance = Activator.CreateInstance(t);
    return t.InvokeMember(
        member,
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod,
        null,
        instance,
        args);
}
// MemberInfo を使ってメソッドを呼び出す。
public static object CreateAndInvokeMethod(string typeName, string member, params object[] args)
{
    Type t = Type.GetType(typeName);
    object instance = Activator.CreateInstance(t);
    MethodInfo m = t.GetTypeInfo().DeclaredMethods.Single(mi => mi.Name == member);
    return m.Invoke(instance, args);
}

 

小休憩

  • .NET 4.5 から追加されたという「リフレクションコンテキスト」は需要がよくわからん。スキップ。
  • リフレクション API を使うと「利用する型の構造に基づいて動作するコード」を作れるという。よく需要がよくわからん。

必要性を実感できないものは理解しづらい。ユニットテストとか書くときに使えるかな? くらい。