I was trying to emit the following code as IL code at runtime.
class TestObject {
public int Hello {get;set;}
public int Test {get;set;}
}
static TestObject test(BinaryReader reader) {
var a = new TestObject();
a.Hello = reader.ReadInt32();
a.Test = reader.ReadInt32();
return a;
}
LINQPad shows:
test:
IL_0000: nop
IL_0001: newobj UserQuery+TestObject..ctor
IL_0006: stloc.0 // a
IL_0007: ldloc.0 // a
IL_0008: ldarg.0
IL_0009: callvirt System.IO.BinaryReader.ReadInt32
IL_000E: callvirt UserQuery+TestObject.set_Hello
IL_0013: nop
IL_0014: ldloc.0 // a
IL_0015: ldarg.0
IL_0016: callvirt System.IO.BinaryReader.ReadInt32
IL_001B: callvirt UserQuery+TestObject.set_Test
IL_0020: nop
IL_0021: ldloc.0 // a
IL_0022: stloc.1
IL_0023: br.s IL_0025
IL_0025: ldloc.1
IL_0026: ret
Trying to reproduce it with C#:
var method = new DynamicMethod("DynamicCreate", typeof(TestSubject), new Type[] {typeof(BinaryReader)},
typeof(TestSubject).Module);
var il = method.GetILGenerator();
var properties = from property in typeof(TestSubject).GetProperties()
let orderAttribute =
property.GetCustomAttributes(typeof(OrderAttribute), false).SingleOrDefault() as OrderAttribute
orderby orderAttribute.Order
select property;
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Newobj, typeof(TestSubject).GetConstructors()[0]); // pushes a new instance of testsubject on the stack
il.Emit(OpCodes.Stloc_0); // pop the instance to local variable 0
foreach (var prop in properties)
{
il.Emit(OpCodes.Ldloc_0); // load local variable 0
il.Emit(OpCodes.Ldarg_0); // load argument 0 (the binary reader)
il.Emit(OpCodes.Callvirt, typeof(BinaryReader).GetMethod("ReadInt32", BindingFlags.Public | BindingFlags.Instance)); // call the binary reader method (ReadInt32)
il.Emit(OpCodes.Callvirt, prop.SetMethod); // call the setter of the property (instance of local variable 0 and return value of readint32)
il.Emit(OpCodes.Nop);
}
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Stloc_1);
var label = il.DefineLabel();
il.Emit(OpCodes.Br_S, label);
il.MarkLabel(label);
il.Emit(OpCodes.Ldloc_1); // push the test subject instance
il.Emit(OpCodes.Ret); // and return
var generator = (Load)method.CreateDelegate(typeof(Load));
var reader = new BinaryReader(new MemoryStream(new byte[] {1, 2, 3, 4, 0, 0, 0, 1}));
var test = generator(reader); // exception here
The TestSubject class:
public class TestSubject
{
[Order]
public int Test1 { get; set; }
[Order]
public int Test2 { get; set; }
}
Giving me the following exception:
System.InvalidProgramException {"Die Common Language Runtime hat ein ungültiges Programm gefunden."}
What's wrong with that?
You need to declare locals before you use them. Also, the code can be simplified (the IL was produced in a debug build).
il.DeclareLocal(typeof(TestSubject));
il.Emit(OpCodes.Newobj, typeof(TestSubject).GetConstructors()[0]); // pushes a new instance of testsubject on the stack
il.Emit(OpCodes.Stloc_0); // store the instance in local variable 0
foreach (var prop in properties)
{
il.Emit(OpCodes.Ldloc_0); // load local variable 0
il.Emit(OpCodes.Ldarg_0); // load argument 0 (the binary reader)
il.Emit(OpCodes.Callvirt, typeof(BinaryReader).GetMethod("ReadInt32", BindingFlags.Public | BindingFlags.Instance)); // call the binary reader method (ReadInt32)
il.Emit(OpCodes.Callvirt, prop.SetMethod); // call the setter of the property (instance of local variable 0 and return value of readint32)
}
il.Emit(OpCodes.Ldloc_0); // push the test subject instance
il.Emit(OpCodes.Ret); // and return
You did not define the locals.
Also, the IL here is from debug mode output. It's a bit more convoluted than necessary.
Reflection emit is nearly obsolete (still needed for some cases). It is much easier to generate code like this using expression trees. Delete all of this code and replace it with expression trees.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With