なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ

書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

2022/05/29

C# でリフレクションに ref パラメータを使いたい

カテゴリー:C#

たとえば以下のような ref パラメータを持ったメソッドがあったとして、

private static void ShaderErrorListUI(Shader shader, ShaderMessage[] messages, ref Vector2 scrollPosition)
{
    // ...
}

これをリフレクション経由で呼びたい場合は、多分普通に作るとこうなる。

var t = typeof(typeof(Editor).Assembly.GetType("UnityEditor.ShaderInspector));
var m = t.GetMethod("ShaderErrorListUI", BindingFlags.NonPublic | BindingFlags.Static);

m.Invoke(null, new object[] { shader, messages, scrollPosition });

ただ、これだとパフォーマンス的にあまりよくないので、式木を使ってこんな感じにするとする。

var args = Expression.Parameter(typeof(object[]), "args");
var body = m.GetParameters().Select((w, i) => Expression.Convert(Expression.ArrayIndex(args, Expression.Constant(i)), w.ParameterType)).Cast<Expression>().ToArray();
var call = Expression.Call(null, m, body);

var invoke = Expression.Lambda<Action<object[]>>(body, args).Compile();

invoke.Invoke(new object[] { shader, messages, scrollPosition });

こうすると式木は 1 度だけコンパイルされるのでリフレクションよりは早く動く......のだけど、実は呼び出し時エラーになる。

System.ArgumentException: Type must not be ByRef (Parameter 'type')

というのも、実は ref みたいな引数にパラメータが付いている場合は、 T に対して MakeByRefType() が呼ばれたメソッドなので、キャストできない。
ということで、以下のように ParameterType の所に、下記のようなメソッドを挟んであげると良い。

private static Type UnpackType(Type t)
{
    if (t.IsByRef)
        return t.GetElementType();
    return t;
}

ということで、メモでした。