Effective C# Item 29: Use the new Modifier Only When Base Class Updates Mandate it
當我們需要重定義一個基類中非虛的成員時,我們可以使用new修飾符,但這並不意味著我們應該這樣做。這種重定義會引起行為上的歧義。大部分程式員看到下面的兩端代碼時都會認為如果這兩個類是繼承關係的話,它們的行為應該時相同的:
object c = MakeObject();
MyClass c1 = c as MyClass;
c1.MagicMethod();
MyOtherClass c2 = c as MyOtherClass;
c2.MagicMethod();
但是如果通過new進行重定義,那麼結果可能並不是這樣:
public class MyClass
{
public void MagicMethod()
{
}
}
public class MyOtherClass : MyClass
{
public new void MagicMethod()
{
}
}
像這種情況經常會讓很多開發人員頭疼。如果調用同一個對象的同一個方法,我們期望執行結果是相同的。但是事實上雖然名稱相同,但它們調用的函數卻是完全不同的,這非常糟糕。它破壞了唯一性。MyOtherClass對象的行為並不是我們期望的。使用new修飾符可以讓我們在類中添加一個完全不同的方法,這不同於重寫基類中的虛方法。
非虛的方法都是靜態確定的。不論在任何位置的代碼,這些方法都調用同樣的函數。在運行時不會在其衍生類別中尋找此方法的不同版本。而虛方法是動態確定的。運行時將通過不同的類型來調用該方法不同的版本。
我們應當避免使用new來重定義非虛方法。不然我們就需要將基類中的所有方法都聲明為虛的。設計者通過定義這些虛方法為類添加了一種行為約定。它的衍生類別都可以通過這個虛方法來達到自己不同的實現需要。每一條虛方法都定義了其衍生類別可能需要改變的行為。“預設虛方法”的設計認為基類中所有的方法都為虛,衍生類別可以修改基類中所有的行為。這往往意味著你並沒有認真思考衍生類別之間行為的分支關係。我們應當多花些時間思考衍生類別中哪些方法和屬性時需要多態性的,僅僅把這些有需要的方法和屬性設為虛的。我們不要將這想象為對使用者的限制。而是將它看作指導使用者自訂行為的入口。
僅有一種情況下我們應該使用new修飾符:我們更新了一個新版本的基類,而這個基類中有包含和我們原有類型相同名稱的方法。我們在下例中建立一個MyWidget類,使用其他類庫定義的BaseWidget類做為基類:
public class MyWidget : BaseWidget
{
public void DoWidgetThings()
{
}
}
我們完成了這個類並交付使用者使用。然後我們發現BaseWidget公司發布了一個新版本的BaseWidget類。對於新特性的熱衷讓我們立即嘗試使用新的基類來構建MyWidget類。但是卻失敗了,因為我們發現BaseWidget中添加了他們自己的DoWidgetThings方法。
public class BaseWidget
{
public void DoWidgetThings()
{
}
}
問題出現了。我們的新基類偷偷的提供了一個和我們類型相同名稱的方法。我們有兩種方法可以解決這個問題。一是更改我們類中DoWidgetThings方法的名稱:
public class MyWidget : BaseWidget
{
public void DoMyWidgetThings()
{
}
}
二是使用new修飾符:
public class MyWidget : BaseWidget
{
public new void DoWidgetThings()
{
}
}
如果能獲得所有所用MyWidget類的客戶程式碼,那我們應當選擇修改名稱的方法。但是如果我們已經發布了MyWidget類,這種修改就會強迫我們所有的使用者修改其代碼。如果發布範圍較大的話,這種修改付出的代價太高。new操作符可以協助我們處理這個問題。所有的用戶端都可以不做任何更改的繼續使用DoWidgetThings()方法。沒有使用者會使用BaseWidget.DoWidgetThings()因為他們不知道基類中有這樣一個方法。new操作符在升級基類版本時隱藏了命名衝突的成員。
當然,在經過一段時間後,有的使用者可能會開始需要使用BaseWidget.DoWidgetThings()方法。這樣我們又回到了原來的問題:兩個方法的名稱相同但是行為不同。考慮到使用new修飾符會產生長期的語義分歧,因此雖然更改方法名稱會比較麻煩,但是相比之下這種處理方式往往更加得當。
在使用new修飾符時必須小心。不恰當的使用會造成方法調用的歧義。只有當升級基類發生命名衝突時才可以使用它來進行特殊處理。而且即便如此也應當謹慎使用。不要在其他情況下使用new修飾符。
譯自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目錄