关于虚拟/新/覆盖的困惑

时间:2010-01-14 16:40:15

标签: c# virtual override new-operator keyword

我对virtual / new / override事情感到有点困惑。这是一个例子:

class A
{
    public virtual void mVVirtual() { Console.WriteLine("A::mVVirtual"); }
}

class B : A
{
    public virtual void mVVirtual() { Console.WriteLine("B::mVVirtual"); }
}

class C : B
{
    public override void mVVirtual() { Console.WriteLine("C::mVVirtual"); }
}


class Test
{
    static void Main()
    {
        B b1 = new C();
        b1.mVVirtual();    //C::mVVirtual ... I understand this

        A a2 = new C();
        a2.mVVirtual();    //A::mVVirtual ... ???
    }
}

我不明白为什么在第二次通话中我们得到A::mVVirtual。我通常用这个“算法”处理这些问题:

  1. 检查包含对象引用的变量的类型,以获取名为mVVirtual的实例方法?没有一个......但确实有一个带有该签名和名称的虚拟方法!
  2. 虚拟方法?然后让我们检查a2C)所持对象的类型,以覆盖该方法。它有一个 - >执行C::mVVirtual
  3. 我的“算法”在哪里错了?我真的对此感到困惑,非常感谢一些帮助。

5 个答案:

答案 0 :(得分:11)

以下是您对虚拟方法的看法。类的每个实例都有“框”来保存方法。当您将方法标记为virtual时,它会说出一个新的“框”并在其中放入一个方法。在派生类中将方法标记为override时,它会保留基类中的“框”,但会在其中添加新方法。

所以这里有一个班级A和一个名为mVVirtual的方法,标记为virtual。这表示创建一个名为mVVirtual的新“框”,并在其中添加定义

的方法
Console.WriteLine("A::mVVirtual"); 

然后您有一个派生类B和一个名为mVVirtual的方法,标记为virtual。这表示创建一个名为mVVirtual的新“框”,并在其中添加定义

的方法
Console.WriteLine("B::mVVirtual"); 

特别是,隐藏了从A继承的“盒子”!输入B的对象或从B派生的类不能看到它。

然后您有一个派生类C和一个名为mVVirtual的方法,标记为override。这表示从mVVirtual继承名为B的“框”,并在其中添加了一个不同的方法

Console.WriteLine("C::mVVirtual"); 

现在,当你有

B b1 = new C(); 
b1.mVVirtual();

您告诉编译器b1B,以便b1.mVVirtual()在“框”mVVirtual中查找,并找到带有定义的方法

Console.WriteLine("C::mVVirtual"); 

因为b1实际上是C,而mVVirtual实例的“C内容就是A a2 = new C(); a2.mVVirtual();

但是当你有

a2

您告诉编译器AConsole.WriteLine("A::mVVirtual"); ,因此它会在“框”中查找并找到

a2

