I’ve been working a little bit with dynamic code generation at runtime (classes in Reflection.Emit namespace, collectively referred to as Reflection.Emit). It’s a low level API, that requires you to work with IL operations, keep track of what is on the stack, and requires quite a bit of knowledge about IL and CLR.
I’m no expert in IL, as probably most of developers, but there are ways to make this things easier.
To work my way through generating code, I use iterative approach.
- Write a class/method in C# that exactly (or as closely as possible) reassembles a single small part of what I want my dynamic method to look like. If my dynamic method is a static method – I write a static method. If it has a value type parameter, I write a method with value type parameter. I think you get the picture. The important thing is to really get as close to what you want to achieve with dynamic method as possible, and understand influence of those parts you can’t get exactly the same (for example because of C# limitations).
- I compile the class (also notice differences between mode you compile in – you usually want Release version, not Debug), and open it up in Reflector to see its IL.
- I write my code that does code generation, to generate IL identical to the one of my statically wrote method (taking into account all the variability).
- I save generated assembly, open it up in Reflector, and compare the IL, to the one created earlier, to see if they match. I often switch in Reflector to C# view, to better see some kinds of errors (passing wrong parameters, or calling method on wrong object). If you can’t save your generated code to physical assembly you can’t use this debugger visualizer to see its IL.
- Write and run tests.
- If everything works, go back to step one, add another part of code and repeat until you have all you wanted.
Here’s an example of this approach. It’s an actual code I wrote while working on DynamicProxy feature. The thing that is different here, is that Castle has an additional layer of abstraction on top of Reflection.Emit so I was not working directly with IL. This makes things a little bit easier, because you’re operating on a higher level of abstraction. On the other hand however, you don’t get that tight control over what gets generated, and you have a new API to learn.
Anyway, I actually already had a pretty good idea of what I wanted to do (I only needed to change the way things worked, by calling other constructor in my dynamic method, passing additional parameters). I wrote some tests to verify that my code works the way I wanted it to, and I implemented it. Unfortunately one test didn’t pass with the following error.
One parameter I wanted to pass was an array, passed by reference, and it seemed that there was something wrong with it.
I opened Reflector and peeked into the generated method. Everything looked just fine when I switched to C# view.
To investigate things further I saved IL of generated method to a file, and I did the same with IL of C# method I wrote as an example of what I wanted to accomplish. I then opened them both up in a diff tool to see where the difference was.
As it turns out, the code below (generated.txt) passes the loads the field value (ldfld) while it should load its address (ldflda). Knowing this it was trivial to fix my code.
On a side note, there’s also a Reflection.Emit Language plug-in for Reflector that may be useful to you if you’re working directly with Reflection.Emit classes. It shows you for any method you select the C# code that you’d write to generate that method with Reflection.Emit.
Hi Krzysztof. Nice explanation, I wish I had found this article when I was struggling with IL about a week ago :). In the end I figured out the same process myself, but this would’ve certainly helped.
Since then I’ve found this add-in for Reflector:
It’s extremely useful, it translates the IL code of a given method into the C# code that would be needed to generate the same IL code using System.Reflection.Emit. It doesn’t work perfectly, but it really speeds up development. I hope it helps.