# 原則33:只有基類更新處理才使用 new 修飾符
**By D.S.Qiu**
**尊重他人的勞動,支持原創,轉載請注明出處:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
你可以使用 new 修飾符重定義從基類繼承的非虛成員。僅僅是因為你可以做某些事情卻不意味著你應該做。重定義非虛方法會有歧義的行為。大多數開發者看到下面兩塊代碼會立即認為他們執行的是同一件事情,如果它們繼承于同一個對象:
當涉及到 new 修飾符,就不是這樣的情況:
```
public class MyClass
{
public void MagicMethod()
{
// details elided.
}
}
public class MyOtherClass : MyClass
{
// Redefine MagicMethod for this class.
public new void MagicMethod()
{
// details elided
}
}
```
這類實踐會困擾很多開發者。如果你對同一個對象調用同一個函數,你會期望相同的代碼被執行。實際上改變引用,即類名標簽,你調用函數改變了行為給人錯誤的感覺。這是不一致的。MyOtherClass 對象的行為取決于你怎么引用它。實際 new 修飾符的修飾不會使非虛方法變成虛方法。而是,它在不同類名作用域增加了不同的方法。
非虛方法是靜態綁定的。任何地方的引用 MyClass.MagicMethod() 的代碼調用都執行這個函數。運行時不會去查找子類定義的不同版本。虛函數,從另一方面講,是動態綁定的。運行時會根據對象的運行時類型調用真正的函數。
推薦避免使用 new 修飾符去重定義非虛函數并不是說建議你把基類的函數都定義為虛的。類庫設計者定義虛函數是有一定的講究。你定義虛函數說明任何子類期望改變虛函數的實現。虛函數集定義了子類期望改變的所有行為。“默認為虛”設計是說子類可以改變基類的所有行為。這確實是說你不會考慮子類修改的行為的所有后果。相反,你應該花時間去思考哪些方法和屬性聲明為多態。使那些并且只有那些是虛的。不要認為這可以限制你的類型的使用。而是,要把它作為提供自定義你的類型的行為的入口。
有一個地方,只有一地方,你要使用 new 修飾符。你在基類已經使用的方法簽名使用 new 修飾符就構成新的版本。你獲得的函數代碼取決于你類的函數名字。可能有其他程序集使用你的方法。你在你的類庫創建下面類,使用 BaseWidget 定義在其他類庫中:
```
public class MyWidget : BaseWidget
{
public void NormalizeValues()
{
// details elided.
}
}
```
你完成你的組件,并且客戶正使用它。而后你發現 BaseWidget 公司發布了新版本。渴望得到新的特性,你理解購買她并嘗試構建 MyWidget 類。就會失敗因為 BaseWidget 自己添加了 NormalizeValues 方法:
```
public class BaseWidget
{
public void Normalizevalues()
{
// details elided.
}
}
```
這就會有問題。你的基類偷偷在你的類名作用域下加了一個方法。有兩種方式修復它。你可以改變你的類型 NormalizeValues 方法的名字。注意直觀上認為 BaseWidget.NormalizeValues() 和 MyWidget.NormalizeValues() 在語意上的相同操作。如果不是,你不應該調用基類的實現。
```
public class MyWidget : BaseWidget
{
public void NormalizeAllValues()
{
// details elided.
// Call the base class only if (by luck)
// the new method does the same operation.
base.NormalizeValues();
}
}
```
或者,你可以使用 new 修飾符:
```
public class MyWidget : BaseWidget
{
public void new NormalizeValues()
{
// details elided.
// Call the base class only if (by luck)
// the new method does the same operation.
base.NormalizeValues();
}
}
```
如果你可以得到 MyWidget 類的所有客戶端代碼,你可以改變方法名字因為這很容易長期運行。但如果你是向世界發布 MyWidget 類,就會強制你的所有使用者做無數的更改。這就是 new 修飾符派上用場的地方。你的客戶可以不更改繼續使用 NormalizeValues() 方法。沒有人會調用 BaseWidget.NormalizeValues() 因為它在之前不存在。 new 修飾符可以處理這樣的情況,即升級基類使得有一個成員和之前聲明的類有沖突的情況。
當然,隨著時間的推移,你的使用者可能需要使用 BaseWidget.NormalizeValues() 方法。那樣你還是回到原來的問題:兩個方法看起來相同但是表現卻不同。考慮所有 new 修飾符的長期后果。有時,短期更改你的方法是不方便卻更好。
new 修飾符必須謹慎使用。如果你不分青紅皂白,你就在創建歧義的方法。它只用于這個在升級基類引起和你的類沖突的特例。即使是那樣的情況,在使用它之前也要仔細考慮。最重要的是,不要在其他情況使用它。
小結:
盡量不用 new 修飾符,減少歧義方法。
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/2087773](/blog/2087773)
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)
- 第一章 C# 語言習慣
- 原則1:使用 屬性(Poperty)代替可直接訪問的數據成員(Data Member)
- 原則2:偏愛 readonly 而不是 const
- 原則3:選擇 is 或 as 而不是強制類型轉換
- 原則4:使用條件特性(conditional attribute)代替 #if
- 原則5:總是提供 ToString()
- 原則6:理解幾個不同相等概念的關系
- 原則7:明白 GetHashCode() 的陷阱
- 原則8:優先考慮查詢語法(query syntax)而不是循環結構
- 原則9:在你的 API 中避免轉換操作
- 原則10:使用默認參數減少函數的重載
- 原則11:理解小函數的魅力
- 第二章 .NET 資源管理
- 原則12:選擇變量初始化語法(initializer)而不是賦值語句
- 原則13:使用恰當的方式對靜態成員進行初始化
- 原則14:減少重復的初始化邏輯
- 原則15:使用 using 和 try/finally 清理資源
- 原則16:避免創建不需要的對象
- 原則17:實現標準的 Dispose 模式
- 原則17:實現標準的 Dispose 模式
- 原則18:值類型和引用類型的區別
- 原則19:確保0是值類型的一個有效狀態
- 原則20:更傾向于使用不可變原子值類型
- 第三章 用 C# 表達設計
- 原則21:限制你的類型的可見性
- 原則22:選擇定義并實現接口,而不是基類
- 原則23:理解接口方法和虛函數的區別
- 原則24:使用委托來表達回調
- 原則25:實現通知的事件模式
- 原則26:避免返回類的內部對象的引用
- 原則27:總是使你的類型可序列化
- 原則28:創建大粒度的網絡服務 APIs
- 原則29:讓接口支持協變和逆變
- 第四章 和框架一起工作
- 原則30:選擇重載而不是事件處理器
- 原則31:用 IComparable<T> 和 IComparer<T> 實現排序關系
- 原則32:避免 ICloneable
- 原則33:只有基類更新處理才使用 new 修飾符
- 原則34:避免定義在基類的方法的重寫
- 原則35:理解 PLINQ 并行算法的實現
- 原則36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原則37:構造并行算法的異常考量
- 第五章 雜項討論
- 原則38:理解動態(Dynamic)的利與弊
- 原則39:使用動態對泛型類型參數的運行時類型的利用
- 原則40:使用動態接收匿名類型參數