After being in Exchange for almost a year, I tend to forget the how to run Reflection.Emit that I was once so familiared with. Occasionally, I would receive emails from this blog asking how to emit this, how to emit that. Before, the solutions came directly from my memory, but now I need some help. The way for me to figure it out is to compile the thing I want to emit into a binary file and run it under my little tool AssemblyRoundTrip.exe with verbose output.
I wrote the tool for testing purpose. I ran every assembly I can get under that tool and it will figure out for me whether we can or cannot emit the assembly.
Now, I found it really helpful for myself to figure out things should be done. I am posting the tool here and hopefully it will help you to work out some generic mystery.
Here is an example, if I want to define Test<T>:List<T> type, I wrote it in C# and compile it. Then I ran
AssemblyRoundTrip.exe repro.dll /verbose
and here is the output:
new Asm Name is repro_EmitSecurity Attribute number :0setting ca:[System.Runtime.CompilerServices.CompilationRelaxationsAttribute((Int32)8)]setting ca:[System.Runtime.CompilerServices.RuntimeCompatibilityAttribute(WrapNonExceptionThrows = True)]new module name is repro_Emit.dllTotal Types:1m_modBuilder.DefineType M`1 <---- Define the typeAdding M`1[T] to the create listDefine Generic Parameters on M`1 <---- Define the type parameterAdding Generic Parameter M`1[T]::T on Type M`1 to the create type listFound a match for :M`1::T <--- this step is a preparation for the type below. We are binding List`1[T] (the type below) to TSet Parent for M`1 System.Collections.Generic.List`1[T] <--- set the paraentFound a match for :M`1::TSet Interface for M`1 System.Collections.Generic.IList`1[T]Found a match for :M`1::TSet Interface for M`1 System.Collections.Generic.ICollection`1[T]Found a match for :M`1::TSet Interface for M`1 System.Collections.Generic.IEnumerable`1[T]Set Interface for M`1 System.Collections.IEnumerableSet Interface for M`1 System.Collections.IListSet Interface for M`1 System.Collections.ICollection+++ Create Method : M`1[T]::.ctorSecurity Attribute for type :M`1[T] is 0Adding M`1[T] to bake type list+++ Created Type : M`1
So I translate the verbose output into the following code:
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Reflection.Emit;
public class Test
{
public static void Main()
string typename = "ListProxy";
AssemblyName asmname = new AssemblyName("mytest");
AssemblyBuilder asmbuild = AppDomain.CurrentDomain.DefineDynamicAssembly(asmname, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder moduleBuilder = asmbuild.DefineDynamicModule("mytest", "mytest.dll");
TypeAttributes typeAttributes = TypeAttributes.Public | TypeAttributes.Class;
TypeBuilder typeBuilder = moduleBuilder.DefineType(typename, typeAttributes);
GenericTypeParameterBuilder[] tpbs = typeBuilder.DefineGenericParameters("T"); // defines a generic type definition
typeBuilder.MakeGenericType(typeof(List<>).GetGenericArguments()[0]);
typeBuilder.SetParent(typeof(List<>).MakeGenericType(tpbs[0])); // bind the correct base type
typeBuilder.CreateType();
asmbuild.Save("mytest");
}
I attached the tool as it is and I am unlikely to support this tool. I could send the source code upon request but the source code is a little confusing since it is a test tool so it tend to has many extra stuff that we are interested to test but not related to the main purpose.
If you set EXT_ROOT to your runtime install path, the tool will run peverify for you at the end to verify the generated assembly.
You will find there is another AssemblyPrinter.exe, it is a reflection verstion of ildasm outputing text file. The only difference is that AssemblyPrinter.exe prints out members in deterministic order. So if you want to compare the two assemblies, AssemblyPrinter.exe is a good tool for that. ildasm.exe output on two assemblies compiled from the same source tend to be difference because the runtime doesn't grarantee the ordering of members to be the same. (This should remind you not to use GetMethods[1] or GetCustomAttributes[2] since they tend to change!)