我对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
。我通常用这个“算法”处理这些问题:
mVVirtual
的实例方法?没有一个......但确实有一个带有该签名和名称的虚拟方法!a2
(C
)所持对象的类型,以覆盖该方法。它有一个 - >执行C::mVVirtual
!我的“算法”在哪里错了?我真的对此感到困惑,非常感谢一些帮助。
答案 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();
您告诉编译器b1
是B
,以便b1.mVVirtual()
在“框”mVVirtual
中查找,并找到带有定义的方法
Console.WriteLine("C::mVVirtual");
因为b1
实际上是C
,而mVVirtual
实例的“C
内容就是A a2 = new C();
a2.mVVirtual();
。
但是当你有
时a2
您告诉编译器A
是Console.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
表示A
是a2.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覆盖。