编译器无法知道C实际上是A(您已将其键入为a2),因此它不知道mVVirtual实际上是{从隐藏由A定义的“框”A的类派生的类的实例。它知道的是mVVirtual有一个名为class A { public virtual void mVVirtual() { Console.WriteLine("A::mVVirtual"); } } 的“框”,因此它会发出代码来调用该框中的方法。

所以,试着简明扼要地说:

A::mVVirtual

定义了一个具有全名mVVirtual的“框”的类,但您可以通过名称class B : A { // "new" method; compiler will tell you that this should be marked "new" for clarity. public virtual void mVVirtual() { Console.WriteLine("B::mVVirtual"); } } 来引用。

B::mVVirtual

定义了一个具有全名mVVirtual的“框”的类,但您可以通过名称B.mVVirtual来引用。提及A::mVVirtual不会引用全名B的“框”;键入为B的对象(或从class C : B { public override void mVVirtual() { Console.WriteLine("C::mVVirtual"); } } 派生的类)无法看到“框”。

B::mVVirtual

定义一个类,该类使用全名A a2 = new C(); a2.mVVirtual(); 的“框”,并在其中放入不同的方法。

然后

a2

表示Aa2.mVVirtual,以便A::mVVirtual在“框”中查看全名A::mVVirtual 并调用该“框”中的方法。这就是你看到

的原因
abstract

在控制台上。

还有另外两种方法注释器。 new使一个新的“框”没有在“框”中放置方法定义。 virtual创建一个新的“框”并将方法定义放在“框”中,但不允许派生类将自己的方法定义放在“框”中(如果需要,请使用{{1}}这样做。)

很抱歉啰嗦但我希望有所帮助。

答案 1 :(得分:6)

更新:有关此语言功能的详细信息,请参阅此处的后续问题:More about Virtual / new...plus interfaces!

杰森的回答是正确的。总而言之,更简洁一点。

你有三种方法。称他们为MA,MB和MC。

你有两个“盒子”,或者,因为它们通常被称为插槽。我们会坚持杰森的命名法。称他们为BOX1和BOX2。

“A”定义BOX1。

“B”定义BOX2。

“C”定义没有框;它重用了BOX2。

当您说“新A()”时,BOX1将填入MA。

当你说“new B()”时,BOX1用MA填充,而BOX2用MB填充。

当你说“new C()”时,BOX1用MA填充,而BOX2用MC填充。

现在假设您有一个类型为A的变量,并调用该方法。原因就像编译器一样。编译器说“类型A上是否有与此名称匹配的框?”是的,有一个:BOX1。因此,编译器生成对BOX1内容的调用。

正如我们所见,BOX1的内容始终为MA,因此无论变量是否实际持有对A,B或C的引用,都会调用MA。

现在假设您有一个B类型的变量,并调用该方法。再次,像编译器一样思考。编译器说“类型B上是否有与此名称匹配的框?”是的,有两个符合名称的盒子。编译器说“这两者中哪一个与B更密切相关?”答案是BOX2,因为B声明了BOX2。因此,编译器生成对BOX2的调用。

如果变量包含B,则调用MB,因为在B中,BOX2包含MB。如果变量包含C,则调用MC,因为在C中,BOX2包含MC。

现在清楚了吗?请记住,重载决议只选择框。该框的内容取决于运行时的对象。

答案 2 :(得分:3)

你有隐藏的警告吗?当我做你做的事情时,我收到了这个警告:

  

'ProjectName.ClassName.B.mVVirtual()'隐藏继承的成员'ProjectName.ClassName.A.mVVirtual()'。要使当前成员覆盖该实现,请添加override关键字。否则添加新关键字。

如果您在B级使用override,则不会出现此问题;两种情况都会给你“C :: mVVirtual”。由于您没有在B类中使用override,因此在方法前面有一个隐式 new。这打破了继承链。您的代码正在调用类型A上的方法,并且由于隐式new,没有覆盖该方法的继承类。所以它必须调用A类的实现。

答案 3 :(得分:1)

考虑它的最佳方式是虚拟方法使用 实际 (或 具体 )类型决定要执行的实现的对象,其中非虚方法使用'用于访问方法的声明类型的变量来决定运行哪个...

覆盖意味着您正在编写一个方法,该方法将“替换”继承链上方的虚拟或抽象方法(具有相同名称/签名)的实现。

如果链上的非虚方法具有相同的名称/签名,则使用

new ,您要添加的方法将替换...

区别如下

class base     { public virtual void  foo() { Console.write("base.foo"); } }
class derived  { public override void foo() { Console.write("derived.foo"); } }
base b = new base();
b.foo()  // prints "base.foo" // no issue b is a base and variable is a base
base b = new derived();
b.foo(); // prints "derived.foo" because concrete tyoe is derived, not base

class base     { public void  foo() { Console.write("base.foo"); } }
class derived  { public new void foo() { Console.write("derived.foo"); } }
base b = new base();
b.foo()  // prints "base.foo" // no issue b is a base and variable is a base
base b = new derived();
b.foo(); // prints "base.foo" because variable b is base. 
derived d = b as derived;
d.foo()    //  prints "derived.foo" - now variable d is declared as derived. 

您的第二个电话打印A::mvVirtual,因为该方法(mVVirtual)实际上 虚拟,(尽管它的名称),因为它有 new 说明符......所以它根据变量类型决定,即A。

为了解释技术上的情况,Every Type有一个“方法表”,其中包含指向该类型中所有方法的指针。 (它不是具有此表的类型的实例,它是 TYPE 本身。)每个类型的方法表首先使用所有可访问的虚拟方法构建,从开头的object(继承链最远),最后是类型本身声明的虚方法。然后,在表示所有虚拟方法之后,再次从object中的任何非虚方法添加所有非虚方法,首先,一直添加到Type本身中的任何非虚方法。该表以这种方式构造,以便所有虚拟元数据的偏移在所有派生类的方法表中都是相同的,因为编译器可以从声明为其他类型的变量调用这些方法,甚至从在基础中声明和实现的其他方法中的代码调用具体类的类型。

当编译器解析虚拟方法调用时,它会转到方法表中查找对象本身的类型( 具体 类型),而对于非虚拟调用它转到方法表以获取变量的声明类型。因此,如果您调用虚方法,即使是基类型中的代码,如果实际的具体类型是从此基类型派生的类型,编译器将转到该具体类型的方法表。

如果调用非虚方法,(无论继承在多大程度上改变了实际对象的类型),编译器访问方法表以获取变量; es'声明的类型。此表格中的任何派生类型中都有 nothing

答案 4 :(得分:0)

这就是我理解的方式

A是基类
B继承A但不覆盖它 C继承B但确实覆盖它

由于您声明A但初始化C,它将忽略覆盖,因为基类是A,A永远不会从B覆盖。

相关问题