假设我有一个带有实现类VideoFoo,AudioFoo和TextFoo的接口IFoo.进一步假设我无法修改任何代码.假设我想编写一个基于IFoo的运行时类型而不同的函数,例如 Public Class Bar Public Shared Sub Fix(ByVal
Public Class Bar Public Shared Sub Fix(ByVal Foo as IFoo) If TypeOf Foo Is VideoFoo Then DoBar1() If TypeOf Foo Is AudioFoo Then DoBar2() If TypeOf Foo Is TextFoo Then DoBar3() End Sub End Class
我想重构这个以使用重载方法:
Sub DoBar(ByVal foo as VideoFoo) Sub DoBar(ByVal foo as AudioFoo) Sub DoBar(ByVal foo as TextFoo)
但我认为做这样的事情的唯一方法就是写作
Sub DoBar(ByVal foo as IFoo)
然后我必须再做一次“If TypeOf …… Is”.我怎样才能重构这个以利用IFoo实现的多态性而无需手动检查类型?
(在VB.NET中,虽然我的问题也适用于C#)
好吧,一个选项是简单地重载Fix()方法,以便为每个实现IFoo的类型都有一个重载.但我怀疑你想直接接受接口,而不是它的实现类型.您实际需要的是multiple dispatch.通常,C#/ VB使用参数的类型在编译时执行重载解析,并根据方法的实例的运行时类型动态调度调用叫做.您想要的是根据参数的运行时类型在运行时执行重载解析 – 这是VB.NET或C#都不支持的功能.
在过去,我通常使用System.Type索引的委托字典来解决这类问题:
private readonly Dictionary<Type,Action<IFoo>> _dispatchDictionary; static Bar() { _dispatchDictionary.Add( typeof(TextFoo), DoBarTextFoo ); _dispatchDictionary.Add( typeof(AudioFoo), DoBarAudioFoo ); _dispatchDictionary.Add( typeof(VideoFoo), DoBarVideoFoo ); } public void Fix( IFoo foo ) { Action<IFoo> barAction; if( _dispatchDictionary.TryGetValue( foo.GetType(), out barAction ) ) { barAction( foo ); } throw new NotSupportedException("No Bar exists for type" + foo.GetType()); } private void DoBarTextFoo( IFoo foo ) { TextFoo textFoo = (TextFoo)foo; ... } private void DoBarAudioFoo( IFoo foo ) { AudioFoo textFoo = (AudioFoo)foo; ... } private void DoBarVideoFoo( IFoo foo ) { VideoFoo textFoo = (VideoFoo)foo; ... }
但是,从C#4开始,我们现在可以在C#中使用dynamic关键字做同样的事情(VB.NET还没有这个功能):
public void Fix( IFoo foo ) { dynamic dynFoo = foo; dynamic thisBar = this; thisBar.DoBar( dynFoo ); // performs runtime resolution, may throw } private void Dobar( TextFoo foo ) { ... /* no casts needed here */ } private void Dobar( AudioFoo foo ) { ... } private void Dobar( VideoFoo foo ) { ... }
请注意,使用动态关键字这种方式需要付出代价 – 它要求在运行时处理调用网站.它本质上是在运行时旋转一个版本的C#编译器,处理编译器捕获的元数据,执行类型的运行时分析,并吐出C#代码.幸运的是,DLR可以在首次使用后有效地缓存这些呼叫站点.
作为一般规则,我发现这两种模式都令人困惑,并且在大多数情况下都是过度杀伤.如果子类型的数量很少并且它们都是提前知道的,那么简单的if / else块就可以更加简单和清晰.