ByRef parameter and C# - 猫とC#について書くmatarilloの日記
MethodInfo.Invoke で動的に実行したメソッド内で例外が発生した場合 out 引数が書き換えられない、という話。
Reflection.Emit でゴリゴリ自前実装してみたものをコメントに投稿しましたが、インスタンスメソッドの動的実行に対応できてなかったので、対応したコードをこちらで晒しときます。あと、NyaRuRu さんからの指摘事項含め、リファクタリングもしときました。
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Invoker = System.Func<object, object[], object>;
public static class MethodInfoExtension
{
public static object InvokeStrictly(this MethodInfo source, object obj, params object[] parameters)
{
if (source == null)
{
throw new NullReferenceException();
}
int parametersLength = (parameters != null) ? parameters.Length : 0;
if (source.GetParameters().Length != parametersLength)
{
throw new ArgumentException("引数 parameters の要素数とメソッドのパラメータ数が一致しません。");
}
var invokerBuilder = new DynamicMethod("DynamicMethod", typeof(object), new[] { typeof(object), typeof(object[]) }, true);
GenerateCode(source, invokerBuilder);
var invoker = (Invoker)invokerBuilder.CreateDelegate(typeof(Invoker));
return invoker(obj, parameters);
}
private static void GenerateCode(MethodInfo source, DynamicMethod invokerBuilder)
{
ILGenerator ilGenerator = invokerBuilder.GetILGenerator();
List<LocalBuilder> paramLocalBuilderList = GenerateCodeForLocalVariableDeclarationsForParameters(source, ilGenerator);
LocalBuilder resultLocal = GenerateCodeForLocalVariableDeclarationForResult(ilGenerator);
GenerateCodeForSubstitutingsParameterToLocalVariable(paramLocalBuilderList, ilGenerator);
/* Begin try block. */
Label exBlockLabel = ilGenerator.BeginExceptionBlock();
GenerateCodeForCallingSourceMethod(source, paramLocalBuilderList, resultLocal, ilGenerator);
ilGenerator.Emit(OpCodes.Leave, exBlockLabel);
/* End try block. */
/* Begin finally block. */
ilGenerator.BeginFinallyBlock();
GenerateCodeForRestorationParameters(paramLocalBuilderList, ilGenerator);
ilGenerator.EndExceptionBlock();
/* End finally block. */
ilGenerator.Emit(OpCodes.Ldloc_S, resultLocal);
ilGenerator.Emit(OpCodes.Ret);
}
private static List<LocalBuilder> GenerateCodeForLocalVariableDeclarationsForParameters(MethodInfo source, ILGenerator ilGenerator)
{
List<LocalBuilder> localBuilderList = new List<LocalBuilder>();
ParameterInfo[] paramInfos = source.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var paramType = paramInfo.ParameterType;
var localType = paramType.IsByRef ? paramType.GetElementType() : paramType;
localBuilderList.Add(ilGenerator.DeclareLocal(localType));
}
return localBuilderList;
}
private static LocalBuilder GenerateCodeForLocalVariableDeclarationForResult(ILGenerator ilGenerator)
{
return ilGenerator.DeclareLocal(typeof(object), false);
}
private static void GenerateCodeForSubstitutingsParameterToLocalVariable(List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
{
for (int i = 0; i < paramLocalBuilderList.Count; i++)
{
var paramLocalBuilder = paramLocalBuilderList[i];
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldc_I4, i);
ilGenerator.Emit(OpCodes.Ldelem_Ref);
Label passLabel = ilGenerator.DefineLabel();
ilGenerator.Emit(OpCodes.Brfalse, passLabel);
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldc_I4, i);
ilGenerator.Emit(OpCodes.Ldelem_Ref);
ilGenerator.Emit(OpCodes.Unbox_Any, paramLocalBuilder.LocalType);
ilGenerator.Emit(OpCodes.Stloc_S, paramLocalBuilder);
ilGenerator.MarkLabel(passLabel);
}
}
private static void GenerateCodeForCallingSourceMethod(MethodInfo source, List<LocalBuilder> paramLocalBuilderList, LocalBuilder resultLocal, ILGenerator ilGenerator)
{
if (!source.IsStatic)
{
GenerateCodeForPushingContextObject(source, ilGenerator);
}
GenerateCodeForPushingParameters(source, paramLocalBuilderList, ilGenerator);
ilGenerator.Emit(OpCodes.Call, source);
if (source.ReturnType == typeof(void))
{
ilGenerator.Emit(OpCodes.Ldnull);
}
ilGenerator.Emit(OpCodes.Stloc_S, resultLocal);
}
private static void GenerateCodeForPushingContextObject(MethodInfo source, ILGenerator ilGenerator)
{
ilGenerator.Emit(OpCodes.Ldarg_0);
if (source.DeclaringType.IsValueType)
{
ilGenerator.Emit(OpCodes.Unbox, source.DeclaringType);
}
else
{
ilGenerator.Emit(OpCodes.Castclass, source.DeclaringType);
}
}
private static void GenerateCodeForPushingParameters(MethodInfo source, List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
{
ParameterInfo[] paramInfos = source.GetParameters();
for (int i = 0; i < paramInfos.Length; i++)
{
var paramInfo = paramInfos[i];
var paramLocalBuilder = paramLocalBuilderList[i];
bool paramIsByRef = paramInfo.ParameterType.IsByRef;
if (paramIsByRef)
{
ilGenerator.Emit(OpCodes.Ldloca_S, paramLocalBuilder);
}
else
{
ilGenerator.Emit(OpCodes.Ldloc_S, paramLocalBuilder);
}
}
}
private static void GenerateCodeForRestorationParameters(List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
{
for (int i = 0; i < paramLocalBuilderList.Count; i++)
{
var paramLocalBuilder = paramLocalBuilderList[i];
var localType = paramLocalBuilder.LocalType;
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Ldc_I4, i);
ilGenerator.Emit(OpCodes.Ldloc_S, paramLocalBuilder);
if (localType.IsValueType)
{
ilGenerator.Emit(OpCodes.Box, localType);
}
ilGenerator.Emit(OpCodes.Stelem, typeof(object));
}
}
}
using System;
using System.Reflection;
class P
{
public static void Method(out string arg)
{
arg = "out";
throw new Exception();
}
static void Main()
{
object[] args = new object[1];
try
{
MethodInfo targetMethod = typeof(P).GetMethod("Method");
targetMethod.InvokeStrictly(null, args);
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.WriteLine();
}
Console.WriteLine(args[0]);
Console.ReadLine();
}
}
こーゆー IL を動的に構築するコードって、もしかしてメソッド分割しない方が見やすい?