Search code examples
c#reflectiondelegatesreflection.emitref

How can I emit a dynamic method returning a ref?


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

Solution

  • 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