zoukankan      html  css  js  c++  java
  • 趣味问题:你能用Reflection.Emit生成这段代码吗?(答案)

    在上一篇博客中我提出了一个问题:如何用.NET的Reflection.Emit生成等价于下面VB代码的三个类型:

    Class A
        Implements B.I
    End Class
    
    Class B
        Inherits A
        Interface I
    
        End Interface
    End Class

    这个问题的难点在于三个类型有循环依赖关系:A实现了接口B.I,因此A依赖于I;B是A的子类,因此B依赖于A;接口I是B的嵌套类型,因此I依赖于B。使用Reflection.Emit的时候最大的问题就是不管以何种顺序调用CreateType方法总会出现依赖项有问题的错误。下面是一个简单的尝试但是无法成功:

    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 Sub
    End Module

    我们发现,这里存在的问题主要是:

    1. Reflection.Emit无法在这种情况下生成B类型的构造函数
    2. 创建类型的时候会发生类型解析错误

    因此我们分别解决这两个问题。首先既然无法自动生成B的构造函数那么我们手工来创造它。这可以用TypeBuilder的DefineConstructor来做到。至于A类型的基类是Object所以直接用DefineDefaultConstructor即可。注意构造函数有很多必须的属性,包括PrivateScope、HideBySig、SpecialName、RTSpecialName等,必须都加上(否则使用它的时候会出很多错)。接下来就是循环引用导致找不到的问题,我们可以处理AppDomain的TypeResolve事件手动处理循环引用的问题。下面就是完整的方案:

    Imports System.Reflection
    Imports System.Reflection.Emit
    
    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)
    
            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 Sub
    End Module

    这样生成的程序集无论是通过Reflector反编译还是使用C#/VB来引用都是正确的,和真正的VB编译器编译出来是一样的。

    这个问题的缘由是VB Team的PM Lucian Wischick的博客上提出了这个问题。微软的VB Team正在将C++编写的VB编译器移植成VB代码。他们正在研究托管编译器的后端可不可以使用Reflection.Emit来实现。由于VB支持这种循环类型依赖的代码,所以他们很头疼遇到的问题。幸好一篇回复解决了这个问题。希望采用Reflection.Emit作为编译器后端的同学可以参考一下这种方法。

  • 相关阅读:
    hdu 1042 N!
    hdu 1002 A + B Problem II
    c++大数模板
    hdu 1004 Let the Balloon Rise
    hdu 4027 Can you answer these queries?
    poj 2823 Sliding Window
    hdu 3074 Multiply game
    hdu 1394 Minimum Inversion Number
    hdu 5199 Gunner
    九度oj 1521 二叉树的镜像
  • 原文地址:https://www.cnblogs.com/Ninputer/p/1614249.html
Copyright © 2011-2022 走看看