如何发出一个返回引用的动态方法?

6

我正在学习ref返回的细节,并且在发出一个通过引用返回的动态方法时遇到了困难。

手工制作的lambda表达式和现有方法按预期工作:

class Widget
{
    public int Length;
}
delegate ref int WidgetMeasurer(Widget w);

WidgetMeasurer GetMeasurerA()
{
    return w => ref w.Length;
}

static ref int MeasureWidget(Widget w) => ref w.Length;
WidgetMeasurer GetMeasurerB()
{
    return MeasureWidget;
}

但是发出动态方法失败了。 注意:我在这里使用Sigil。 抱歉,我对System.Reflection.Emit不太熟悉。

WidgetMeasurer GetMeasurerC()
{
    FieldInfo lengthField = typeof(Widget).GetField(nameof(Widget.Length));
    var emitter = Emit<WidgetMeasurer>.NewDynamicMethod()
        .LoadArgument(0)
        .LoadFieldAddress(lengthField)
        .Return();
    return emitter.CreateDelegate();
}

这在NewDynamicMethod时失败,抛出 '返回类型包含一些无效类型(即null、ByRef)'的异常。这很有道理,因为我了解到,在WidgetMeasurer底层返回的是Int32&
问题是,有没有我可以使用的第一方或第三方技术来发射代码,模仿前两个示例(我已经知道它们能够正确工作)?如果没有,这个限制是否是逻辑上的? 编辑:我已经尝试了等价的System.Reflection.Emit代码,并得到了相同的异常(如预期所述)。
WidgetMeasurer GetMeasurerD()
{
    FieldInfo lengthField = typeof(Widget).GetField(nameof(Widget.Length));

    Type returnType = typeof(int).MakeByRefType();
    Type[] paramTypes = { typeof(Widget) };
    DynamicMethod method = new DynamicMethod("", returnType, paramTypes);

    ILGenerator il = method.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldflda, lengthField);
    il.Emit(OpCodes.Ret);

    return (WidgetMeasurer)method.CreateDelegate(typeof(WidgetMeasurer));
}

看起来 DynamicMethod 不支持引用返回,根据 这个 提出的问题。 - Rob
@Rob 很好知道,谢谢。 - user6027203
我在这里漏掉了什么吗?甚至使用反射也无法创建一个预期工作的WidgetMeasurer委托实例。例如,WidgetMeasurer m = w => ref w.Length; ref int x = m(new Widget());不会编译,因为m返回的是int而不是ref int - Mike Zboray
@mikez 预期的用法是 GetMeasurerA()(widget) = 7;,这确实有效。 - Rob
@mikez,你能详细说明整个过程吗?或者在回答中解释一下?有什么方法可以解决这个问题吗?我尝试为TypeBuilder生成一个返回引用实例方法,但是得到了相同的异常。 - user6027203
显示剩余2条评论
1个回答

4
我不知道为什么DynamicMethod存在这个限制,但以下方式适用于我。一个区别是我们手动定义了自己的动态程序集。另一个区别是,由于这是单独的程序集,所以 Widget 需要公共可见(或者如果你适当地为动态程序集命名,可以在父程序集上使用InternalsVisibleTo)。
static void Main(string[] args)
{
    var widget = new Widget();
    GetLengthMeasurer()(widget) = 7;
    Console.WriteLine(widget.Length);
}

private static WidgetMeasurer GetLengthMeasurer()
{
    var fieldInfo = typeof(Widget).GetField("Length");
    var asmName = new AssemblyName("WidgetDynamicAssembly." + Guid.NewGuid().ToString());
    var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndCollect);
    var moduleBuilder = asmBuilder.DefineDynamicModule("<Module>");
    var typeBuilder = moduleBuilder.DefineType("WidgetHelper");
    var methodBuilder = typeBuilder.DefineMethod("GetLength", MethodAttributes.Static | MethodAttributes.Public, typeof(int).MakeByRefType(), new[] { typeof(Widget) });
    var ilGen = methodBuilder.GetILGenerator();
    ilGen.Emit(OpCodes.Ldarg_0);
    ilGen.Emit(OpCodes.Ldflda, fieldInfo);
    ilGen.Emit(OpCodes.Ret);
    var type = typeBuilder.CreateType();
    var mi = type.GetMethod(methodBuilder.Name);
    var del = (WidgetMeasurer)mi.CreateDelegate(typeof(WidgetMeasurer));
    return del;
}

输出:

7


谢谢 Mike,这个对我有用。我的第一次尝试使用 TypeBuilder 实现因为与无关的原因再次抛出 'The return Type contains some invalid type (i.e. null, ByRef)' - user6027203

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接