The limitations of Reflection.Emit
Question: Is Reflection.Emit a comprehensive API that lets you build any assembly you want?
Answer: No. There are some things that are valid IL, and yet can't be built with Reflection.Emit. Thanks to Eric Lippert for showing me the following example.
Why do we care about this? Partly it's just curiosity. Partly it's because we're porting the VB compiler from C++ into VB, and we wanted to know if we could use Reflection.Emit for the back-end (answer "no"). Partly because we're exploring the idea of a "REPL loop" and want to know how best to implement it (no solid answers yet).
Attempt 1: Wrong Order
Imports System.Reflection
Imports System.Reflection.Emit
' Goal: to emit this class structure through Reflection.Emit:
Public Class X
Inherits Y
Public Interface I
End Interface
End Class
Public Class Y
Implements X.I
End Class
' Implementation: this is our attempt. We can't actually do it,
' because of limitations in Reflection.Emit
Module Module1
Sub Main()
Dim name = New AssemblyName("test")
Dim ba = Threading.Thread.GetDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave)
Dim bm = ba.DefineDynamicModule(name.Name, name.Name & ".dll")
Dim by = bm.DefineType("Y", TypeAttributes.Public Or TypeAttributes.Class)
Dim bx = bm.DefineType("X", TypeAttributes.Public Or TypeAttributes.Class, by)
Dim bi = bx.DefineNestedType("I", TypeAttributes.NestedPublic Or TypeAttributes.Interface Or TypeAttributes.Abstract)
by.AddInterfaceImplementation(bi)
' The question now is: in what order do we create the types X/Y/I ?
' https://msdn.microsoft.com/en-us/library/system.reflection.emit.typebuilder.createtype.aspx
' * X before I - "create enclosing type before creating the nested type"
' * Y before X - "create parent type before creating the current type"
' * I before Y - "create interface type before creating the current type"
' It's impossible to satisfy all three constraints
Dim ti = bi.CreateType()
Dim ty = by.CreateType()
Dim tx = bx.CreateType()
ba.Save(name.Name & ".dll")
End Sub
End Module
Attempt 2: Callbacks
Update: Thank you for the excellent discussion in the comments. Kvb86 pointed out the use of "ResolveEventHandler" callbacks. With them we can emit the code at the top of this article:
Imports System.Reflection
Imports System.Reflection.Emit
' Goal: to emit this class structure through Reflection.Emit:
Public Class X
Inherits N.Y
Public Interface I
End Interface
End Class
Namespace N
Public Class Y
Implements Global.X.I
End Class
End Namespace
' Implementation: this is our attempt. We can't actually do it,
' because of limitations in Reflection.Emit
Module Module1
Sub Main()
Dim ad = Threading.Thread.GetDomain()
Dim name = New AssemblyName("test")
Dim ba = Threading.Thread.GetDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave)
Dim bm = ba.DefineDynamicModule(name.Name, name.Name & ".dll")
Dim by = bm.DefineType("N.Y", TypeAttributes.Public Or TypeAttributes.Class)
Dim bx = bm.DefineType("X", TypeAttributes.Public Or TypeAttributes.Class, by)
Dim bi = bx.DefineNestedType("I", TypeAttributes.NestedPublic Or TypeAttributes.Interface Or TypeAttributes.Abstract)
by.AddInterfaceImplementation(bi)
' The question now is: in what order do we create the types X/Y/I ?
' https://msdn.microsoft.com/en-us/library/system.reflection.emit.typebuilder.createtype.aspx
' * X before I - "create enclosing type before creating the nested type"
' * Y before X - "create parent type before creating the current type"
' * I before Y - "create interface type before creating the current type"
' It's impossible to satisfy all three constraints
Dim yc = by.DefineDefaultConstructor(MethodAttributes.Public Or MethodAttributes.PrivateScope Or MethodAttributes.HideBySig Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName)
Dim xc = bx.DefineConstructor(MethodAttributes.Public Or MethodAttributes.PrivateScope Or MethodAttributes.HideBySig Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName, CallingConventions.Standard, Type.EmptyTypes)
Dim ilg = xc.GetILGenerator()
ilg.Emit(OpCodes.Ldarg_0)
ilg.Emit(OpCodes.Call, yc)
ilg.Emit(OpCodes.Ret)
Dim eh = Function(o As Object, e As ResolveEventArgs) As Assembly
Select Case e.Name
Case "X" : bx.CreateType()
Case "Y" : by.CreateType()
Case "I" : bi.CreateType()
End Select
Return ba
End Function
AddHandler ad.TypeResolve, eh
Dim ti = bi.CreateType()
Dim ty = by.CreateType()
Dim tx = bx.CreateType()
RemoveHandler ad.TypeResolve, eh
ba.Save(name.Name & ".dll")
End Sub
End Module
Attempt 3: Lack of precision
Update: but as Rolf points out, the existing ResolveEventHandler in Reflection.Emit is inadequate. That's because it doesn't qualify the names. So if you were trying to emit this code, where everything is called "X", then the ResolveEventHandler wouldn't tell you which "X" it has in mind!
Public Class X
Inherits N.X
Public Interface X
End Interface
End Class
Namespace N
Public Class X
Implements Global.X.X
End Class
End Namespace
Conclusion: if Reflection.Emit were to be used seriously, it'd have to be fixed first.