【Unity】AddComponentのジェネリック版をリフレクションで実行する

結論

リファレンス的に見に来た人のために先にうまくいったコードだけ書いておきます。

Type t = Type.GetType(data); //dataはstring型で、AddComponentするクラスを示す
MethodInfo mi = typeof(GameObject).GetMethod(
    "AddComponent",
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
    null,
    new Type[0],
    null
);
MethodInfo bound = mi.MakeGenericMethod(t);
//Invokeの返り値はObject型なのでキャストが必要
(キャストしたい型)bound.Invoke(gameObject, null);

このコードでは、dataで指定したクラス(例えば、data = "int")でAddComponentを行なっています。つまり、

//dataはstring型で、クラスを示す。
gameObject.AddComponent<data>();

というコードを実行しているのと同じです。(もちろん、このコードは「dataはstring型だから正しい使い方じゃないよ」とか「dataというクラスは無いよ」というエラーを出されます。)

背景

使用環境は、Unity2019.1.11f1です。

Unityではコードによって動的にインスタンスを生成する際、new()や Activator.CreateInstance(Type)を使おうとすると、「代わりにAddComponentを使ってくれ」というエラーを吐かれてしまいます。これらが使えればもう少し楽に実装できそうだったのですが、仕方がないのでAddComponentを使っています。

また、UnityのGameObject.AddComponentメソッドには

①public Component AddComponent (Type componentType);

②public T AddComponent ();

の2種類が存在しています。しかし、①を使おうとすると「時代遅れだから新しいのを使ってくれ」というエラーを吐かれます。別に書き方が古いだけだし注意くらいでいいと思うのですが・・・。①を使えば

gameObject.AddComponent(Type.GetType(data));

だけで済んだはずなんですが、仕方がないので②のジェネリック版を利用せざるを得ないという事になりました。

解説

コードの動作を説明します。

MethodInfo mi = typeof(GameObject).GetMethod(
    "AddComponent",
    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
    null,
    new Type[0], //引数の数を指定
    null
);

まずこのコードなのですが、GameObjectクラスのAddComponentというメソッドをMethodInfoとして得ています。

GetMethod("AddComponent")では駄目な理由は、AddComponentがオーバーロードされた関数だからです。この書き方では、「①と②(背景参照)のどっちを取れば良いかわからない」というエラーを吐かれます。このため、第4引数の配列の要素数によって、取ってくる関数が必要とする引数の個数を指定する必要があります。今回は0(引数なし)にすることでジェネリック版を指定しています。

MethodInfo bound = mi.MakeGenericMethod(t);
//Invokeの返り値はObject型なのでキャストが必要
(キャストしたい型)bound.Invoke(gameObject, null);

先ほど取ったのはジェネリック版なので、その型を何にするかをMakeGenericMethodで指定します。そして、そのメソッドをInvokeによって実行します。gameObjectは、このメソッドを実行するインスタンスです。この返り値はObject型になるので、代入などする際はキャストしてあげましょう。

あとがき

これは今回のケースだけではなく、C#でのジェネリックメソッドのリフレクションによる実行全般に応用できると思うので、そういった形でも参考になれば幸いです。ここまでお読みいただきありがとうございました。

参考サイト

【Unity】【C#】Reflectionを使ってメソッドを呼んだり変数を書き換えたりキャッシュして高速化したりする - LIGHT11C#でReflectionを使ってメソッドを呼んだり変数を書き換えたりする方法です。light11.hatenadiary.com