I have the following class:
private sealed class Person
{
public string Name { get; }
public int Age { get; }
public Person(string name)
{
Name = name;
}
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
And the following method to dynamically create an instance of a ConstructorInfo
:
public static Func<object[], T> GetBuilder<T>(ConstructorInfo constructor)
{
var type = constructor.ReflectedType;
var ctorParams = constructor.GetParameters();
var dynamicMethod = new DynamicMethod("Create_" + constructor.Name, type, new[] { typeof(object[]) }, type, true);
var ilGen = dynamicMethod.GetILGenerator();
/*
* Cast each argument of the input object array to the appropriate type
* The order of objects should match the order set by the Ctor
* It is also assumed the length of object array args is same length as Ctor args.
* Exceptions for the delegate that mean the above weren't satisfied:
* InvalidCastException, IndexOutOfRangeException
*/
for (var i = 0; i < ctorParams.Length; i++)
{
// Push Object array
ilGen.Emit(OpCodes.Ldarg_0);
// Push the index to access
ilGen.Emit(OpCodes.Ldc_I4, i);
// Push the element at the previously loaded index
ilGen.Emit(OpCodes.Ldelem_Ref);
// Cast the object to the appropriate Ctor Parameter Type
var paramType = ctorParams[i].ParameterType;
ilGen.Emit(paramType.IsValueType ? OpCodes.Box : OpCodes.Castclass, paramType);
}
// Call the Ctor, all values on the stack are passed to the Ctor
ilGen.Emit(OpCodes.Newobj, constructor);
// Return the new object
ilGen.Emit(OpCodes.Ret);
// Create delegate from our IL, cast and return
return (Func<object[], T>)dynamicMethod.CreateDelegate(typeof(Func<object[], T>));
}
I then use the method to create two instances of this class one for each of the constructors:
var ctorOne = typeof(Person).GetConstructors(BindingFlags.Public | BindingFlags.Instance)[0];
var instanceBuilderOne = GetBuilder<Person>(publicCtor[0]);
var instanceOne = instanceBuilderOne(new object[] { "Foo"});
instanceOne.Name.Dump(); // is "Foo"
var ctorTwo = typeof(Person).GetConstructors(BindingFlags.Public | BindingFlags.Instance)[1];
var instanceBuilderTwo = GetBuilder<Person>(publicCtor[1]);
var instanceTwo = instanceBuilderTwo(new object[] { "Bar", 1});
instanceTwo.Name.Dump(); // is "Bar"
instanceTwo.Age.Dump(); // is 43603896
However for the instanceTwo
instead of getting 1 I am getting 43603896.
Hitting the breakpoint in the related constructor does indeed show 43603896 being passed to the instance but I cannot figure out why!?
First,
OpCodes.Box
is obviously wrong here, because you want to unbox int from object, not box it.Now what
OpCodes.Unbox
does is it unboxes value and pushes reference to unboxed value to the stack. That reference is what you see instead of "1". If you want to useOpCodes.Unbox
, correct way is this:But easier to just use
OpCodes.Unbox_Any
which will basically do the same, but in one line: