在C#中从基类调用被重写的方法

25

假设有以下C#类定义和代码:


public class BaseClass
{
    public virtual void MyMethod()
    {
        ...do something...
    }
}

public class A : BaseClass
{
    public override void MyMethod()
    {
        ...do something different...
    }
}

public class B : BaseClass
{
    public override void MyMethod()
    {
        ...do something different...
    }
}

public class AnotherObject
{
    public AnotherObject(BaseClass someObject)
    {
        someObject.MyMethod(); //This calls the BaseClass method, unfortunately.
    }
}

假设传入的对象是A或B的实例,我想调用实际上在A或B中找到的MyMethod(),而不是在BaseClass中找到的。除了这样做:


public class AnotherObject
{
    public AnotherObject(BaseClass someObject)
    {
        A temp1 = someObject as A;
        if (A != null)
        {
            A.MyMethod();
        }
        
        B temp2 = someObject as B;
        if (B != null)
        {
            B.MyMethod();
        }
    }
}

我该怎么做?


10
你传递给AnotherObject构造函数的实际对象是什么类型?换句话说,新声明是什么?因为你所描述的情况只有在使用BaseClass而不是A或B时才会发生。 - Nick
糟糕,我刚意识到我的问题。我原以为有几个类覆盖了这个函数,但实际上其中一个类没有成功覆盖该函数。问题解决了。 - David
1
我认为这是一个代码异味,对于大卫来说显然是很明显的,否则就不会发布这个问题了,而是直接使用那段有异味的代码。 - Greg
5个回答

14

调用的方法是通过传递到AnotherObject构造函数中的类型的多态性来确定的:

AnotherObject a = new AnotherObject(new A()); // invokes A.MyMethod() 
AnotherObject b = new AnotherObject(new B()); // invokes B.MyMethod() 
AnotherObject c = new AnotherObject(new BaseClass()); //invokes BaseClass.MyMethod() 

1
使用@base。我不喜欢打更多的字符。 - Yuriy Faktorovich

11
抱歉,你完全错了;这将违背虚拟方法的全部意义。如果 someObject 是 A,则会调用 A.MyMethod。如果 someObject 是 B,则会调用 B.MyMethod。如果 someObject 是 BaseClass,而不是从 BaseClass 派生的类型的实例,则将调用 BaseClass.MyMethod。
让我们使用大家最喜欢的例子:
class Animal {
    public virtual void Speak() {
        Console.WriteLine("i can haz cheezburger?");
    } 
}
class Feeder {
    public void Feed(Animal animal) { animal.Speak(); }
}
class Cat : Animal {
    public override void Speak() { Console.WriteLine("Meow!"); }
}
class Dog : Animal {
    public override void Speak() { Console.WriteLine("Woof!"); }
}

然后:

Animal a = new Animal();
Animal c = new Cat();
Animal d = new Dog();
Feeder f = new Feeder();
f.Feed(a);
f.Feed(c);
f.Feed(d);

这会打印出:

i can haz cheezburger?
Meow!
Woof!
再次强调,这就是虚方法的全部意义所在。 此外,我们可以查看规范。从10.6.3(虚方法)开始: 在虚方法调用中,该调用发生的实例的运行时类型决定要调用的实际方法实现。 具体来说,当使用参数列表A在编译时类型为C且运行时类型为R的实例上调用名为N的方法时(其中R是C或继承自C的类),处理调用如下: • 首先,将重载解析应用于C、N和A,以从在C中声明和继承的方法集中选择特定方法M。此在§7.5.5.1中描述。 • 然后,如果M是非虚方法,则调用M。 • 否则,M是虚方法,并且针对R的最派生实现被调用。 然后,我们需要“M的最派生实现”的定义。这是一个不错的递归定义: 针对类R确定虚方法M的最派生实现如下: • 如果R包含M的引入虚拟声明,则这是M的最派生实现。 • 否则,如果R包含对M的覆盖,则这是M的最派生实现。 • 否则,针对直接基类R的M的最派生实现与针对R的M的最派生实现相同。 因此,在我们上面的示例中,当参数a传递给Feeder.Feed(Animal)的实例是Cat的实例时,则Cat.Speak是最派生的实现。这就是为什么我们会看到“Meow!”而不是“i can haz cheezburger?”

我可能错过了与Feeder相关的课程。这对我来说很有可能。 - Yuriy Faktorovich
@Yuriy Faktorovich:Feeder只是扮演OP的AnotherObject的角色。 - jason

3

如果在基类中MyMethod()是抽象的,那么派生类中的版本将被使用。因此,如果您不需要调用基类中的实例,则这将是一个选项。

    static void Main(string[] args)
    {

        A classA = new A();
        B classB = new B();

        DoFunctionInClass(classA);
        DoFunctionInClass(classB);
        DoFunctionInClass(classA as BaseClass);

        Console.ReadKey();
    }

    public static void DoFunctionInClass(BaseClass c) 
    {
        c.MyMethod();
    }



public abstract class BaseClass
{
    public abstract void MyMethod();
}


public class A : BaseClass
{
    public override void MyMethod()
    {
        Console.WriteLine("Class A");
    }
}

public class B : BaseClass
{
    public override void MyMethod()
    {
        Console.WriteLine("Class B");
    }
}

如果不需要所列出的行为,+1 应该考虑设计 BaseClass 为抽象类。 - Greg

1

因为您将其输入为BaseClass而不是A或B,所以基类是方法调用的起点。

您可以尝试使用泛型:

public class AnotherObject 
{ 
    public AnotherObject<T>(T someObject) where T : BaseClass
    { 
        someObject.MyMethod(); //This calls the BaseClass method, unfortunately. 
    } 
} 

我不确定这个构造函数中是否适用,但你或许可以将它移到另一个方法中。


1
如果传入的someObject是A类,则调用A.MyMethod,而不是基类实现。还要看一下is关键字。

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