2011-03-24 61 views
5

假設我有一個接口IFoo,其實現類別爲VideoFoo,AudioFooTextFoo。進一步假設我不能修改任何代碼。假設我會那麼喜歡寫作用不同的基礎上運行時類型的IFoo的功能,如如何幹淨地處理基於多態的不同行爲

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#太)

+0

爲什麼不能重載Fix? – Blorgbeard 2011-03-24 20:42:34

+0

因爲我有一個IFoo類型的實例;如果我只是添加其他重載到類吧,我的代碼將始終調用Fix(IFoo),而不是我想要的特定Fix(...)。 – 2011-03-24 20:47:07

+0

噢,對 - 我明白了 – Blorgbeard 2011-03-24 20:49:58

回答

2

那麼,一種選擇是簡單地重載Fix()方法,以便每個類型實現IFoo有一個過載。但我懷疑你想直接接受接口,而不是實現類型。

實際上你要找的是multiple dispatch通常,C#/ VB在編譯時使用參數的類型來執行重載解析,並根據調用該方法的實例的運行時類型動態分派調用。你想要的是在運行時基於運行時執行重載分辨率參數的類型 - VB.NET或C#直接支持的功能。

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,我們現在可以使用dynamic關鍵字:

在過去,我已經使用System.Type索引代表字典通常解決這類問題C#基本上做同樣的事情(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) { ... } 

注意使用dynamic關鍵字這種方式有一個價格 - 它需要調用站點在運行時處理。它本質上是在運行時激活了C#編譯器的一個版本,處理編譯器捕獲的元數據,執行類型的運行時分析,並吐出C#代碼。幸運的是,DLR可以典型地在第一次使用後緩存這些呼叫站點。

作爲一般規則,我發現這兩種模式都令人困惑,並且在大多數情況下都是過度殺傷性的。如果子類型的數量很小,並且它們全都是提前知道的,那麼簡單的if/else塊就可以更簡單,更清晰。

+0

我不認爲多派遣是這個問題的答案。帕特里克似乎只是基於* one *類型切換,而不是兩個。 – 2011-03-25 13:17:07

+0

事實上,VB.Net從第一個版本開始具有運行時分辨率,只要您使用「Option Strict Off」。大多數人建議將'Option Strict Off'限制爲儘可能少的源文件,因爲沒有直接等同於'dynamic' - 您無法將動態分辨率和隱式轉換限制爲特定變量,它適用於整個源文件。 – MarkJ 2011-03-25 15:00:01

+0

... [請求](https://connect.microsoft.com/VisualStudio/feedback/details/591963/please-give-vb-net-some-equivalent-for-c-dynamic-with-option-strict- )在VB.Net中與'dynamic'完全相同的關鍵字不符合[很多支持](http://blogs.msdn.com/b/lucian/archive/2010/01/28/core2-dynamic-僞類型範圍後期binding.aspx)從微軟:( – MarkJ 2011-03-25 15:06:10

3

你問什麼是Multiple Dispatch,或語言功能,允許在運行時,而不是編譯時方法重載的分辨率。

不幸的是,C#和VB.NET都是單分發語言,這意味着在編譯時選擇方法重載。這意味着IFoo對象的重載將始終爲IFoo選擇,而不管它的實現類型如何。

然而,有些方法可以解決這個問題。一種方法是使用Visitor設計模式來實現雙分派,這將起作用。在C#中,您還可以使用新的dynamic關鍵字來強制運行時環境在運行時解決過載問題。我寫了一個blog entry關於如何使用這種技術執行衝突處理,但它當然也適用於你正在做的事情。

我對VB.NET並不十分熟悉,但我相信如果對象被轉換爲Object,語言會默認展現一些動態行爲。有人請糾正我,如果這是錯誤的。

+0

是的,我想避免動態行爲,所以我的問題對於C#和VB.NET來說是一樣的 – 2011-03-24 20:55:44

+0

足夠公平:-) – 2011-03-24 21:22:17

+0

如果你使用'Option Strict Off',VB.Net可以動態使用。我建議在大部分代碼中使用'Option Strict On',並將'Option Strict Off'限制爲源文件,這些文件特別需要這樣的東西。 – MarkJ 2011-03-25 14:56:56

0

如果您無法更改界面或任何類別,那麼沒有先前編寫的代碼可以利用您要添加的新Fix功能。

我不知道VB.net,但我不禁想知道爲什麼你不只是爲每個當前類(和接口)分類,並將你的新的Fix方法子類。所有想要發送Fix消息的新代碼都應接受IFixFoo而不是IFoo。

如果您想調用您未創建的IFoo對象的Fix,那麼您需要一種可以創建正確的IFixFoo的方法。使用以上,你只有一個地方你必須做If TypeOf ... Is(當你實際上將IFoo轉換爲IFixFoo時