在.NET世界,IL是最自由,最强大,但也最不容易用好的语言。C#和Visual Basic则为快速、有效、安全地开发.NET程序做了适当的限制。比如C#不能换名重写父类方法,VB.NET 2003不能重新实现父类已经实现的接口等等。有时这些限制看起来有道理,但若遇到了特定的情况还真是麻烦!比如我们遇到这样一个类,它实现了ISerializable做自定义序列化,但却没有将实现用的方法“GetObjectData”标记为虚方法,子类无法重写它。如果我们想继承这个类,同时添加一些新的字段,并希望新的类可以将新字段一起序列化,那怎么办?我们不能重写父类的GetObjectData,因为它不是虚的,我们又不能再次实现ISerializable,因为它已经被父类实现过了,这真是%#@$&^呀!别以为这事情不存在,大名鼎鼎的DataTable类在.NET 1.x中就是这样的。当然,VB2005已经具备了重新实现接口的能力,但是我们假设要用VB.NET 2003,并尝试手工突破这个限制。首先我们用一个简单一些的接口做个例子:IDisposable。下面是最初的代码:
Class Class2
Inherits Class3
Implements IDisposable 'Error
Public Shared Sub Main()
Dim d As IDisposable = New Class2
d.Dispose()
End Sub
End Class
Class Class3
Implements IDisposable
Public Sub Dispose() Implements System.IDisposable.Dispose
Console.WriteLine("Base")
End Sub
End Class
还没有输入完代码,编译错误已经出来了。接口IDisposable已经被父类Class3实现了,不能再次实现。我们首先要欺骗编译器,让我们的代码通过编译,同时还要让Class2成功直接实现IDisposable,这层继承关系是必须要有的。写一个自定义接口:
Interface IMyInterface
Inherits IDisposable
End Interface
接下来,让我们的子类实现IMyInterface:
Class Class2
Inherits Class3
Implements IMyInterface
Public Shared Sub Main()
Dim d As IDisposable = New Class2
d.Dispose()
End Sub
End Class
这已经成功编译,但我们没有达到目的。因为我们还是不允许再次实现Dispose方法,而那才是我们必须的任务。这里我们必须借助一个外部工具——InlineIL,这个工具允许在VB或C#源代码中直接插入IL指令!Mike Stall创建了这个神奇的工具,具体介绍可见这里。这是突破编译器的限制的最简单办法。Class2中添加代码:
Class Class2
Inherits Class3
Implements IMyInterface
Public Shared Sub Main()
Dim d As IDisposable = New Class2
d.Dispose()
End Sub
Public Overridable Shadows Sub Dispose()
#If IL Then
.override [mscorlib]System.IDisposable::Dispose
#End If
Console.WriteLine("Sub")
End Sub
End Class
注意Overridable,这是必须的,孙展波早就在他的Blog中提到,实现接口的方法必须是虚方法,平时这由编译器自主实现,这时就只能靠我们手工添加。Shadows可以在虚方法表中开启一个new slot,确保继承树被断开。插入的IL片断是一个换名重写的元数据,VB和C++/CLI都可以使用这个指令直接换名重写或实现接口,而C#则利用它来处理接口显式实现。先不用inlineIL编译器,直接用vbc.exe编译,我们可以看到运行结果是“Base”,接下来用inlineIL编译,结果则是我们期待的“Sub”,重新实现接口成功。
我这里只是举了一个很小的InlineIL应用的例子,实际上既然我们能够(有限的)嵌入IL到我们的代码中,就等于获得了IL的最高自由和控制力,突破语言的限制自然不在话下。当然InlineIL本身是以一个调试工具的身份出现的,用它来开发并不合适。
浙公网安备 33010602011771号