【问题标题】:Non virtual method resolution - why is this happening非虚拟方法解析 - 为什么会发生这种情况
【发布时间】:2012-12-18 02:44:36
【问题描述】:

我对如何解析非虚拟方法的理解(在 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。就目前而言,取决于您从哪里调用它(从内部班级或外部)行为正在改变。

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

【问题讨论】:

  • 不确定您在这里发现了什么令人困惑。你能解释一下你发现的问题吗?
  • 我以为我做到了。已经提供了两个输出以及我的期望。我将编辑我的帖子以更清楚地解释我的期望
  • 如果您在VehicleBase 中将Accelerate 设为虚拟并在Sedan 中覆盖它,您将获得您所期望的行为。
  • @juharr,这个问题的重点是方法 is 非虚拟的。所以我不想让它虚拟化。
  • @ShivKumar 你要么在虚拟环境中创建,要么得到你所看到的行为,就这么简单。 10 次中有 9 次在方法上使用新方法只是糟糕的设计。

标签: c# polymorphism overload-resolution


【解决方案1】:

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

  • Drive方法调用Accelerate时,不知道Sedan中有new方法,所以调用了base的对应方法
  • 当从Main 方法调用Accelerate 时,编译器知道您正在调用new 方法,因为它知道vehicle 变量的确切类型是Sedan

另一方面,当从Main方法调用Accelerate,但变量被声明为VehicleBase时,编译器不能假定类型是Sedan,所以它解析再次将Accelerate 传递给基类的方法。

【讨论】:

  • 因为 Accelerate 是一种非虚拟方法,并且变量已被键入为 Sedan,编译器确实知道该方法已被重新引入(新)并且应该从两个地方调用重新引入的 Accelerate 方法。
  • @ShivKumar 编译器只有在您将变量声明为Sedan 时才知道这一点,即在您的帖子中编写代码的方式。但是当你声明变量为VehicleBase vehicle = new Sedan()时,编译器知道vehicle的类型是VehicleBase,所以不能调用新添加的方法。
  • 变量被键入为轿车的情况是(在我看来)行为是意外的。因此,如果它在编译时知道类型是 Sedan,并且所讨论的方法是非虚拟的,为什么它的行为方式会如此呢?也许如果你再读一遍我的帖子,它会有所帮助:)
  • @ShivKumar 我想我确实正确地阅读了您的帖子。您从两个地方调用Accelerate。在一个地方,编译器知道它是Sedan;另一方面,编译器不知道它是Sedan。具体来说,在Main 中,编译器知道确切的类型是Sedan,但在VehicleBase.Drive 中,编译器不知道它是Sedan。因此,new 方法只能从 Main 调用,而不是从 Drive 方法调用。
  • 了解编译器如何知道变量是轿车类型。它被宣布为这样。恕我直言,这里还有其他事情发生。尽管如此,我很难相信这是“预期的”行为。人们总是可以合理化这一点,但认为我们在语言中具有“按设计”的这种行为是很难相信的,因为我无法想象我会在何时何地使用这种行为。
【解决方案2】:

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

这就是为什么对 Accelerate 的调用在 Main 上下文中转到 Sedan.Accelerate,但在 VehicleBase.Drive 上下文中转到 VehicleBase.Accelerate

Main 函数的主体中,您声明了一个Sedan 类型的变量,并使用该变量进行方法调用。编译器在用于进行调用的变量类型中查找名为“Accelerate”的方法,键入 Sedan,并找到 Sedan.Accelerate

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

Sedan 类型中声明的方法体中,编译器会先查看Sedan 类型的方法,如果没有则查看VehicleBase 类型的方法来解析非虚拟方法调用在Sedan 中找到匹配项。

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

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

【讨论】:

  • 感谢您的解释并阐明了从 Drive 方法调用 VehicleBase.Accelerate 的原因。我得到这个的关键是 self/this 的 compile-time 类型是 VehicleBase 而不是变量的类型。因此,即使我现在理解了解释,我也不太明白为什么编译器在编译时看不到 Sedan 类型(在 Drive 方法中):)。
  • Delphi 的行为会类似吗?我经常使用静态方法分派来发挥我的优势,并且在从基类调用它们时从未在后代类中重新引入这些方法。 Java 怎么样(你知道吗?)
  • C# 和 Delphi 行为之间的唯一区别是 Delphi 在 C# 之前 6 年就这样做了。 ;>
  • 在 Delphi 术语中,可以这样想:Delphi 自顶向下编译。 Sedan 类出现在 VehicleBase.Drive 之后。因此,编译器在编译和发出 VehicleBase.Drive 的机器代码时甚至还没有解析 Sedan 类。因此,至少对于 Delphi,它不可能在 VehicleBase.Drive 方法的上下文中了解 Sedan 类。 C# 是一个多通道解析器,所以这个论点不成立。
  • 所以这个论点不成立....因此这种行为是“设计使然”。我猜它也在 Delphi 中?
猜你喜欢
  • 2021-01-24
  • 1970-01-01
  • 2021-04-30
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 2018-04-27
  • 2013-08-02
  • 2018-12-19
相关资源
最近更新 更多