国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > 綜合 > 正文

漫談C#編程中的多態與new關鍵字

2024-07-21 02:29:32
字體:
來源:轉載
供稿:網友
注冊會員,創建你的web開發資料庫,  1. 你通常怎樣用多態?

  假設我有一個類,里面有一個 printstatus 方法,用于打印實例的當前狀態,我希望該類的派生類都帶有一個 printstatus 方法,并且這些方法都用于打印其實例的當前狀態。那么我會這樣表達我的愿望:

// code #01

class base
{
 public virtual void printstatus()
 {
  console.writeline("public virtual void printstatus() in base");
 }
}

  于是我可以寫一個這樣的方法:

// code #02

public void displaystatusof(base[] bs)
{
 foreach (base b in bs)
 {
  b.printstatus();
 }
}

  bs 中可能包含著不同的 base 的派生類,但我們卻可以忽略這些“個性”而使用一種統一的方式來處理某事。在 .net 2.0 中,xmlreader 的 create 有這樣一個版本:

public static xmlreader create(stream input);

  你可以向 create 傳遞任何可用的“流”,例如來自文件的“流”(filestream)、來自內存的“流”(memorystream)或來自網絡的“流”(networkstream)等。雖然每一中“流”的工作細節都不同,但我們卻使用一種統一的方式來處理這些“流”。

  2. 假如有人不遵守承諾...

  displaystatusof 隱含著這樣一個假設:bs 中如果存在派生類的實例,那么該派生類應該重寫 printstatus,當然必須加上 override 關鍵字:

// code #03

class derived1 : base
{
 public override void printstatus()
 {
  console.writeline("public override void printstatus() in derived1");
 }
}

  你可以把這看作一種承諾、約定,直到有人沉不住氣...

// code #04

class derived2 : base
{
 public new void printstatus()
 {
  console.writeline("public new void printstatus() in derived2");
 }
}

  假設我們有這樣一個數組: // code #05

base[] bs = new base[]
{
 new base(),
 new derived1(),
 new derived2()
};

  把它傳遞給 displaystatusof,則輸出是:

// output #01

// public virtual void printstatus() in base
// public override void printstatus() in derived1
// public virtual void printstatus() in base

  從輸出結果中很容易看出 derived2 并沒有按照我們期望的去做。但你無需驚訝,這是由于 derived2 的設計者沒有“遵守約定”的緣故。

  3. new:封印咒術

  new 似乎給人一種這樣的感覺,它的使用者喜歡打破別人的約定,然而,如果使用恰當,new 可以彌補基類設計者的“短見”。在 creating a data bound listview control 中,rockford lhotka 就示范了如何封印原來的 listview.columns,并使自行添加的返回 datacolumnheadercollection 的 columns 取而代之。

  從 output #01 中我們可以看到,new 只是把 base.printstatus 封印起來而不是消滅掉,你可以解除封印然后進行訪問。對于 derived2 的使用者,解封的方法是把 derived2 的實例轉換成 base 類型:

// code #06

base d2 = new derived2();
d2.printstatus();

// output #02

// public virtual void printstatus() in base
而在 derived2 內部,你可以透過 base 來訪問:

// code #07

base.printstatus();

  這種方法是針對實例成員的,如果被封印的成員是靜態成員的話,就要透過類名來訪問了。

  4. 假如 base.printstatus 是某個接口的隱式實現...

  假如 base 實現了一個 iface 接口:

// code #08

interface iface
{
 void printstatus();
}

class base : iface
{
 public virtual void printstatus()
 {
  console.writeline("public virtual void printstatus() in base");
 }
}

  我們只需要讓 derived2 重新實現 iface:

// code #09

class derived2 : base, iface
{
 public new void printstatus()
 {
  console.writeline("public new void printstatus() in derived2");
 }
}

  derived1 保持不變。則把:

// code #10

iface[] fs = new iface[]
{
 new base(),
 new derived1(),
 new derived2(),
}

  傳遞給:

// code #11

public void displaystatusof(iface[] fs)
{
 foreach (iface f in fs)
 {
  f.printstatus();
 }
}

  輸出結果是:

// output #03

