2009-01-23 141 views
24

我一直在考慮使用擴展方法作爲抽象基類的替代品。擴展方法可以提供默認功能,並且可以通過在派生類中放入相同簽名的方法來「覆蓋」。覆蓋擴展方法

任何理由我不應該這樣做?

此外,如果我有兩個具有相同簽名的擴展方法,使用哪一個?有沒有辦法確定優先權?

+2

投票,因爲我認爲這是一個很好的問題,但一個壞主意。 – 2009-01-23 20:39:42

回答

3

我同意邁克爾。基類應該包含所有的基本功能擴展方法顯然應該擴展基本功能。在像Ruby這樣的動態語言中,通常使用擴展方法來提供附加功能而不是使用子類。基本上,擴展方法可以替代使用子類,而不是使用基類來替換。

我看到的唯一的例外是,如果你有多個不同的類層次(比如winform控件),你可以創建一個子類來實現和接口,然後擴展這個接口爲一組不同的控件提供「基礎」功能,而不會像控制或對象那樣擴展所有內容。

編輯:在回答你的第二個問題

我覺得編譯器將捕獲這個給你。

+0

當你說擴展方法要替換子類而不是基類時,我不確定你的意思。由於編譯器比擴展實現更喜歡本地實現,因此不是擴展方法的「基礎」方法嗎? – JoshRivers 2009-01-24 01:07:42

+0

我的意思是通常如果你使用一個Foo類的程序集,並且需要添加一個DoSomething方法,你可以創建一個具有DoSomething方法的SubFoo子類。通過擴展方法,您可以通過擴展方法替換對子類的需求。 – 2009-01-26 18:35:42

12

一般而言,您不應通過擴展方法提供「基本」功能。它們只能用於「擴展」類功能。如果您有權訪問基類代碼,並且您試圖實現的功能在邏輯上是繼承屬性的一部分,那麼您應該將它放在抽象類中。

我的觀點是,僅僅因爲你可以並不意味着你應該。通常情況下,只要堅持使用老式的面向對象編程語言,並使用新的語言功能,當普通的舊面向對象編程不能爲您提供合理的解決方案時。

2

它們在語義上是不同的操作。例如,多態性可能無法像抽象基類一樣工作。

作爲一般規則,使用任何語言工具來設計它。擴展方法不是繼承的替代方法,它是一種使用(通常)已經可見的接口來擴展類功能的技術。

2

你有什麼理由不應該這樣做。首先的是,你不能保證你的擴展將如何被稱爲:

MyExtensions.AMethod(myObj) 

myObj.AMethod() 

第二個是第一隻語法糖。

你的建議違背了語言功能的精神。擴展方法決定爲而不是面向對象。然而,你正試圖實現一種面嚮對象的技術。不要爲此使用擴展方法。

+0

爲什麼執行語法很重要? 不是'特徵的精神'是一個哲學論證嗎?實體主體並不總是「面向對象」的。有時你需要違反'規則'才能獲得一個好的設計? – JoshRivers 2009-01-24 01:11:17

8

這絕對是一個壞主意。擴展方法是靜態綁定的,這意味着,除非您對編譯時類型爲子類型的對象調用覆蓋,否則您仍將繼續調用擴展方法。再見多態性。 This page對擴展方法的風險進行了很好的討論。

4

這裏的所有答案都說「你不能」,這是真的。大多數添加「你不應該」。我想說的是,你應該能夠 - 儘可能小的安慰。

以一個痛苦的現實世界的例子:如果你不幸被使用新的MVC框架,和您的視圖代碼使用一些的HtmlHelper擴展方法所有的地方,你要重寫它的默認行爲...然後怎樣呢?

你是SOL,就是這樣。即使你做了「OOP事物」 - 從HtmlHelper派生出來,改變你的基礎視圖類,用你的DerivedHtmlHelper實例替換'Html'對象實例,並在其中定義一個明確的'Foo'方法 - 即使你做了所有的事情那叫'Html.Foo'將仍然調用原來的擴展方法而不是方法。

這是令人驚訝的!畢竟,只有當對象還沒有方法時纔會應用擴展方法!這裏發生了什麼?

嗯,這是因爲擴展方法是靜態功能。也就是說,當看到'Html.Foo'時,編譯器會查看'Html'類型的靜態。如果它有'Foo'方法,它會照常進行調用。否則,如果有'SomeClass'提供'Foo'擴展方法,它會將表達式轉換爲'SomeClass.Foo(Html)'。

您所期望的是,編譯器會考慮對象的動態類型。也就是說,生成的(僞)代碼將讀爲'Html.HasMethod(「Foo」)? Html.Foo():SomeClass.Foo(Html)'。

這當然會招致在每個擴展方法調用中使用反射的代價。所以,你會希望你可以寫一些像'static void Foo(virtual this HtmlHelper html)'來顯式地請求編譯器插入運行時檢查。稱此爲「虛擬擴展方法」。然而,在他們有限的預算和無限的智慧中,C#語言設計師只採用了更高效,更受限制的替代方案。當我需要重寫HtmlHelper的默認行爲時,這仍然讓我仍然SOL :-(

0

首先,將[new]作爲方法修飾符進行檢查會很好 - 它應該提供相同的perf和method分離,但是會有很多少了麻煩

後,如果您還在琢磨同樣的招數,這裏需要考慮的主要選項 - 不涉及意識形態,剛剛上市apsects你需要知道:-)的

首先,您可能必須限制模板化函數參數的所有用途,以確保確定性函數選擇,因爲此技巧將創建與CLR瓜爾在功能調用邊界上支持確定性綁定優先級。

如果你仍然有抽象基類,你擁有所有的代碼,那麼只需將一個基本方法「轉換」爲擴展,我們根本不會購買任何東西,只會損失一個通用接口其他任何東西都存在,包括vtables的成本。

如果您的目標是將抽象類轉換爲一個具體的,消除所有虛擬方法的全面原因,並使用具體的基類和所有派生的模板(甚至可能轉換爲結構),那麼這個技巧可能會給你一些perf 。你只需要意識到,如果不重構,你將無法回到基於接口的使用。您還需要在簽名中的基類中測試非模板函數中的調用順序,如果您有多個dlls-s,則需要特別小心。嘗試與[新]運營商相同,看看它是否更好。

此外,即使它們具有完全相同的功能,您也必須爲每個派生類編寫單獨的方法實現,因此如果您有很多派生類,那麼您正在查看大量的代碼重複。即使你使用[new],這部分也會打到你,除非你回到虛擬方法並在繼承中引入另一層 - 當然這會取消所有性能提升。

2

你可能要考慮前述多態性,如果你不需要獨特的衍生類。

從這個MSDN article一些一般準則:

  1. 一旦一個方法是一個類中定義, 該方法的名稱將不再執行的類共享任何擴展方法。

  2. 擴展方法由命名空間加載。要允許其他擴展方法的用戶使用 ,您應該避免將 擴展方法放入與擴展類相同的名稱空間中。

如果各種相關組件消耗相同的庫,每個都需要對庫中的一類自定義功能,你可以聲明特定的依賴程序集在組件內的命名空間中的延伸。其他不依賴於該組件的組件不會有該擴展名。

雖然這不是多態,但您可能不想使用多態,因爲它需要所有派生類來實現其覆蓋。

換句話說,如果您具有特定變體邏輯的衍生物,請使用多態性。 如果您只是在特定情況下需要自定義函數,否則可能會造成混淆或負擔,但擴展方法並不是一個壞主意。

此外,請參閱JoshRivers的答案,以顯示如何在單獨的名稱空間內的類中使用命名空間來覆蓋擴展方法。