.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,不需要实例对象)

未完待续

原文地址:https://www.cnblogs.com/PurpleTide/p/2182906.html