c#继承覆盖问题

时间:2010-01-11 00:31:33

标签: c# inheritance

修复了代码问题。 好的,我想我需要澄清这个问题。

new A.example();输出“A”

示例方法中应该包含哪些内容以便输出“???”? 这甚至可能吗?

public class Letter {

    public virtual void AsAString() {
         Console.WriteLine("???");
    }
    public void example() {
         this.AsAString();
    }
}

public class A : Letter {
  public override void AsAString() { Console.WriteLine("A"); }
  public void example2() { base.AsAString(); }
}

new A().example2();
new A().example();

6 个答案:

答案 0 :(得分:2)

这很容易:

public void example() {
     Console.WriteLine("???");
}

从这个答案中你应该意识到你实际要求的并不是你想要的......

如果您的意思是要将虚拟方法称为非虚拟方法,那么这是不可能的。即使您将引用转换为基类,它仍然使用对象的实际类型来确定要调用的方法。

答案 1 :(得分:2)

让我们首先确保我正确地解释您的问题。您有如上定义的类。您正在实例化A的实例并调用example从基类继承的方法A。您想知道方法this.AsAString()中的调用Letter.Example是否可以调用AsAString的基本实现而不是派生的实现。

首先,让我们理解为什么如上所述定义example,通过Letter.example的实例(例如A)调用new A().example会导致A.AsAString被调用。从规范(第7.4.4节):

  

确定要调用的函数成员实现:

     

如果E的编译时类型是接口,则要调用的函数成员是由E引用的实例的运行时类型提供的M的实现。此函数成员通过应用接口映射规则来确定(§13.4.4)确定由E引用的实例的运行时类型提供的M的实现。

     

否则,如果M是虚函数成员,则要调用的函数成员是由E引用的实例的运行时类型提供的M的实现。此函数成员通过应用规则来确定确定M的最多派生实现(第10.6.3节)与E引用的实例的运行时类型有关。

     

否则,M是非虚函数成员,要调用的函数成员是M本身。

现在让我们考虑你的情况。您有一个a的实例A来自Letter。您已通过语法example调用名为a.example()的方法。这将调用具有定义的Letter.example

public void example() {
    this.AsAString();
}

这会调用Letter.AsAString。但是,Letter.AsAString已声明为virtual,因此,根据上面的粗体规则,调用的方法为A.AsAString,因为this的类型为A,{ {1}}来自ALetter提供A override

现在,如果您更改Letter.AsAString的定义,以便使用A.AsAString修饰符隐藏基础实现

new

然后public new void AsAString() { Console.WriteLine("A"); } 将导致使用基础实现,您将根据需要看到输出a.example。这是因为,根据上述规则,???的派生程度最高的实现(即Letter.AsAString层次结构中提供A方法{{1}的定义的派生类型最多的类型}}是基础实现。 virtual修饰符允许AsAString使用与new具有相同签名的A方法,但它不是AsAString方法。

如果我错误地解释您的问题,或者如果上述任何一项需要澄清,请告诉我。

答案 2 :(得分:1)

回答你的问题,我认为不可能。一旦覆盖该方法,除非使用基本访问器访问基类实现,否则您的调用将是您拥有的“最专业”方法(在这种情况下,在A中定义的AsASString中的方法,而不是在A中定义的AsASString)信。

Woot4Moo放在一起的建议不起作用,因为AsASString()和this.AsASString()正在访问相同的方法,恕我直言(在A中实现该方法)。

正如您所知,base.AsASString()实际上调用了基本方法,而不是专门的方法。

我希望这会有所帮助。

干杯,瓦格纳。

答案 3 :(得分:1)

C#中的virtual关键字与大多数其他语言中的关联一样 - 具体而言,调用的具体方法由实例的实际运行时类型决定。

您正在寻找的只是简单的方法隐藏。该计划:

class A
{
    public void Foo()
    {
        Console.WriteLine("Foo called from A");
    }

    public virtual void Bar()
    {
        Console.WriteLine("Bar called from A");
    }

    public virtual void Baz()
    {
        Console.WriteLine("Baz called from A");
    }
}

class B : A
{
    public new void Foo()
    {
        Console.WriteLine("Foo called from B");
    }

    public override void Bar()
    {
        Console.WriteLine("Bar called from B");
    }

    public override void Baz()
    {
        base.Baz();
        Console.WriteLine("Baz called from B");
    }
}

static void Main()
{
    A a = new A();
    a.Foo();
    a.Bar();
    a.Baz();

    B b = new B();
    b.Foo();
    b.Bar();
    b.Baz();

    A a2 = new B();
    a2.Foo();
    a2.Bar();
    a2.Baz();
}

将产生以下输出:

Foo called from A
Bar called from A
Baz called from A

Foo called from B
Bar called from B
Baz called from A
Baz called from B

Foo called from A
Bar called from B
Baz called from A
Baz called from B

让我们打破这个:

  • 方法A.Foo()是一种非虚拟方法,B 隐藏。如果您在声明为Foo的变量上调用A,则始终会调用A.Foo(),即使它实际上是B的实例。

  • 方法A.Bar()是一种虚拟方法,由B 覆盖。如果对声明为Bar的变量调用A,如果变量实际上是B.Foo()的实例,则实际上会调用B。根据规范,这就是虚拟调度的工作方式。

  • 方法A.Baz()也是虚拟的,但B.Baz()在运行自己的代码之前也会调用基本版本。这就是为什么在最后两组中看到该方法的两行输出。 只有派生类可以调用虚拟基本方法 - 无法从外部调用它。

因此,如果您需要能够执行基本方法和派生方法,使方法成为虚拟方法。在派生类中隐藏阴影

答案 4 :(得分:1)

您似乎对虚拟方法的工作方式感到困惑。这是一个思考它的好方法。想象一下,每个类的每个实例都有一定数量的“槽”。在运行时,插槽包含一个方法。要调用方法,请告诉运行时“调用此对象的插槽x中的任何方法”。

当你制作一个“抽象”方法时,它会创建一个新的插槽并且不会放入任何内容。

制作“虚拟”方法时,会创建一个新插槽并将方法放入插槽中。

制作“覆盖”方法时,不会创建新插槽。它通过替换先前声明的虚方法槽中的任何内容来“覆盖”。

当你制作一个“新”方法时,它会创建一个新的插槽。这就是“新”的意思。

当你制作一个没有注释的普通方法时,它就像一个“新”方法。

当您编写调用方法的代码时,编译器会计算出您正在讨论的插槽,并生成代码,告诉运行时“调用此对象的此插槽中的任何方法。”

此规则的例外是“基本”电话;生成代码意味着“忽略插槽中的内容并调用此方法的基类版本”。

现在清楚了吗?

答案 5 :(得分:0)

对此的解决方案是声明A.AsAString,如下所示:

public new void AsAString() { Console.WriteLine("A"); }

这可能不符合您的需求,因为((Letter)new A()).AsAString()将返回“???”。如果这符合您的需求,那么您最好不要首先将其声明为虚拟。

因此,您要么允许通过基本引用调用您的覆盖函数,要么不这样做。有点你不能吃,也吃它。

考虑这个问题的另一种方法是,如果存在这样的解决方案,将会严重破坏封装。关于它如何与基类交互,该类应该是权限。对于类的用户,调用哪个基函数的确切实现应该是不透明的。 A.AsAString()必须是与Letter.AsAString()互动的权威,而不是其他任何人。

要实现您想要的功能,您需要添加其他功能,如下所示:     公共课信{

    public virtual void AsAString() {
        BaseAsAString();
    }
    public virtual void BaseAsAString() {
        Console.WriteLine("???");
    }
    public void example() {
        this.AsAString();
    }
}

public class A : Letter {
    public override void AsAString() { Console.WriteLine("A"); }
}

A a = new A();
a.AsAString(); //A
a.BaseAsAString(); //???