In my previous blog, I raised a question: how to use. NET Reflection. Emit to generate three types equivalent to the following VB code:
Class A Implements B.IEnd ClassClass B Inherits A Interface I End InterfaceEnd Class |
The difficulty of this problem lies in the three types of circular dependency: A implements interface B. therefore, A depends on I; B is A subclass of A, so B depends on A; interface I is A nested type of B, so I depends on B. The biggest problem when using Reflection. Emit is that the CreateType method is called in whatever order, there will always be a dependency error. The following is a simple attempt but cannot be successful:
Module Program Sub Main() Dim name = New AssemblyName("test") Dim dasm = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave) Dim dmod = dasm.DefineDynamicModule(name.Name, name.Name + ".dll") Dim tA = dmod.DefineType("A", TypeAttributes.Public Or TypeAttributes.Class) Dim tB = dmod.DefineType("B", TypeAttributes.Public Or TypeAttributes.Class, tA) Dim tI = tB.DefineNestedType("I", TypeAttributes.NestedPublic Or TypeAttributes.Interface Or TypeAttributes.Abstract) tA.AddInterfaceImplementation(tI) tA.CreateType() tB.CreateType() tI.CreateType() dasm.Save(name.Name + ".dll") End SubEnd Module |
We found that the main problems here are:
- Reflection. Emit cannot generate B-Type constructor in this case
- A type parsing error occurs when you create a type.
Therefore, we solve these two problems respectively. First, since the constructor of B cannot be automatically generated, we can create it manually. This can be done using DefineConstructor of TypeBuilder. As the base class of type A is an Object, use definedefaconconstructor directly. Note that constructor has many required attributes, including PrivateScope, HideBySig, SpecialName, and RTSpecialName. They must be added (otherwise, many errors may occur when using it ). The next step is the issue that cannot be found due to loop reference. We can handle the TypeResolve event of AppDomain to manually handle the issue of loop reference. The following is a complete solution:
Imports System.ReflectionImports System.Reflection.EmitModule Program Sub Main() Dim name = New AssemblyName("test") Dim dasm = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave) Dim dmod = dasm.DefineDynamicModule(name.Name, name.Name + ".dll") Dim tA = dmod.DefineType("A", TypeAttributes.Public Or TypeAttributes.Class) Dim tB = dmod.DefineType("B", TypeAttributes.Public Or TypeAttributes.Class, tA) Dim tI = tB.DefineNestedType("I", TypeAttributes.NestedPublic Or TypeAttributes.Interface Or TypeAttributes.Abstract) tA.AddInterfaceImplementation(tI) Const ctorAttr As MethodAttributes = MethodAttributes.Public Or MethodAttributes.PrivateScope Or MethodAttributes.HideBySig Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName Dim ctorA = tA.DefineDefaultConstructor(ctorAttr) Dim ctorB = tB.DefineConstructor(ctorAttr, CallingConventions.Standard, {}) With ctorB.GetILGenerator .Emit(OpCodes.Ldarg_0) .Emit(OpCodes.Call, ctorA) .Emit(OpCodes.Ret) End With AddHandler AppDomain.CurrentDomain.TypeResolve, Function(sender As Object, e As ResolveEventArgs) Select Case e.Name Case "A" : Return tA.CreateType.Assembly Case "B" : Return tB.CreateType.Assembly Case "I" : Return tI.CreateType.Assembly End Select Return Nothing End Function tA.CreateType() tB.CreateType() tI.CreateType() dasm.Save(name.Name + ".dll") End SubEnd Module |
The generated assembly is correct, whether through Reflector decompilation or using C #/VB for reference. It is the same as that compiled by the true VB compiler.
The reason for this problem is that the PM Lucian Wischick blog of VB Team raised this question. Microsoft's VB Team is porting the VB compiler written in C ++ into VB code. They are studying whether the backend hosting compiler can be implemented using Reflection. Emit. VB supports the code of this type of loop dependency, so they have a headache. Fortunately, a reply solved this problem. If you want to use Reflection. Emit as the compiler backend, you can refer to this method.