.net 繼承&多態情況下,調用方法的判斷規則
從子類開始,一直向父類遞歸
如果方法聲明在接口上,那么返回這個方法
如果方法聲明在父類上,如下所示
如果給定的方法是override,那么返回虛方法 (被override的方法)
如果給定的方法是new的 (注意,默認就是new), 那么將返回該方法本身, (在IL中可以看到newslot)
如果該方法沒有被定義在當前類型中,那么返回開始分析當前類的父類
以下是原文,我的翻譯有改動部分內容
If the method is declared on an interface, returns the method.
If the method is defined in a base class, then works as follows:
If a given method overrides a virtual definition in the base class, the virtual definition is returned.
If a given method is specified with the new keyword (as in newslot as described in Common Type System), the given method is returned.
If the method is not defined in the type of the object on which GetBaseDefinition is called, the method definition highest in the class hierarchy is returned.
通過ILSpy 查看生成的IL可以證明這一點
還是舉個詳細的例子吧
1.子類沒有實現 繼承自父類
namespace CSharpTester
{
class Program
{
static void Main(string[] args)
{
A a = new A();
B b = new A();
a.A1();
b.A1();
}
}
public class A : B
{
}
public class B
{
public void A1() { Console.WriteLine(1); }
}
}
結果是,注意IL指令中子類調用的是B.A1

2.子類override父類的實現
namespace CSharpTester
{
class Program
{
static void Main(string[] args)
{
A a = new A();
B b = new A();
a.A1();
b.A1();
}
}
public class A : B
{
public new void A1()
{
Console.WriteLine(2);
}
}
public class B
{
public virtual void A1()
{
Console.WriteLine(1);
}
}
}
結果是,注意IL指令中子類調用的是A.A1

3.子類override父類的方法
namespace CSharpTester
{
class Program
{
static void Main(string[] args)
{
A a = new A();
B b = new A();
a.A1();
b.A1();
}
}
public class A : B
{
public override void A1()
{
Console.WriteLine(2);
}
}
public class B
{
public virtual void A1()
{
Console.WriteLine(1);
}
}
}
結果是,IL中子類調用的是B.A1

4.子類直接或者間接繼承接口 (注意調用方法還是從接口出發;如果是從類型出發,那么和上面三種情況是一致的)
namespace CSharpTester
{
class Program
{
static void Main(string[] args)
{
IB a = new A();
IB b = new A();
A a1 = new A();
B b1 = new A();
a.A1();
b.A1();
a1.A1();
b1.A1();
}
}
public class A : B
{
public new void A1()
{
Console.WriteLine(2);
}
}
public class B : IB
{
public virtual void A1()
{
Console.WriteLine(1);
}
}
public interface IB
{
void A1();
}
}
如果是從接口出發調用方法,IL中都是調用IB.A1 (無論其真實類型是什么); 而如果從類型出發 和1-3點是一致的

到此為止,我們知道了不同類型聲明對應的IL指令
在沒有override只有new的情況下,我們可以很準確的判斷出被調用的方法
而在有override的情況下,(注 只有virtual方法才能被override)
子類執行的IL指令是B.A1(), 那么CLR是如何讓真正被執行的方法是 A.A1() 而不是B.A1()呢
因為CLR在執行B.A1()的時候傳入了一個參數, 就是A的實例對象a (C#代碼 a.A1()中的a就是A的實例對象)
現在有了對象,不過呢,方法定義在哪里?
在。net中虛方法繼承以后的位置不改變, 例如 B.A1()的Slot位置是5, 那么其子類的方法A.A1()的Slot位置也是5
那么結果就出來了,首先根據IL指令 B.A1()獲取Slot相對位置,然后根據實例參數a 獲取A的類型定義,同樣的 找到位置5.那就是A.A1(),這才是真正被執行的方法
a.A1() -> IL指令為B.A1() -> 獲取slot位置 ->
-> 獲得a -> 獲得a的類型定義A -> 獲得A同樣slot位置的方法
->執行方法
順便說一句:
IL指令callvirt 就是專門用來處理這種情況的, (callvirt會執行運行時綁定檢查實例對象,在運行時才知道真正的方法在哪里)
對應的 還有一個IL指令call就不檢查這種情況,(你可以發現 靜態方法的生成的IL指令都是call,不需要實例對象)
未完待續
浙公網安備 33010602011771號