2

我对如何解析非虚拟方法的理解(在 C# 中)是它取决于变量的类型(而不是实例的类型)。

看看下面的代码。

class Program
{
    static void Main(string[] args)
    {
        Sedan vehicle = new Sedan();
        vehicle.Drive();
        vehicle.Accelerate();
    }
}

abstract class VehicleBase
{
    public void Drive()
    {
        ShiftIntoGear();
        Accelerate();
        Steer();
    }

    protected abstract void ShiftIntoGear();
    protected abstract void Steer();

    public void Accelerate()
    {
        Console.WriteLine("VehicleBase.Accelerate");
    }
}

class Sedan : VehicleBase
{
    protected override void ShiftIntoGear()
    {
        Console.WriteLine("Sedan.ShiftIntoGear");
    }

    protected override void Steer()
    {
        Console.WriteLine("Sedan.Steer");
    }

    public new void Accelerate()
    {
        Console.WriteLine("Sedan.Accelerate");
    }
}

控制台窗口显示以下内容:

Sedan.ShiftIntoGear
VehicleBase.Accelerate
Sedan.Steer
Sedan.Accelerate

这对我来说没有意义,我相信会让很多人陷入困境。如果您现在将变量车辆声明为 VehicleBase 类型,您将得到

Sedan.ShiftIntoGear
VehicleBase.Accelerate
Sedan.Steer
VehicleBase.Accelerate

这也是我在前一种情况下所期望的,因为方法 Accelerate 是非虚拟的。

在前面的输出中,(变量车辆类型为 Sedan,我希望调用 Sedan.Accelerate 而不是 VehicleBase.Accelerate。就目前而言,取决于您从哪里调用它(从类中或从外部)行为正在改变。

在我看来,重新引入方法的重载解决规则优先,但我很难相信这是正确/预期的行为。

4

2 回答 2

4

所有这些都非常有意义 - 当您将车辆声明为Sedan时,两个调用Accelerate的解析方式不同:

  • AccelerateDrive方法中调用到时,它不知道 中有一个new方法Sedan,所以它调用了基的相应方法
  • AccelerateMain方法调用 to 时,编译器知道您正在调用该new方法,因为它知道vehicle变量的确切类型是Sedan.

另一方面,当AccelerateMain方法调用 to ,但变量声明为VehicleBase时,编译器不能假定类型为Sedan,因此它Accelerate再次将 解析为基类的方法。

于 2012-12-15T20:14:57.943 回答
1

非虚方法在编译时根据表达式的类型进行解析,调用目的地在编译时是固定的。编译器在调用的源代码上下文中的编译时可用的符号信息中查找方法名称,并发出调用指令来调用它能够找到的非虚拟方法。实例的实际类型是什么并不重要,调用总是转到在编译时从静态类型信息中找出的非虚拟方法。

这就是为什么在Main 的上下文中调用 to Accelerate,但在 的上下文中调用:Sedan.AccelerateVehicleBase.AccelerateVehicleBase.Drive

在你的Main函数体中,你已经声明了一个 type 的变量Sedan,并且你使用这个变量来调用方法。编译器在用于进行调用的变量类型中查找名为“Accelerate”的方法,键入 Sedan,然后找到Sedan.Accelerate.

在方法内部,“self”VehicleBase.Drive编译时类型是VehicleBase. 这是编译器可以在此源代码上下文中看到的唯一类型,因此在“VehicleBase.Drive”中对 Accelerate 的调用将始终转到VehicleBase.Accelerate,即使运行时对象实例的类型实际上是Sedan

Sedan类型中声明的方法的主体中,编译器将通过首先查看该类型的方法来解析非虚拟方法调用,然后如果没有找到匹配项Sedan,则查看该类型。VehicleBaseSedan

如果要根据实际对象实例类型改变调用目的地,则必须使用虚方法。使用虚拟方法也将提供更加一致的执行结果,因为调用将始终转到由对象实例在运行时确定的最具体的实现。

非虚拟方法调用目的地是根据编译时类型选择的,不知道运行时。在运行时根据实例类型选择虚拟方法调用目的地。

于 2012-12-17T18:28:07.917 回答