Search code examples
c#delegatesconstructorreflection.emitdynamic-method

c# Emitting Dynamic Method Delegate to Load Parametrized Constructor Problem


I am trying create a delegate representation of constructor by emitting a Dynamic Method, which has to match this very "loosely-typed" signature so it can be used with any kind of parametrized constructor:

public delegate Object ParamsConstructorDelegate(params object[] parameters);

and the code for this creating the delegate looks like (note this is for Silverlight)

public static ParamsConstructorDelegate CreateDelegate(ConstructorInfo constructor)
    {
        Guard.ArgumentNotNull(constructor, "constructor");
        Guard.ArgumentValue(constructor.GetParameters().Length == 0, MUSTBE_PARAMETERIZED_CONSTRUCTOR);

        var _argumentTypes = new Type[] { typeof(object[]) };
        var _parameters = constructor.GetParameters();
        var _parameterTypes = _parameters.Select((p) => p.ParameterType).ToArray();

        var _sourceType = constructor.DeclaringType;
        var _method = new DynamicMethod(constructor.Name, _sourceType, _argumentTypes);
        var _gen = _method.GetILGenerator();

        for (var _i = 0; _i < _parameters.Length; _i++)
        {
            if (_parameters[_i].IsOut || _parameterTypes[_i].IsByRef)
            {
                if (_i < 128)
                {
                    _gen.Emit(OpCodes.Ldarga_S, (byte)_i);
                }
                else
                    _gen.Emit(OpCodes.Ldarga, _i);
            }
            else
            {
                switch (_i)
                {
                    case 0:
                        _gen.Emit(OpCodes.Ldarg_0, _i);
                        break;
                    case 1:
                        _gen.Emit(OpCodes.Ldarg_1, _i);
                        break;
                    case 2:
                        _gen.Emit(OpCodes.Ldarg_2, _i);
                        break;
                    case 3:
                        _gen.Emit(OpCodes.Ldarg_3, _i);
                        break;
                    default:
                        if (_i < 128)
                            _gen.Emit(OpCodes.Ldarg_S, (byte)_i);
                        else
                            _gen.Emit(OpCodes.Ldarg, _i);
                        break;
                }
            }
        }
        _gen.Emit(OpCodes.Newobj, constructor);
        _gen.Emit(OpCodes.Ret); ;

        return (ParamsConstructorDelegate)_method.CreateDelegate(typeof(ParamsConstructorDelegate));
    }

Now, I'm getting a "Operation could destabilize the runtime." verification exception, obviously the IL is wrong, so I hoping someone could correct me.

Thanks


Solution

  • I can see two problems; firstly you don't need the _i for the Ldarg_0 thru Ldarg_3 cases (it is implicit). Secondly - your delegate only has one arg (the array). You're going to need to get the items out of the array and cast - something like below (which handles pass-by-value only; for ref / out you'll have to define a local and use stloc / ldloca / etc):

    using System;
    using System.Reflection.Emit;
    public delegate object ParamsConstructorDelegate(params object[] parameters);
    public class Foo
    {
        string s;
        int i;
        float? f;
        public Foo(string s, int i, float? f)
        {
            this.s = s;
            this.i = i;
            this.f = f;
        }
    }
    
    static class Program
    {
        static void Main()
        {
            var ctor = Build(typeof(Foo));
            Foo foo1 = (Foo)ctor("abc", 123, null);
            Foo foo2 = (Foo)ctor(null, 123, 123.45F);
        }
        static ParamsConstructorDelegate Build(Type type)
        {
            var mthd = new DynamicMethod(".ctor", type,
                new Type[] { typeof(object[]) });
            var il = mthd.GetILGenerator();
            var ctor = type.GetConstructors()[0]; // not very robust, but meh...
            var ctorParams = ctor.GetParameters();
            for (int i = 0; i < ctorParams.Length; i++)
            {
                il.Emit(OpCodes.Ldarg_0);
                switch (i)
                {
                    case 0: il.Emit(OpCodes.Ldc_I4_0); break;
                    case 1: il.Emit(OpCodes.Ldc_I4_1); break;
                    case 2: il.Emit(OpCodes.Ldc_I4_2); break;
                    case 3: il.Emit(OpCodes.Ldc_I4_3); break;
                    case 4: il.Emit(OpCodes.Ldc_I4_4); break;
                    case 5: il.Emit(OpCodes.Ldc_I4_5); break;
                    case 6: il.Emit(OpCodes.Ldc_I4_6); break;
                    case 7: il.Emit(OpCodes.Ldc_I4_7); break;
                    case 8: il.Emit(OpCodes.Ldc_I4_8); break;
                    default: il.Emit(OpCodes.Ldc_I4, i); break;
                }
                il.Emit(OpCodes.Ldelem_Ref);
                Type paramType = ctorParams[i].ParameterType;
                il.Emit(paramType.IsValueType ? OpCodes.Unbox_Any
                    : OpCodes.Castclass, paramType);
            }
            il.Emit(OpCodes.Newobj, ctor);
            il.Emit(OpCodes.Ret);
            return (ParamsConstructorDelegate)
                mthd.CreateDelegate(typeof(ParamsConstructorDelegate));
        }
    }
    

    For info - I'm lazy - if I want to know what IL to write I write it in C# and then load it into reflector. For example, to do this I wrote a method:

    static object CreateFoo(object[] vals)
    {
        return new Foo((string)vals[0], (int)vals[1], (float?)vals[2]);
    }
    

    and reversed it from there