How can I emit a dynamic method returning a ref?
Asked Answered
P

1

6

I'm navigating the ins and outs of ref returns, and am having trouble emitting a dynamic method which returns by ref.

Handcrafted lambdas and existing methods work as expected:

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;
}

But emitting a dynamic method fails. Note: I'm using Sigil here. Apologies, I'm less familiar with 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();
}

This fails at NewDynamicMethod, throwing 'The return Type contains some invalid type (i.e. null, ByRef)'. Which makes sense, since I understand that under the hood WidgetMeasurer returns an Int32&.

The question is, is there some first- or third-party technique I can employ to emit code mimicking the first two examples (which I empirically know work correctly)? If not, is this restriction a logical one?

EDIT: I've tried the equivalent System.Reflection.Emit code and got the same exception (as expected):

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));
}
Prelacy answered 26/7, 2017 at 0:57 Comment(7)
It would appear that DynamicMethod does not support ref returns, judging by this issue raisedLibyan
@Libyan Good to know, thank you.Prelacy
Am I missing something here? You can't even create a delegate instance of WidgetMeasurer without reflection that works as intended. For example, WidgetMeasurer m = w => ref w.Length; ref int x = m(new Widget()); won't compile because m returns int not ref int.Quinte
@mikez The intended usage is GetMeasurerA()(widget) = 7; which does workLibyan
@Libyan Ah I totally forgot the ref on the rhs in that assignment.Quinte
I can say it does work if you do the whole song and dance with a full dynamic assembly. I don't know why DynamicMethod has this restriction.Quinte
@mikez Can you elaborate on the whole song and dance, maybe in an answer? What works? I tried generating a return-by-ref instance method for a TypeBuilder and got the same exception.Prelacy
Q
4

I don't know why this limitation exists for DynamicMethod, but the following worked for me. One difference is that we are defining our own dynamic assembly by hand. Another difference is that since this is separate assembly, Widget needs to be public (or if you name the dynamic assembly appropriately you could use InternalsVisibleTo on the parent assembly).

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;
}

Output:

7

Quinte answered 26/7, 2017 at 4:46 Comment(1)
Cheers mike, this works for me. My first attempt at aTypeBuilder implementation was throwing 'The return Type contains some invalid type (i.e. null, ByRef)' again for an unrelated reason.Prelacy

© 2022 - 2024 — McMap. All rights reserved.