c# - DynamicMethod Reflection 发出对 Func<Task> 的调用

标签 c# reflection.emit dynamicmethod

我正在为一些内部库计算 Reflection.Emit,但在调用作为参数传入的 Func 时遇到了困难。我的场景测试使用的是图中用Linqpad传输到IL的代码 Code to 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);
    }
    
}

代码编译并返回,但是当您调用 WrapScope 委托(delegate)时 del(Func<Task>)它抛出 System.InvalidProgramException:公共(public)语言运行时检测到无效程序。 运行此 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并传入返回的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 看起来毫无意义:我们从堆栈中取出一个变量,将其移动到本地,然后立即将其从本地复制回来并入栈以便返回它。完全抛弃本地的:

gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Callvirt, mi);
gen.Emit(OpCodes.Ret);

关于c# - DynamicMethod Reflection 发出对 Func<Task> 的调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66804398/

相关文章:

C# 检查用户输入的特定文件名并打开文件

c# - ConstructorInfo.Invoke 的 DynamicMethod,我需要考虑什么?

c# - 动态类型影子基类的属性并使用 Reflection.Emit 设置为 protected

reflection - 如何发送到具有 'params' 构造函数的类?

c# - .Net - 传递可变数量的参数

.net - 如何通过 ILGenerator.Emit* 调用 'normal' 方法?

c# - 通过 DynamicMethod 调用可变参数方法

c# - 在 C# 中执行基于存储过程的查询后,如何为 CommandText 重新使用 SqlCommand 对象?

c# - 向分部类添加接口(interface)

c# - 启动后自动登录windows