为什么我无法在 C# 中使用 IL Generation 创建对象的实例?
Why am I unable to create an instance of an object using IL Generation in C#?
我有以下 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;
}
}
和下面的方法动态创建一个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>));
}
然后我使用该方法为每个构造函数创建此 class 的两个实例:
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
但是对于 instanceTwo
而不是 1 我得到 43603896。
在相关构造函数中打断点确实显示 43603896 被传递给了实例,但我不明白为什么!?
首先,OpCodes.Box
在这里显然是错误的,因为你想从对象中拆箱而不是装箱。
现在 OpCodes.Unbox
所做的是取消装箱值并将 reference 推入堆栈。该参考是您看到的而不是“1”。如果你想使用OpCodes.Unbox
,正确的方法是这样的:
if (paramType.IsValueType) {
ilGen.Emit(OpCodes.Unbox, paramType);
ilGen.Emit(OpCodes.Ldobj, paramType);
}
else {
ilGen.Emit(OpCodes.Castclass, paramType);
}
但使用 OpCodes.Unbox_Any
更容易,它基本上会做同样的事情,但在一行中:
ilGen.Emit(OpCodes.Unbox_Any, paramType);
我有以下 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;
}
}
和下面的方法动态创建一个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>));
}
然后我使用该方法为每个构造函数创建此 class 的两个实例:
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
但是对于 instanceTwo
而不是 1 我得到 43603896。
在相关构造函数中打断点确实显示 43603896 被传递给了实例,但我不明白为什么!?
首先,OpCodes.Box
在这里显然是错误的,因为你想从对象中拆箱而不是装箱。
现在 OpCodes.Unbox
所做的是取消装箱值并将 reference 推入堆栈。该参考是您看到的而不是“1”。如果你想使用OpCodes.Unbox
,正确的方法是这样的:
if (paramType.IsValueType) {
ilGen.Emit(OpCodes.Unbox, paramType);
ilGen.Emit(OpCodes.Ldobj, paramType);
}
else {
ilGen.Emit(OpCodes.Castclass, paramType);
}
但使用 OpCodes.Unbox_Any
更容易,它基本上会做同样的事情,但在一行中:
ilGen.Emit(OpCodes.Unbox_Any, paramType);