Reputation: 3811
I have a class like below code :
public class MyClass
{
public int MyProperty1 { get; set; }
}
and I hope to create a dynamic method call tostring
public static string MyProperty1ToString(MyClass o){
return o.MyProperty1.ToString();
}
MyProperty1ToString:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: callvirt UserQuery+MyClass.get_MyProperty1
IL_0007: stloc.0
IL_0008: ldloca.s 00
IL_000A: call System.Int32.ToString
IL_000F: stloc.1
IL_0010: br.s IL_0012
IL_0012: ldloc.1
IL_0013: ret
so i try to use Emit to create method,but get error
InvalidProgramException : common language runtime detected an invalid program
public class Program
{
public static void Main()
{
var obj = new MyClass() { MyProperty1 = 123 };
var prop = obj.GetType().GetProperty("MyProperty1");
var func = GetByPropertyCallToStringFunction<MyClass>(prop);
var data = func(obj); //InvalidProgramException : common language runtime detected an invalid program
}
public static Func<T, string> GetByPropertyCallToStringFunction<T>(PropertyInfo prop)
{
var type = prop.DeclaringType;
var propGetMethod = prop.GetMethod;
var propType = prop.PropertyType;
DynamicMethod dynamicMethod = new DynamicMethod($"{prop.Name}_method", typeof(string), new Type[] { type }, type.Module);
var toStringMethod = propType.GetMethods(BindingFlags.Public | BindingFlags.Instance).Where(p => p.Name == "ToString").First();
ILGenerator il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, propGetMethod);
il.Emit(OpCodes.Stloc);
il.Emit(OpCodes.Ldloca_S, 00);
il.Emit(OpCodes.Call, toStringMethod);
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Br_S, "IL_0012");
il.Emit(OpCodes.Ldloc_1);
il.Emit(OpCodes.Ret);
var invoke = (Func<T, string>)dynamicMethod.CreateDelegate(typeof(Func<T, string>));
return invoke;
}
}
how can i get emit runtime compiler details error message?
I have no idea when only InvalidProgramException
Upvotes: 3
Views: 288
Reputation: 3629
Got it. Further to the fact that branch labels must be explicitly declared (which ckuri already correctly discovered), you also have to declare locals - including their type - before referencing them.
Here's the code:
var propType = prop.PropertyType;
var propGetMethod = prop.GetMethod;
ILGenerator il = dynamicMethod.GetILGenerator();
LocalBuilder local0 = il.DeclareLocal(typeof(propType));
LocalBuilder local1 = il.DeclareLocal(typeof(string));
Label label0 = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, propGetMethod);
il.Emit(OpCodes.Stloc, local0);
il.Emit(OpCodes.Ldloca_S, local0);
il.Emit(OpCodes.Call, toStringMethod);
il.Emit(OpCodes.Stloc, local1);
il.Emit(OpCodes.Br_S, label0);
il.MarkLabel(label0);
il.Emit(OpCodes.Ldloc, local1);
il.Emit(OpCodes.Ret);
return (Func<T, string>)dynamicMethod.CreateDelegate(typeof(Func<T, string>));
This version however is not optimized very much. It seems you took it from code that was compiled for debug mode.
The following version does the same, but more efficiently, mainly requiring no labels and only one local:
var propType = prop.PropertyType;
var propGetMethod = prop.GetMethod;
ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
LocalBuilder local0 = iLGenerator.DeclareLocal(propType);
iLGenerator.Emit(OpCodes.Ldarg_0);
iLGenerator.Emit(OpCodes.Call, propGetMethod);
iLGenerator.Emit(OpCodes.Stloc, local0);
iLGenerator.Emit(OpCodes.Ldloca_S, local0);
iLGenerator.Emit(OpCodes.Call, toStringMethod);
iLGenerator.Emit(OpCodes.Ret);
return (Func<T, string>)dynamicMethod.CreateDelegate(typeof(Func<T, string>));
Upvotes: 3