DynamicMethod 反射发出对 Func<Task> 的调用
DynamicMethod Reflection Emit a call to a Func<Task>
我正在为一些内部库找出 Reflection.Emit 并且无法调用作为参数传入的 Func。我的场景测试使用图中用Linqpad转IL的代码
我在 DynamicMethod 中复制 IL 的代码如下
public class ScopeTest
{
public delegate Task WrapScope(Func<Task> value);
public (WrapScope scope,string id) WrapScopeInId()
{
var id = $"wrap~{Guid.NewGuid().ToString().Replace("-",string.Empty)}";
var mi = typeof(Func<Task>).GetMethod("Invoke");
var d = new DynamicMethod(id, typeof(Task), new[] { typeof(Func<Task>) });
var gen = d.GetILGenerator();
var lab = gen.DefineLabel();
gen.Emit(OpCodes.Nop);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt, mi);
gen.Emit(OpCodes.Stloc_0);
gen.Emit(OpCodes.Br_S, lab);
gen.MarkLabel(lab);
gen.Emit(OpCodes.Ldloc_0);
gen.Emit(OpCodes.Ret);
WrapScope del = (WrapScope)d.CreateDelegate(typeof(WrapScope));
return (del,id);
}
}
代码编译并且 returns 但是当您调用 WrapScope 委托时 del(Func<Task>)
它会抛出 System.InvalidProgramException:公共语言运行时检测到无效程序。
这个 DynamicMethod 可能有什么问题 运行?
谢谢
您的主要问题是您在槽 0 中存储了一个变量,但您从未声明槽 0。如果我们查看您的代码 on SharpLab,我们可以看到 IL 声明了每个槽该方法使用:
.locals init (
[0] class [System.Private.CoreLib]System.Threading.Tasks.Task
)
(也就是说我们有 1 个插槽,索引 0,类型为 Task
)。
您使用 ILGenerator.DeclareLocal
对 ILGenerator
执行此操作。我们可以使用 ldloc
/stloc
并传入 returned 的 LocalBuilder
,而不是使用编号的 ldloc.0
/stloc.0
.
var taskLocal = gen.DeclareLocal(typeof(Task));
gen.Emit(OpCodes.Nop);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt, mi);
gen.Emit(OpCodes.Stloc, taskLocal);
gen.Emit(OpCodes.Br_S, lab);
gen.MarkLabel(lab);
gen.Emit(OpCodes.Ldloc, taskLocal);
gen.Emit(OpCodes.Ret);
这一切都很好并且可以工作,但是它包含很多不必要的指令。 Linqpad 在 Debug 中为您提供编译器的输出,它会发出许多不必要的 NOP 等。您会看到 Release 模式下的 SharpLab 不显示这些,我们可以简单地删除它们。 br.s
也是无关紧要的,因为它无条件地跳转到下一条指令,所以我们也可以删除它:
var taskLocal = gen.DeclareLocal(typeof(Task));
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt, mi);
gen.Emit(OpCodes.Stloc, taskLocal);
gen.Emit(OpCodes.Ldloc, taskLocal);
gen.Emit(OpCodes.Ret);
现在 stloc
/ldloc
看起来毫无意义:我们从堆栈中取出一个变量,将其移动到本地,然后立即将其从本地复制回堆栈中为了return吧。完全放弃本地:
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt, mi);
gen.Emit(OpCodes.Ret);
我正在为一些内部库找出 Reflection.Emit 并且无法调用作为参数传入的 Func。我的场景测试使用图中用Linqpad转IL的代码
public class ScopeTest
{
public delegate Task WrapScope(Func<Task> value);
public (WrapScope scope,string id) WrapScopeInId()
{
var id = $"wrap~{Guid.NewGuid().ToString().Replace("-",string.Empty)}";
var mi = typeof(Func<Task>).GetMethod("Invoke");
var d = new DynamicMethod(id, typeof(Task), new[] { typeof(Func<Task>) });
var gen = d.GetILGenerator();
var lab = gen.DefineLabel();
gen.Emit(OpCodes.Nop);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt, mi);
gen.Emit(OpCodes.Stloc_0);
gen.Emit(OpCodes.Br_S, lab);
gen.MarkLabel(lab);
gen.Emit(OpCodes.Ldloc_0);
gen.Emit(OpCodes.Ret);
WrapScope del = (WrapScope)d.CreateDelegate(typeof(WrapScope));
return (del,id);
}
}
代码编译并且 returns 但是当您调用 WrapScope 委托时 del(Func<Task>)
它会抛出 System.InvalidProgramException:公共语言运行时检测到无效程序。
这个 DynamicMethod 可能有什么问题 运行?
谢谢
您的主要问题是您在槽 0 中存储了一个变量,但您从未声明槽 0。如果我们查看您的代码 on SharpLab,我们可以看到 IL 声明了每个槽该方法使用:
.locals init (
[0] class [System.Private.CoreLib]System.Threading.Tasks.Task
)
(也就是说我们有 1 个插槽,索引 0,类型为 Task
)。
您使用 ILGenerator.DeclareLocal
对 ILGenerator
执行此操作。我们可以使用 ldloc
/stloc
并传入 returned 的 LocalBuilder
,而不是使用编号的 ldloc.0
/stloc.0
.
var taskLocal = gen.DeclareLocal(typeof(Task));
gen.Emit(OpCodes.Nop);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt, mi);
gen.Emit(OpCodes.Stloc, taskLocal);
gen.Emit(OpCodes.Br_S, lab);
gen.MarkLabel(lab);
gen.Emit(OpCodes.Ldloc, taskLocal);
gen.Emit(OpCodes.Ret);
这一切都很好并且可以工作,但是它包含很多不必要的指令。 Linqpad 在 Debug 中为您提供编译器的输出,它会发出许多不必要的 NOP 等。您会看到 Release 模式下的 SharpLab 不显示这些,我们可以简单地删除它们。 br.s
也是无关紧要的,因为它无条件地跳转到下一条指令,所以我们也可以删除它:
var taskLocal = gen.DeclareLocal(typeof(Task));
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt, mi);
gen.Emit(OpCodes.Stloc, taskLocal);
gen.Emit(OpCodes.Ldloc, taskLocal);
gen.Emit(OpCodes.Ret);
现在 stloc
/ldloc
看起来毫无意义:我们从堆栈中取出一个变量,将其移动到本地,然后立即将其从本地复制回堆栈中为了return吧。完全放弃本地:
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt, mi);
gen.Emit(OpCodes.Ret);