C#中的方法重载与继承

3
我一直以为C#在运行时通过查看方法调用接收者(即点号前的对象)的运行时类型来动态解析方法调用。
但是以下代码示例工作方式不同。如果我在代码中使用GenericSpaceShip,则返回“Generic”;如果我使用SpaceShip,则返回“Specific”。请注意,两种情况下的运行时类型都是SpaceShip。
所以我的问题是:C#如何解析Visit方法调用,并为什么在这种情况下它会查看编译时而不是运行时类型?
请注意,这两个Visit方法具有不同的参数。正如Patko指出的那样,这意味着我不能在这里使用虚拟/重写。
class GenericSpaceShip
{
    public void Visit(GenericPlanet planet)
    {
        Console.WriteLine("Generic");
    }
}

class SpaceShip : GenericSpaceShip
{
    public void Visit(Planet planet)
    {
        Console.WriteLine("Specific");
    }
}

class GenericPlanet { }

class Planet : GenericPlanet { }

class Starter
{
    static void Main(string[] args)
    {
        // SpaceShip ship = new SpaceShip();
        GenericSpaceShip ship = new SpaceShip();
        Planet planet = new Planet();

        ship.Visit(planet); // => Generic
    }
}

我认为多态是您要寻找的。这与类型的运行时 vs. 编译时评估无关。 - Elad Lachmi
5个回答

5

如果您想要真正的动态分辨率,则需要使用dynamic关键字,如下所示:

static void Main(string[] args)
{
    dynamic ship = new SpaceShip();
    Planet planet = new Planet();

    ship.Visit(planet); // => Specific

    // also
    GenericPlanet genericPlanet = new GenericPlanet();
    ship.Visit(planet); // Generic
}

在这种情况下,行为会像您所描述的那样——参数的类型很重要。但是您最有可能想要的是方法覆盖,如下所示:
class GenericSpaceShip
{
    public virtual void Visit(GenericPlanet planet)
    {
        Console.WriteLine("Generic");
    }
}

class SpaceShip : GenericSpaceShip
{
    public override void Visit(GenericPlanet planet)
    {
        Console.WriteLine("Specific");
    }
}

在这种情况下,如果你有一艘 SpaceShip 的实例,则将调用 Specific 方法;如果有一个 GenericSpaceShip 实例,则将调用 Generic 方法,不考虑行星类型。在这种情况下,船的类型很重要:
static void Main(string[] args)
{
    SpaceShip ship = new SpaceShip();
    Planet planet = new Planet();

    ship.Visit(planet); // => Specific

    // also
    GenericPlanet genericPlanet = new GenericPlanet();
    ship.Visit(planet); // Specific       
}

如果使用GenericSpaceShip,则永远会得到Generic


我不知道为什么有人会投反对票:使用dynamic是一个100%有效的想法,它肯定会起作用。 - Sergey Kalinichenko

3

C#有两种方法:

  • 在运行时解决(重写) - 这适用于抽象和虚拟方法以及实现接口的方法。除其他外,这还要求方法返回类型和参数相同。
  • 在编译时解决(隐藏) - 这适用于具有相同名称和参数但不是虚拟的方法。

这给您对方法解析过程的最大控制权,但也需要您告诉编译器您的决定。

在您的代码中,派生类中的方法会隐藏基类中的方法。以下是如何修改代码使其成为重写:

class GenericSpaceShip {
    // Mark the base method virtual
    public virtual void Visit(GenericPlanet planet) {
        Console.WriteLine("Generic");
    }
}

class SpaceShip : GenericSpaceShip {
    // Mark the overriding method as such.
    // Also note that you cannot change argument types when you override:
    public override void Visit(GenericPlanet planet) {
        Console.WriteLine("Specific");
    }
}

需要注意两点:

  • 重写方法的签名必须与基础方法相同(因此,它们都使用GenericPlanet;如果需要,可以进行转换)。
  • 您需要使用关键字virtualoverride告诉编译器这两个方法是相关的。

你不能在覆盖方法时更改类型,因此用 Visit(Planet) 覆盖 Visit(GenericPlanet) 是行不通的。 - Patko
@Patko非常感谢您的评论 - 我完全忽略了OP那里有不同类型的行星! - Sergey Kalinichenko
谢谢您提到覆盖和隐藏之间的区别。这帮助我更好地理解了它。尽管如此,我不得不接受@Patko的答案,因为他给出了完美的解决方案。 - Jonas Pfannschmidt

1
在C#中,如果你想在派生类中重写方法,你需要明确声明这些方法为virtual/abstract,并使用override关键字进行重写。因此,代码应该如下所示:
class GenericSpaceShip
{
    public virtual void Visit(GenericPlanet planet)
    {
        Console.WriteLine("Generic");
    }
}

class SpaceShip : GenericSpaceShip
{
    public override void Visit(Planet planet)
    {
        Console.WriteLine("Specific");
    }
}

0

你正在使用继承来进行方法重载,这意味着你只是在基类(SpaceShip)和派生类(GenericSpaceShip)中保留了两个具有不同签名的方法。

间接派生类对象将始终具有这两个具有不同签名的方法,并且这将在编译时进行检查。你没有覆盖任何具有相同签名和返回类型的方法,因此不会有任何运行时或动态检查。


0

你要找的关键字是virtual和override。

class GenericSpaceShip
{
    public virtual void Visit(GenericPlanet planet)
    {
        Console.WriteLine("Generic");
    }
}

class SpaceShip : GenericSpaceShip
{
    public override void Visit(Planet planet)
    {
        Console.WriteLine("Specific");
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接