2010-02-04 51 views
2

請看下面的代碼:擴展方法VS父類的方法行爲

class A 
{ 
    public string DoSomething(string str) 
    { 
     return "A.DoSomething: " + str; 
    } 
} 

class B : A 
{ 
} 

static class BExtensions 
{ 
    public static string DoSomething(this B b, string str) 
    { 
     return "BExtensions.DoSomething: " + str; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var a = new A(); 
     var b = new B(); 
     Console.WriteLine(a.DoSomething("test")); 
     Console.WriteLine(b.DoSomething("test")); 

     Console.ReadKey(); 
    } 
} 

代碼的輸出是:

A.DoSomething:測試

A.DoSomething:測試

當它編譯它不給予警告。

我的問題是:爲什麼在代碼編譯時沒有警告,調用DoSomething方法時會發生什麼?

+0

[這涉及到這個問題](http://stackoverflow.com/questions/ 1745166/override-or-shadow-a-method-with-extension-method)和[also this](http://stackoverflow.com/questions/899539/is-there-any-way-in-c-to -override-a-class-method-with-an-extension-method) – 2010-02-04 11:40:31

回答

6

當該方法被調用時會發生什麼很簡單:只是實例方法調用。由於C#是早期綁定的,因此所有的方法都會在編譯時解析。另外,實例方法比擴展方法更受歡迎,所以這就是爲什麼你的擴展方法永遠不會被調用的原因。

this

您可以使用擴展方法來擴展一個類或接口,但不覆蓋它們。與接口或類方法具有相同名稱和簽名的擴展方法永遠不會被調用。在編譯時,擴展方法總是比類型本身定義的實例方法具有更低的優先級。

換句話說,如果一個類型有一個名爲Process(int i)的方法,並且你有一個具有相同簽名的擴展方法,那麼編譯器將總是綁定到實例方法。當編譯器遇到方法調用時,它首先在類型的實例方法中查找匹配項。如果找不到匹配項,它將搜索爲該類型定義的任何擴展方法,並綁定到它找到的第一個擴展方法。

4

基本上,編譯器總是會使用實例方法(如果可用的話),只有在其他一切失敗時才使用擴展方法。從部分中的C#3.0規範的7.5.5.2:

在的形式

  • EXPR 一項所述的方法調用(第7.5.5.1節)。標識符()
  • expr。標識符(參數)
  • expr。標識< typeargs>()
  • expr。標識符< typeargs>(參數)

如果 調用的正常處理沒有發現適用 方法,試圖處理 構建體作爲擴展方法 調用。

這是我找到擴展方法的方法之一...擴展方法DoSomething永遠被稱爲擴展方法(雖然它的調用與普通的靜態方法的語法)......然而,編譯器甚至不給予警告:(

+0

這讓我很傷心,也沒有警告): – bniwredyc 2010-02-04 12:06:03

+6

警告將是不良行爲。考慮這種情況:AlphaCorp實現了Alpha類型。 BetaCorp實現庫BetaExt,其中一種方法將擴展方法Frob添加到Alpha。 AlphaCorp說:「這是個好主意!」並將方法Frob添加到Alpha v2。 Beta客戶Gamma升級到Alpha的最新版本,現在*有一個警告,他們不會造成任何寫入雜注或放棄所有BetaExt *使用的情況下無法抑制的警告。這是壞壞壞。 – 2010-02-04 16:52:50

1

我可能是不完全正確,但是編譯器是這樣的: 當它到達

b.DoSomething("test") 

它試圖找到方法具有相同簽名在這個或基類,當它發現在基類中的方法調用映射到它,只需忽略擴展方法

但是,如果您例如從同一行的B聲明中移除基類A,編譯器將檢查此類或基類中是否存在具有此類簽名的方法,並將其替換爲調用靜態方法BExtensions.DoSomething。

你可以用.NET Reflector檢查它。

當B,從A派生:

.locals init (
    [0] class Test.A a, 
    [1] class Test.B b) 
... 
ldloc.1 // loading local variable b 
ldstr "test" 
callvirt instance string Test.A::DoSomething(string) 
call void [mscorlib]System.Console::WriteLine(string) 

當乙從System.Object派生:

.locals init (
    [0] class Test.A a, 
    [1] class Test.B b) 
... 
ldloc.1 // loading local variable b 
ldstr "test" 
call string Test.BExtensions::DoSomething(class Test.B, string) 
call void [mscorlib]System.Console::WriteLine(string)