リフレクションについてまとめた

最近 "Reflection" について調べていたので、それについて自分なりにまとめようと思います。

▼学習を兼ねて作ったもの

sonoichi-blog.hatenablog.com

※この記事はUnityゆるふわサマーアドベントカレンダー 2018 12日目の記事です。(C#なのでギリUnityということで...)

Reflectionとは

クラスを他のプログラムから利用できるようにするため、 プログラムやライブラリ中にはクラス名やメンバー名、それらのアクセスレベル等の情報が格納されています。 これらの情報はメタデータと呼ばれ、 プログラムの実行時にメタデータを取り出すための機能をリフレクション(reflection)と呼びます。

(プログラムが自分自身の情報を調べることができる機能なので、reflection(鏡映、反射)と呼ぶわけです。)

実行時型情報 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

 です。

アクセシビリティレベルに関係なくメンバをあれこれできるのは便利だけど危険、加えて処理も遅いようなので、デバッグ用途で使うことの多い機能だと思います。

なぜリフレクションは遅いのか | POSTD

MemberInfo

名前や宣言されたクラスなど、メンバとしての情報を持っています。

各 Info のクラスの基底クラスであり、MemberTypeによって変数なのか、関数なのかといったことが判別できます。

gist.github.com

MethodInfo

関数に関する様々な情報を持ちます。

関数の実行もできます。

gist.github.com

methodInfo.IsSpecialName は関数がコンストラクタ( .ctor )や自動実装プロパティ( プロパティの get/set メソッド( get_xxx, set_xxx )等 )であるときに true になります。 

ParametorInfo.HasDefaultValue は .Net 4.5以降で使用可能です。

virtual, abstruct はそれぞれ MethodInfo.IsVirtual, MethodInfo.IsAbstract で判定できますが、overrideされたかのフラグは取得できません。判定したいときは

var isOverride = methodInfo.GetBaseDefinition().DeclaringType != methodInfo.DeclaringType;

のようにすればできます。

FieldInfo

変数に関する様々な情報を持ちます。

値の変更もできます。

gist.github.com

 IsDefined(), GetCustomAttribute(), GetCustomAttributes() はMethodInfoでも使用可能です。

PropertyInfo 

プロパティに関する様々な情報を持ちます。

gist.github.com

GetAccesser(), GetGetMethod(), GetSetMethod() の第一引数( bool nonPublic )は publicでないアクセサを取得するかどうかです。

PropertyのアクセサビリティレベルはPropertyInfoからは判定できないので、アクセサから求めます。

GetProperty() のオーバーロードに Type を渡すものがあり、なんだろうと思ったのですが、インデクサを取得するためのものでした。

上記の例では次のようなインデクサを定義しています。

[System.Runtime.CompilerServices.IndexerName("Indexer")]
public int this[int x, int y]
{
    get { return _array[y * _width + x]; }
    set { _array[y * _width + x] = value; }
}
private int _array;
private int _width;

IndexerNameAttributeで名前を指定しない場合、名前は"Item"になります。

インデックサ this[] をリフレクションで取得する

ConstructorInfo

コンストラクタに関する様々な情報を持ちます。 

ConstructorInfoを利用してインスタンスの作成ができます。

gist.github.com

BindingFlags

各 Info 取得時に検索するメンバを指定するためのものです。

ここでは、主に扱われそうなものを紹介します。

  • Public : publicなメンバ
  • NonPublic : publicでないメンバ
  • Static : 静的なメンバ
  • Instance : 静的でないメンバ
  • DeclaredOnly : 基底クラスから継承したメンバを含まない

BindingFlags は enum ですが、それぞれが各ビットのフラグを表しており、ビット演算のようにして扱うことができます。

// publicかつ非staticな関数を全取得

var methods = typeof(TestClass).GetMethods(BindingFlags.Public | BindingFlags.Instance);

 最後に

リフレクションはどんなメンバにもアクセスでき、外部から関数の実行や値の変更ができる便利な機能です。

実行時に設定ファイル(ScriptableObject)の値を変更したり、クラスが保持する値を全て出力したり、PlantUML用のコードを出力したりと、色々なことができそうな気がします。

間違った認識や、紹介した以外で便利な機能などありましたら教えていただけると嬉しいです。