簡體   English   中英

C#中的非虛擬方法,靜態綁定和接口

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM