Jon
Jon

Reputation: 4295

Reflection.Emit create object with parameters

I'm creating a dynamic function to create an object at runtime given an object[] of constructor params. I keep getting the generic exception 'Operation could destablise the runtime' and I can't see what I've done wrong.

The method works fine if the created object needs no constructor arguments - so the problem must be in the code in the for loop.

The code indexes into the given object[] putting the object onto the stack after which the ctor is called and the object returned.

Any ideas???

internal static Func<object[], object> CreateObjectFactoryMethodWithCtorParams(ConstructorInfo ctor, int ctorArgsLength)
    {
        Func<object[], object> factoryMethod = null;
        if (ctor != null)
        {
            var dm = new DynamicMethod(string.Format("_CreationFacotry_{0}", Guid.NewGuid()), typeof(object), new Type[] { typeof(object[])}, true);
            var il = dm.GetILGenerator();
            il.DeclareLocal(typeof(int));
            il.DeclareLocal(typeof(object));

            il.BeginExceptionBlock();

            il.Emit(OpCodes.Ldc_I4_0); // [0]
            il.Emit(OpCodes.Stloc_0); //[nothing]

            for (int i = 0; i < ctorArgsLength; i++)
            {
                EmitInt32(il, i); // [args][index]
                il.Emit(OpCodes.Stloc_0); // [args][index]
                il.Emit(OpCodes.Ldarg_0); //[args]
                EmitInt32(il, i); // [args][index]
                il.Emit(OpCodes.Ldelem_Ref); // [item-in-args-at-index]
            }
            il.Emit(OpCodes.Newobj, ctor); //[new-object]
            il.Emit(OpCodes.Stloc_1); // nothing

            il.BeginCatchBlock(ExceptionType); // stack is Exception
            il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
            il.EmitCall(OpCodes.Call, EmitGeneratorType.GetMethod("ThrowFactoryException"), null);
            il.EndExceptionBlock();

            il.Emit(OpCodes.Ldloc_1); //[new-object]
            il.Emit(OpCodes.Ret);
            factoryMethod = (Func<object[], object>)dm.CreateDelegate(typeof(Func<object[], object>));
        }
        else
        {
            throw new EmitGeneratorException("Cannot create instance factory for a null ctor instance");
        }
        return factoryMethod;
    }

        private static void EmitInt32(ILGenerator il, int value)
        {
            switch (value)
            {
                case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
                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:
                    if (value >= -128 && value <= 127)
                    {
                        il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
                    }
                    else
                    {
                        il.Emit(OpCodes.Ldc_I4, value);
                    }
                    break;
            }
        }

Calling code

    Func<object[], object> factoryFunction = GetFunction(someCtor, new object[] { arg1, arg2});
var obj = factoryFunction(new object[] {new SomeClass, "A String" }); //input ctor args

Upvotes: 4

Views: 5023

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1064204

It works fine for me, as long as I make all the constructor parameters object:

class SomeClass {
    public SomeClass(object s, object t) { }
}
static void Main()
{
    var someCtor = typeof(SomeClass).GetConstructors()[0];
    Func<object[], object> factoryFunction = CreateObjectFactoryMethodWithCtorParams(someCtor, someCtor.GetParameters().Length);
    var obj = factoryFunction(new object[] {"A String", 123 });
}

I think the problem is that you haven't done any conversions from the objects from the array to the actual constructor types, noting that you need to consider both reference types and value-types (unbox). Like so:

var parameters = ctor.GetParameters();
for (int i = 0; i < parameters.Length ; i++)
{
    EmitInt32(il, i); // [index]
    il.Emit(OpCodes.Stloc_0); // [nothing]
    il.Emit(OpCodes.Ldarg_0); //[args]
    EmitInt32(il, i); // [args][index]
    il.Emit(OpCodes.Ldelem_Ref); // [item-in-args-at-index]
    var paramType = parameters[i].ParameterType;
    if (paramType != typeof(object))
    {
        il.Emit(OpCodes.Unbox_Any, paramType); // same as a cast if ref-type
    }
}
il.Emit(OpCodes.Newobj, ctor); //[new-object]
il.Emit(OpCodes.Stloc_1); // nothing

as a minor note: since you need to call .GetParameters(), you should not pass in the parameter length as a parameter to the method; that is redundant, and could cause errors when wrong.

This then works with my exmaple:

class SomeClass {
    public SomeClass(string s, int t) { }
}
static void Main()
{
    var someCtor = typeof(SomeClass).GetConstructors()[0];
    Func<object[], object> factoryFunction = CreateObjectFactoryMethodWithCtorParams(someCtor);
    var obj = factoryFunction(new object[] {"A String", 123 });
}

Upvotes: 5

Related Questions