こんにちは、Unityエンジニアのオオバです。
ゲーム開発中またはUnityエディタを拡張しているときUnityのinternalクラスにアクセスしたくなるときがあります。
internalとはpublicに提供していないけどUnityが内部で使用している機能のこと。 「めちゃくちゃ便利そうな機能っぽいけど使えない。。。」 って思ったことありませんか?
そこで本記事ではC#のリフレクションという機能を使って内部クラスにアクセスする方法を紹介していきます。本番アプリへの採用は危険ですがエディタ拡張に使用する分にはリスク低めです。
- Unity内に隠されている機能を使ってみたい
- より深くUnityの中に潜ってみたい
このような方はぜひ最後まで読んでみてください。
👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!
- UnityエディタのC#コードの確認方法
- リフレクションって何?
- 具体的なリフレクションの使い方
- 文字列からクラスを取得し生成するリフレクション
- publicメソッドをリストアップするリフレクション
- 親クラスを含めたpublicメソッドをリストアップするリフレクション
- BindingFlagsがリフレクションの肝
- public以外のメソッドをリストアップするリフレクション
- 親クラスを取得するリフレクション
- リフレクションで取得したメソッドを実行する方法
- 引数付きのメソッドを文字列から実行するリフレクション
- メソッドの引数を調べるリフレクション
- メソッド戻り値の型を調べるリフレクション
- クラスメソッドへアクセスするリフレクション
- リフレクション変数編
- 1.変数リストを取得するリフレクション
- 2.変数の値を取得するリフレクション
- 3.変数の値を更新するリフレクション
- 変数の型を調べるリフレクション
- リフレクションまとめ
UnityエディタのC#コードの確認方法
リフレクションを始める前に、Unityエディタがどのようなコードで動作しているのか確認する方法を紹介します。
例えばMonoBehaviourクラス内に定義された関数はどんなものがあるでしょうか。
実はUnity公式のGitHubで公開されています。以下のページからUnityエディタ内のC#コードを確認できます。
例えばMonoBehaviourは↓↓こちらです。
または皆さんが普段使用しているソースコードエディタ「Visual Studio」や「Rider」を使っていると何も設定することなく中のソースは確認可能です。
上図はRiderの画面です。ソースコード上の「MonoBehaviour」を選択した状態で Go To -> Go to Declaration or Usages
つまり宣言場所にジャンプでコードを確認できます。
リフレクションって何?
ここから実際にリレフレクションを使って内部クラスにアクセスしていきますが、そもそもリフレクションとは何か解説していきます。
リフレクション(Reflection)とはプログラム実行中にオブジェクトの型情報を取得し、動的にメソッドやプロパティを操作できる仕組みのことです。
クラス内のメソッドリストを取得したり、文字列から関数を実行したりすることが可能です。
例えば次のようなprivate関数を外側から呼び出す手段は本来ありません。
public class Sample
{
// privateメソッドは外から呼び出せない
private void PrivateHoge()
{
Debug.Log("Execute PrivateHoge!");
}
}
しかしリフレクションを使うと次のようなコードでprivate関数を外側から実行可能になります。
var sample = new Sample();
var type = sample.GetType();
// 文字列で関数名を指定して取得
var method = type.GetMethod("PrivateHoge",
BindingFlags.NonPublic | BindingFlags.Instance);
if (method != null)
{
// Private関数を実行
method.Invoke(sample, null); //出力:Execute PrivateHoge!
}
このようにリフレクションを使うとクラスの外からprivate関数を簡単に呼び出せます。面白くないですか?オオバは初めてリフレクションを知ったとき 「ヤバい!何でも操作できる!」 と興奮したのを覚えています。
リフレクションによって通常アクセスできない機能が使える
リフレクションを使えるようになって何がおいしいのかと言うと 「本来アクセスできない機能にアクセスできる」 ことです。
例えばUnityにはAnimationウィンドウというアニメーション編集パネルがあります。Animationウィンドウ内の レコードボタン はソースコードから切り替えるAPIは提供されていません。しかしリフレクションを使えば可能です。
上の動画のようにスクリプトからレコードボタンを操作できるようになりました。リフレクションを使うことで今まで正攻法ではできなかった処理を作ることができるため 開発の幅が一気に広がる のです。
リフレクションの注意点
一見便利なリフレクションですが、デメリットもあります。それは「負荷」と「APIの更新」です。
1点目はリフレクションが 高負荷 であることです。特性上リフレクションはCPU負荷が高くなりがちです。パフォーマンスを求めるゲーム開発では使い所を見定める必要があります。
2点目はAPIの更新によってリフレクションで作成した処理が動作しなくなる可能性があることです。例えばUnityのバージョンアップによって内部処理が変わると リフレクションで作った処理は動かなくなります。
このようなデメリットを踏まえた上でリフレクションと付き合っていきましょう。
具体的なリフレクションの使い方
ここから具体的なリフレクションの使い方について解説していきます。リフレクションは様々な使い方があるため、よく使うものをピックアップして解説していこうと思います。
サンプルとして以下リンク先のコードをリフレクションで参照、アクセスして解説していきます。