// public virtual void printstatus() in base
// public override void printstatus() in derived1
// public new void printstatus() in derived2


  從輸出結果中,我們可以看到,雖然 derived2.printstatus 應用了 new,但卻依然參與動態綁定,這是由于 new 只能割斷 derived2.printstatus 和 base.printstatus 的聯系,而不能割斷它與 iface.printstatus 的聯系。我在 derived2 的定義中重新指定實現 iface,這將使得編譯器認為 derived2.printstatus 是 iface.printstatus 的隱式實現,于是,在動態綁定時 derived2.printstatus 就被包括進來了。

  5. 誰的問題?

  我必須指出,如果 base(code #01)和 derived2(code #04)同時存在的話,它們倆其中一個存在著設計上的問題。為什么這樣說呢?base 的設計者在 printstatus 上應用 virtual 說明了他希望派生類能透過重寫這一方法來參與動態綁定,即多態性;而 derived2 的設計者在 printstatus 上應用 new 則說明了他希望割斷 derived2.printstatus 和 base.printstatus 之間的聯系,這將使得 derived2.printstatus 無法參與到 base 的設計者所期望的動態綁定中。如果在 base.printstatus 上應用 virtual(即對多態性的期望)是合理的話,那么 derived2.printstatus 應該換用另外一個名字了;如果在 derived2.printstatus 上應用 new(即否決參與動態綁定)是合理的,那么 base.printstatus 應該考慮是否去掉 virtual 了,否則就會出現一些奇怪的行為,例如 output #01 的第三行輸出。

  假如繼承體系中多態性行為的期望是合理的話,那么更實際的做法應該是把 base 定義成這樣:

// code #12

abstract class base
{
 public abstract void printstatus();
}

  而原來 base 中的實現應該下移到一個派生類中: // code #13

class derived3 : base
{
 public override void printstatus()
 {
  console.writeline("public override void printstatus() in derived3 [originally implemented in base]");
 }
}

  這樣,derived2.printstatus 將使得編譯無法完成,從而迫使其設計者要么更改方法的名字,要么換用 override 修飾。這種強制使得 derived2 的設計者不得不重新考慮其設計的合理性。

  假如繼承體系中多態性行為的期望不總是合理呢?例如 stream 有這樣一個方法:

public abstract long seek(long offset, seekorigin origin);

  現在假設我有一個方法在處理輸入流時需要用到 stream.seek:

// code #14

public void resume(stream input, long offset)
{
 //
 input.seek(offset, seekorigin.begin);
 //
}

  當我們向 resume 傳遞一個 networkstream 的實例,resume 將會拋出一個 notsupportedexception,因為 networkstream 不支持 seek。那么這是否說明 stream 的設計有問題呢?

  設想 resume 是一個下載工具進行斷點續傳的方法,然而,并不是所有的服務器都支持斷點續傳的,于是,你需要首先判斷輸入流是否支持 seek 操作,再決定如何處理輸入流:

// code #15

public void resume(stream input, long offset)
{
 if (input.canseek)
 {
  //
  input.seek(offset, seekorigin.begin);
  //
 }
 else
 {
  //
 }
}

  如果 canseek 為 false,那就只好從頭來過了。

  實際上,我們并不能保證任何 stream 的派生類都能夠支持某個(些)操作,我們甚至不能保證來自同一個派生類的所有實例都支持某個(些)操作。你可以設想有這樣一個 prioritystream,它能夠根據當前登錄賬號的權限來決定是否提供寫操作,這使得擁有足夠權限的人才能修改數據。或許 stream 的設計者已經預料到這類情況的發生,所以 canread、canseek 和 canwrite 就被加入到 stream 里了。

  值得注意的是,code #07 的 derived2 可能是一個很糟糕的設計,也可能是一個很實用的設計。在本文,它是一個很糟糕的設計,如果你足夠細心,你會察覺到 derived2 的設計者希望 derived2.printstatus 繞過 base.printstatus 而直接和 iface.printstauts 進行關聯,表面上這沒什么不妥,但實質上 base.printstatus 和 iface.printstauts 在約定上是同質的,這意味著如果與 iface.printstauts 進行關聯就等于承認自己和 base.printstatus 是同質的,這樣的話,為什么不直接在 derived2 里重寫 printstatus 呢?在《基類與接口混合繼承的聲明問題》中,我示范了一個實用的設計,用 new 和接口重新實現(interface reimplementation)來糾正非預期的多態行為。

  6. 最后...

  當我的朋友拿著問題來找我時,我通常都不會直接給出我的答案,而是盡我的能力向他提供足夠多的可用信息,以便他能夠根據他所面臨的實際情況作出處理,畢竟,我不會比他更了解他的問題,而他也應該形成他自己的關于他的問題的思考。我希望浪子能用自己的答案回答他所提出的問題,因為只有這樣,那些知識才真正屬于他,并且我也相信本文已經提供了足夠多的可用信息。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 库尔勒市| 仪征市| 阿拉善左旗| 江阴市| 遵化市| 思南县| 芦溪县| 海宁市| 临邑县| 永修县| 河北省| 历史| 遵义市| 榆社县| 宿州市| 朝阳区| 青州市| 巴中市| 天柱县| 凤山县| 固原市| 治多县| 德保县| 包头市| 林口县| 阿勒泰市| 弥勒县| 泸西县| 巴楚县| 肥东县| 子长县| 中牟县| 汽车| 济南市| 江安县| 彭山县| 修文县| 海宁市| 博爱县| 广宗县| 东兴市|