我想创建一个动态方法,该方法采用Int32
参数并返回其字符串表示形式:
public class Item
{
public int Age { get; } = 22;
}
static void CreateDynamicMethod()
{
var ageGet = typeof(Item).GetProperty("Age").GetGetMethod(true);
var intToString = typeof(int).GetMethod("ToString", new Type[] { });
var dm = new DynamicMethod("getAgeString", typeof(string), new[] { typeof(Item) }, typeof(Item).Module);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); //load Item instance on stack
il.Emit(OpCodes.Callvirt, ageGet); //age 44 on stack
il.Emit(OpCodes.Call, intToString); //call tostring for age, "44" on stack now
il.Emit(OpCodes.Ret); //return "44"
var agestr = (Func<Item, string>)dm.CreateDelegate(typeof(Func<Item, string>));
Console.WriteLine(agestr.Invoke(new Item()));
}
但是该方法失败,异常为Object reference not set to an instance of an object
。我错过了什么?
更新:我检查了方法的C#版本的MSIL:
static string GetAge(Item item)
{
return item.Age.ToString();
}
我发现我需要在调用intToString
之前从堆栈中弹出整数。完整版:
var dm = new DynamicMethod("getAgeString", typeof(string), new[] { typeof(Item) }, typeof(Item).Module);
var il = dm.GetILGenerator();
il.DeclareLocal(typeof(int)); //[NEW] declare local integer variable
il.Emit(OpCodes.Ldarg_0); //load Item instance on stack
il.Emit(OpCodes.Callvirt, ageGet); //age 44 on stack now
il.Emit(OpCodes.Stloc_0); //[NEW] pop ineteger from stack to local variable
il.Emit(OpCodes.Ldloca_S, 0); //[NEW] load address of integer variable onto stack
il.Emit(OpCodes.Call, intToString); //call tostring for age, "44" on stack now
il.Emit(OpCodes.Ret); //return "44"
var agestr = (Func<Item, string>)dm.CreateDelegate(typeof(Func<Item, string>));
Console.WriteLine(agestr.Invoke(new Item()));
现在可以使用了。
答案 0 :(得分:1)
我不是IL专业人士,但我认为您需要对int进行包装,以在其上调用ToString。我的猜测是JIT将22
整数值视为指向对象的指针。然后,运行时将访问冲突转换为对小指针值所做的NRE。
我建议完全放弃反射发射并使用表达式树。更简单,相同的性能。