これからリフレクションを使って上記のクラスのデータを取得していきますが、前提として以下の情報しか所持していないものとし、あくまで文字列で取得していきます。
- ネームスペース(info.shibuya24)
- クラス名(ReflectionSample)
早速リフレクションを使ってクラスを解析していきましょう。
文字列からクラスを取得し生成するリフレクション
最初のリフレクションとして「文字列からクラス情報を取得」と「インスタンス化」をしてみます。インスタンス化とはnewキーワードを使ってましたよね。
ReflectionSample instance = new ReflectionSample();
上記のようなインスタンス化をリフレクションで実現してみます。
文字列からクラスの取得
まずは文字列からクラスを取得してみましょう。必要な情報は クラスのフルネーム です。フルネームとはネームスペースを含んだクラス名です。
namespace info.Shibuya24
{
public class ReflectionSample(){}
}
例えば上記のクラスであれば、 info.Shibuya24.ReflectionSample がクラスのフルネームになります。ネームスペースとクラス名をドットでつなげた形です。
具体的には次のコードでクラス情報を取得します。
Type type = Type.GetType("info.shibuya24.ReflectionSample");
Type.GetType を使うとクラス情報を取得できます。クラス情報は 「Type型」 で扱います。
このType型のインスタンス「type」を使って以降のリフレクションを取り扱っていきます。
リフレクションで取得したクラスのインスタンス化
次にリフレクションで取得したクラスをインスタンス化していきます。前章で取得したType型のインスタンス「type」を使います。
次のコードを見てみましょう。
object instance = Activator.CreateInstance(type);
Debug.Log(instance);
具体的には Activator.CreateInstance の引数にtypeを渡すことでインスタンス化します。
戻り値はobject型であることに注意してください。このobject型のインスタンスは以降の メソッドの実行 や 変数の取得 で使用していきます。
別アセンブリだった場合のアクセスの場合
上記のコードでクラス情報が取得できない場合があります。
考えられるケースとして別のアセンブリからのアクセスです。例えばUnity開発では以下のようなケースが発生します。
- リフレクション対象がランタイムコード
- リフレクション元がエディタ拡張コード
ランタイムコードとエディタ拡張コードはアセンブリが異なるため、先のコードではリフレクションで値を取得できません。
Unityのランタイムコードはデフォルト状態で 「Assembly-CSharp」 に、エディターコードは 「Assembly-Editor」 にまとめられます。
異なるアセンブリ間で処理する場合は、GetType関数の引数にアセンブリ情報を追加してみましょう。
Type type = Type.GetType("info.shibuya24.ReflectionSample, Assembly-CSharp");
上記のように「info.shibuya24.ReflectionSample, Assembly-CSharp」と、 アセンブリ名を追加 することで別アセンブリの要素にもアクセスできるようになるのです。
もしリフレクションに失敗してしまう場合はアセンブリを疑ってみてください。
publicメソッドをリストアップするリフレクション
クラス情報は取得できましたが、このままではどんなメソッドが実装されているかわかりません。そこでクラス内に宣言されているメソッドをリストアップしてみましょう。解析の第一歩ですね。
次のコードを実行してみてください。
Type type = Type.GetType("info.shibuya24.ReflectionSample");
object instance = Activator.CreateInstance(type);
// typeからメソッド情報を取得
MethodInfo[] methods = type.GetMethods(
BindingFlags.Instance |
BindingFlags.DeclaredOnly |
BindingFlags.Public);
foreach(var method in methods)
{
Debug.Log(method.Name);
}
すると次の3つのログが出力されます。
get_MemberPublicProp
set_MemberPublicProp
MemberPublicMethod
ログ | 内容 |
---|---|
get_MemberPublicProp | publicプロパティ(ゲッター) |
set_MemberPublicProp | publicプロパティ(セッター) |
MemberPublicMethod | publicメソッド |
クラスに定義されたpublic要素が取得できたことが分かります。プロパティだけ特殊で、頭に「get」「set」が自動的に付与されることに注意しましょう。
このように 「GetMethods」 関数を使うことで簡単にクラス内に宣言されたメソッド一覧を取得できます。
本章ではあくまで対象のクラスに定義されたメソッドリストの取得でした。次に取得対象を親クラスを含めてみましょう。
親クラスを含めたpublicメソッドをリストアップするリフレクション
前章でクラス内に定義されたpublicメソッドをリストアップしましたが、親クラスのpublicメソッドもリストアップしてみたいですよね。実はとても簡単です。
Type type = Type.GetType("info.shibuya24.ReflectionSample");
object instance = Activator.CreateInstance(type);
// typeからメソッド情報を取得
MethodInfo[] methods = type.GetMethods(
BindingFlags.Instance |
BindingFlags.Public);
foreach(var method in methods)
{
Debug.Log(method.Name);
}
上記のコードを実行すると次のように親クラスを含めたpublic関数、プロパティのログが出力されます。
get_MemberPublicProp
set_MemberPublicProp
MemberPublicMethod
get_ParentMemberPublicProp
set_ParentMemberPublicProp
ParentMemberPublicMethod
Equals
GetHashCode
GetType
ToString
ポイントは親クラスの更に親クラスである「objectクラス」のpublicメソッド(Equals
、ToString
など)も取得できていることです。
ソースコードの変更点はたったの1箇所。
type.GetMethods(
BindingFlags.Instance |
BindingFlags.DeclaredOnly |
BindingFlags.Public);
↓
type.GetMethods(
BindingFlags.Instance |
BindingFlags.Public);
「BindingFlags.DeclaredOnly」 を消したことです。
BindingFlags.DeclaredOnlyが存在すれば親クラスは対象外、BindingFlags.DeclaredOnlyを除けば親クラスを対象とします。
ぜひ覚えておきましょう。
BindingFlagsがリフレクションの肝
前章で登場したGetMethods関数に使用した 「BindingFlags」がリフレクションの肝 。BindingFlagsの組み合わせによって自分の思い通りの情報を取得できるようになります。
ここまでで登場したBindingFlagsをまとめてみました。
BindingFlags | リフレクション対象 | 備考 |
---|---|---|
Instance | インスタンス要素 | - |
Public | public要素 | - |
DeclaredOnly | 宣言元のみ | 外すと親クラスも対象 |
以降の章ではリフレクションを使ってデータを取得する中で、BindingFlagsの使い方を解説していきます。みなさんもここからは BindingFlags に注目して読んでみてください。
public以外のメソッドをリストアップするリフレクション
ここまでpublicメソッドの取得をリフレクションで実現してきました。
では次にprivateやprotectedメソッドを取得してみましょう。
前章コード内の GetMethods の引数を次のように変えて実行してみます。
MethodInfo[] methods = type.GetMethods(
BindingFlags.Instance |
BindingFlags.NonPublic);
すると次のようにログが出力されます。
get_MemberInternalProp
set_MemberInternalProp
get_MemberPrivateProp
set_MemberPrivateProp
get_MemberProtectedProp
set_MemberProtectedProp
MemberPrivateMethod
MemberPrivateMethodArg
MemberProtectedMethod
MemberInternalMethod
get_ParentMemberInternalProp
set_ParentMemberInternalProp
get_ParentMemberProtectedProp
set_ParentMemberProtectedProp
ParentMemberProtectedMethod
ParentMemberInternalMethod
Finalize
MemberwiseClone
「internal」「private」「protected」のメソッドとプロパティが取得できました。しかし、 親クラスのprivateメソッドとプロパティは取得できていない ことにお気づきでしょうか。
つまりGetMethods関数のリフレクション対象内のみprivate情報を取得できるということです。では親クラスのprivate情報はどうやって取得するのでしょうか。
結論、リフレクション対象を親クラスにすればよいのです。つまり GetMethodsの対象を親クラスにする ということです。
親クラスを取得するリフレクション
では親クラスをリフレクションで取得し見ましょう。リフレクションで親クラスへのアクセスはとても簡単で次の2行で実装できます。
Type type = Type.GetType("info.shibuya24.ReflectionSample");
Type baseType = type.BaseType;
取得したTypeに対して 「BaseType」 プロパティで親クラスを取得できます。そして親クラスに対してGetMehods関数を使うことでprivate情報を取得することができるのです。
Type type = Type.GetType("info.shibuya24.ReflectionSample");
Type baseType = type.BaseType;
MethodInfo[] methods = baseType.GetMethods(
BindingFlags.Instance |
BindingFlags.NonPublic);
上記のコードで 親クラスのprivateメソッド を取得できるようになりました。
リフレクションで取得したメソッドを実行する方法
次にリフレクションで取得したメソッドを実行してみましょう。
手順は簡単です。
- 型情報の取得
- インスタンス化
- GetMethodでメソッド取得
- Invokeで実行
ソースコードに落とし込むと次のようになります。
// 1.型情報取得
Type type = Type.GetType("info.shibuya24.ReflectionSample");
// 2.インスタンス化
object instance = Activator.CreateInstance(type);
// 3.GetMethodでメソッド取得
MethodInfo method = type.GetMethod("MemberPrivateMethod",
BindingFlags.Instance |
BindingFlags.NonPublic);
// 4.Invokeで関数実行
method.Invoke(instance, null);
ポイントは「GetMethod」と「Invoke」です。
GetMethodで呼びたいメソッド名(文字列)とBindingFlagsで MethodInfo型 のインスタンスを取得します。
MethodInfoの Invoke関数 を実行することでメソッドを呼び出すことができるのです。
引数付きのメソッドを文字列から実行するリフレクション
次に引数ありのメソッドを実行する方法です。先の例では引数がありませんでした。引数ありメソッド「MemberPrivateMethodArg」をリフレクションで取得して実行してみます。
次のコードを見てみましょう。
MethodInfo method = type.GetMethod("MemberPrivateMethodArg",
BindingFlags.Instance |
BindingFlags.NonPublic);
method.Invoke(instance, new object[] { 80, "shibuya24" });
MethodInfoの第2引数に呼びたいメソッドの引数を設定します。型はobject型の配列です。
今回のサンプルは、第1引数がint型、第2引数が文字列型だったため、以下の配列が引数になります。
new object[] { 80, "shibuya24" }
このようにリフレクションで取得した引数ありのメソッドも問題なく実行できます。
メソッドの引数を調べるリフレクション
メソッドは取得できたけど引数がわからないということもあると思います。リフレクションを使えば引数情報も取得可能です。
private void MemberPrivateMethodArg(int id, string name)
{
Debug.Log($"{nameof(MemberPrivateMethodArg)} id : {id}, name : {name}");
}
上記の引数ありメソッドをリフレクションで実行してみましょう。次のソースコードを見てみてください。
// GetParametersで引数情報を取得
ParameterInfo[] parameters = methodInfo.GetParameters();
foreach (var p in parameters)
{
Debug.Log(p.ParameterType.Name);
}
上記のコードを実行すると次のように型名が出力されます。
Int32
String
第1引数がInt32(int型)、第2引数がString型(文字列型)ということが分かります。
MethodInfoの GetParameters関数 を呼ぶことでParameterInfo型の配列を取得できます。ParameterInfo型に引数情報が詰まっています。
ParameterInfoの「ParameterType」から引数の型を取得しているのです。
もし取得したメソッドの引数がわからないときは GetParameters と ParameterType を活用してみましょう。
メソッド戻り値の型を調べるリフレクション
メソッド名、引数情報ともにリフレクションで取得できるようになりました。最後に戻り値の型情報をリフレクションで取得してみます。
methodInfo.GetParameters();
// ReturnTypeで戻り値の型情報を取得
Debug.Log(methodInfo.ReturnType);
上記コードの通り、戻り値の型の取得はMethodInfoの ReturnType を使います。
以上で一通りのメソッド情報を取得できるようになったかと思います。
クラスメソッドへアクセスするリフレクション
前章まででインスタンスメソッドへのアクセスはできるようになりました。次にクラスメソッドにアクセスしてみます。
ポイントはBindingFlagsです。
MethodInfo[] methodInfos = type.GetMethods(
BindingFlags.Public
| BindingFlags.Static
| BindingFlags.NonPublic
| BindingFlags.FlattenHierarchy
);
foreach (var m in methodInfos) Debug.Log(m.Name);
ポイントは 「BindingFlags.Static」 です。BindingFlags.Staticを指定するとstatic要素を取得できます。
もう1つ新しい要素として「BindingFlags.FlattenHierarchy」です。static要素を取得する場合「BindingFlags.FlattenHierarchy」を指定しないと親クラスのstatic要素は取得できません。
親クラスのstatid要素を取得したい場合は 「BindingFlags.FlattenHierarchy」 を指定しましょう。
BindingFlags.FlattenHierarchyの注意点
BindingFlags.FlattenHierarchyには注意点があります。それは 親クラスのprivate要素は取得できない ことです。
「BindingFlags.FlattenHierarchy」と「BindingFlags.NonPublic」を指定しても親クラスのprivate要素は取得できません。
親クラスのprivate static要素にアクセスするためには、type.baseTypeでリフレクションの対象を親クラスに変更します。
MethodInfo[] methodInfos = type.baseType.GetMethods(
BindingFlags.Static
| BindingFlags.NonPublic
);
上記のように「baseType」で親クラスを指定することでprivate statid要素にアクセスできるようになるのです。
リフレクション変数編
ここまでメソッドを取得するリフレクションを解説してきました。ここからはリフレクションを使って変数を解析、処理をしていきます。
- 変数リストの取得
- 変数の値の取得
- 変数の値の更新
変数については上記の3種類の処理をリフレクションで解説していきます。
1.変数リストを取得するリフレクション
最初に変数リストを取得してみましょう。基本的にメソッドのときと同様でBindingFlagsを調整して取得する変数をフィルタリングします。
// public以外のインスタンス変数リストを取得
FieldInfo[] fieldInfos = type.GetFields(
BindingFlags.NonPublic
| BindingFlags.Instance
| BindingFlags.DeclaredOnly
);
クラス内の変数リストはGetFields関数を使います。上記のコードを実行するとクラス内に宣言された変数情報リストを取得できます。
2.変数の値を取得するリフレクション
次に変数に格納された値を取得します。早速次のソースコードを見ていきましょう。
// 変数情報の取得
FieldInfo fieldInfo = type.GetField("MemberPrivateField",
BindingFlags.NonPublic
| BindingFlags.Instance
);
// GetValueで値を取得
var value = fieldInfo.GetValue(instance);
Debug.Log(value);
FieldInfoのGetValue関数を使って変数の値を取得できます。GetValueの引数には生成したインスタンスを渡してください。
3.変数の値を更新するリフレクション
変数リフレクション最後は値の更新です。早速ソースコードから見ていきましょう。
// 変数情報の取得
FieldInfo fieldInfo = type.GetField("MemberPrivateField",
BindingFlags.NonPublic
| BindingFlags.Instance
);
// SetValuで値の更新
fieldInfo.SetValue(instance, 120);
リフレクションで値の更新は SetValue を使います。第1引数にはインスタンス、第2引数には更新したい値を代入してください。
変数の型を調べるリフレクション
そもそも変数の型が分からないこともありますよね。リフレクションを使って変数の方を調べてみましょう。
// 変数情報の取得
FieldInfo fieldInfo = type.GetField("MemberPrivateField",
BindingFlags.NonPublic
| BindingFlags.Instance
);
// 変数の型を取得
Type fieldType = fieldInfo.FieldType;
Debug.Log(fieldType.Name);
上記コードのようにFieldInfoの 「FieldType」 から変数の型を取得できます。もし変数の型が分からないときに活用してみてください。
リフレクションまとめ
本記事では、Unityで使うC#のリフレクションについて解説してきました。簡単に内容をまとめます。
まずはリフレクションでよく使う関数について。
関数 | 内容 |
---|---|
GetMethod | メソッド取得 |
GetMethods | メソッドリスト取得 |
Invoke | メソッド実行 |
GetField | 変数取得 |
GetFields | 変数リスト取得 |
GetValue | 変数の値取得 |
SetValue | 変数の値を更新 |
リフレクションの肝となるBindingFlagはこちら。
BindingFlags | 取得対象 | 備考 |
---|---|---|
Public | public要素 | - |
NonPublic | public以外の要素 | 親クラスのprivateは取得不可 |
Instance | インスタンス要素 | - |
Static | Static要素 | - |
DeclaredOnly | 宣言クラスのみ | インスタンス要素のみ |
FlattenHierarchy | 親クラスを含む | Static要素のみ |
リフレクションを使えるようになると 開発の自由度 が上がります。なぜなら通常アクセスできない Unity内部の機能を使える ためです。
内部クラスにアクセスすることで今まで作れなかったツールや機能を開発できるようになり開発の幅が一気に広がります。
しかし便利な一方 リフレクションは負荷が高い です。使い所を見定める必要があります。オオバは基本的に ツールやエディタ拡張でのみ使用 することにしています。基本的にランタイムコードでは使いません。
また内部クラスへのアクセスは十分注意してください。基本的に 自己責任 です。Unityのバージョンアップによって動かなくなることもあるため注意しましょう。
以上の注意点を把握した上でリフレクションと付き合ってみてください。開発がもっと面白くなりますよ。

筆者のXをフォローしよう
Unityオブジェクトの描画順の制御って難しいですよね。
この度、Unityの描画順を体系的に学べる「Unity描画順の教科書」を執筆しました。
Unityの描画順を基礎から学びたい方はぜひ確認してみてください!
→ Unity描画順の教科書
最後まで読んでいただきありがとうございました!
すばらしいリフレクションライフをお過ごしください。