[英]Non-virtual methods, static binding, and interface in C#
我知道非虛擬方法是靜態綁定的,據我所知,這意味着在編譯時它本身就知道將在哪個對象上調用哪種方法。 該決定是基於對象的靜態類型進行的。 使我感到困惑的是接口 (而不是class )和靜態綁定。
考慮一下這段代碼,
public interface IA
{
void f();
}
public class A : IA
{
public void f() { Console.WriteLine("A.f()"); }
}
public class B : A
{
public new void f() { Console.WriteLine("B.f()"); }
}
B b = new B();
b.f(); //calls B.f() //Line 1
IA ia = b as IA;
ia.f(); //calls A.f() //Line 2
演示代碼: http : //ideone.com/JOVmi
我了解Line 1
。 編譯器可以知道bf()
將調用Bf()
因為它知道b
的靜態類型為B
但是,編譯器如何在編譯時自行確定ia.f()
將調用Af()
? 對象ia
的靜態類型ia
什么? 不是IA
嗎? 但這是一個接口,並且沒有f()
任何定義。 那它怎么運作的呢?
為了使情況更加令人困惑,讓我們考慮以下static
方法:
static void g(IA ia)
{
ia.f(); //What will it call? There can be too many classes implementing IA!
}
正如評論所言,實現接口IA
類太多了,那么編譯器如何靜態確定ia.f()
將調用哪個方法? 我的意思是說,如果我有一個定義為的班級:
public class C : A, IA
{
public new void f() { Console.WriteLine("C.f()"); }
}
如您所見,與B
不同, C
除了從A
派生之外,還實現了IA
。 這意味着,我們在這里有不同的行為:
g(new B()); //inside g(): ia.f() calls A.f() as before!
g(new C()); //inside g(): ia.f() doesn't calls A.f(), rather it calls C.f()
演示代碼: http : //ideone.com/awCor
我將如何理解所有這些變化,尤其是接口和靜態綁定如何一起工作?
還有更多( ideone ):
C c = new C();
c.f(); //calls C.f()
IA ia = c as IA;
ia.f(); //calls C.f()
A a = c as A;
a.f(); //doesn't call C.f() - instead calls A.f()
IA iaa = a as IA;
iaa.f(); //calls C.f() - not A.f()
請幫助我理解所有這些內容,以及C#編譯器如何完成靜態綁定。
但是,編譯器如何在編譯時自行確定
ia.f()
將調用Af()
?
它沒有。 它知道ia.f()
將對ia
包含的對象實例調用IA.f()
。 它發出此調用操作碼,並在執行調用時讓運行時確定。
這是將為示例代碼的下半部分發出的IL:
.locals init (
class B V_0,
class IA V_1)
IL_0000: newobj instance void class B::'.ctor'()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: callvirt instance void class B::f()
IL_000c: ldloc.0
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: callvirt instance void class IA::f()
IL_0014: ret
請注意,兩種情況都使用callvirt
。 之所以使用它,是因為當目標方法為非虛擬方法時,運行時可以自行確定。 (此外, callvirt
this
參數執行隱式null檢查,而call
則不執行。)
此IL轉儲應回答您所有其他問題。 簡而言之:編譯器甚至不會嘗試解析最終的方法調用。 這是運行時的工作。
靜態綁定意味着您想不到的其他東西。 也稱為“早期綁定”,它與后期綁定相反,在C#版本4中使用dynamic關鍵字,在所有版本中都使用反射。 后期綁定的主要特征是編譯器無法驗證所調用的方法是否存在,更不用說驗證是否傳遞了正確的參數。 如果有什么不對勁,您將獲得運行時異常。 這也很慢,因為運行時需要做額外的工作來查找方法,驗證參數並構造調用堆棧框架。
當您使用接口或虛擬方法時,這不是問題,編譯器可以預先驗證所有內容。 生成的代碼非常有效。 這仍然會導致實現接口和虛擬方法所需的間接方法調用(也稱為“動態調度”),但仍在C#中用於非虛擬實例方法。 在此博客文章中由前C#團隊成員記錄。 使這項工作起作用的CLR管道稱為“方法表”。 大致類似於C ++中的v表,但是方法表包含每個方法的條目,包括非虛擬方法。 接口引用只是指向該表的指針。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.