关于虚拟/新建/覆盖的混淆
我对 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
!
我的“算法”哪里错了?我真的对此感到困惑,非常感谢一些帮助。
I am a bit confused about the virtual
/new
/override
thing. Here's an example:
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 ... ???
}
}
I don't get why in the second call we get A::mVVirtual
. I usually treat these issues with this "algorithm":
- Check the type of the variable holding the reference for the object for an instance method called
mVVirtual
? Doesn't have one...but does have a virtual method with that signature and name! - Virtual method? Let's then check the type of the object being held by
a2
(C
) for an overriding of that method. It has one -> ExecutesC::mVVirtual
!
Where is my "algorithm" wrong? I really am confused by this, and would greatly appreciate some help.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
这是您对虚拟方法的看法。类的每个实例都有保存方法的“盒子”。当您将一个方法标记为
虚拟
时,它会说创建一个新的“盒子”并在其中放入一个方法。当您在派生类中将方法标记为override
时,它会保留基类中的“框”,但会在其中放入一个新方法。因此,这里有一个类
A
和一个名为mVVirtual
的方法,该方法被标记为virtual
。这表示创建一个名为mVVirtual
的新“盒子”,并在其中添加一个带有定义的方法然后您就有一个派生类
B
和一个名为mVVirtual
的方法> 被标记为虚拟
。这表示创建一个名为mVVirtual
的新“盒子”,并在其中放入一个带有定义的方法特别是,从
A
继承的“盒子”被隐藏了!类型为B
的对象或从B
派生的类无法看到它。那么您就有一个派生类
C 和一个名为
mVVirtual
的方法,该方法被标记为override
。这表示采用从B
继承的名为mVVirtual
的“盒子”,并在其中添加一个不同的方法和定义。现在,当您拥有时,
您将告诉编译器
b1
是一个B
,因此b1.mVVirtual()
会在“盒子”mVVirtual
中查找并找到具有定义的方法,因为 < code>b1 实际上是一个
C
,这就是C
实例的“盒子”mVVirtual
中的内容。但是,当您
告诉编译器
a2
是A
时,它会在“盒子”中查找并找到编译器无法知道
a2< /code> 实际上是一个
C
(您将其输入为A
),因此它不知道a2
实际上是一个实例派生自隐藏了由A
定义的“盒子”mVVirtual
的类。它所知道的是A
有一个名为mVVirtual
的“盒子”,因此它会发出代码来调用该“盒子”中的方法。因此,尝试简洁地说:
定义一个类,它有一个全名为
A::mVVirtual
的“盒子”,但您可以通过名称mVVirtual
来引用它。定义一个类,该类有一个全名为
B::mVVirtual
的“盒子”,但您可以通过名称mVVirtual
来引用它。引用B.mVVirtual
不会引用全名为A::mVVirtual
的“盒子”;类型为B
的对象(或从B
派生的类)无法看到“盒子”。定义一个类,它采用全名为 B::mVVirtual 的“盒子”,并在其中放入不同的方法。
然后
说
a2
是A
,以便a2.mVVirtual
在全名A::mVVirtual< 的“盒子”中查找/code> 并调用该“盒子”中的方法。这就是您
在控制台上看到的原因。
还有另外两个方法注释器。
abstract
创建一个新的“盒子”,但不会在“盒子”中放入方法定义。new
创建一个新的“盒子”,并将方法定义放入“盒子”中,但不允许派生类将自己的方法定义放入“盒子”中(使用virtual< /code> 如果你想这样做)。
抱歉啰嗦了,但希望能有所帮助。
Here's how you think of virtual methods. Every instance of a class has "boxes" to hold methods. When you mark a method as
virtual
it says make a new "box" and put a method in it. When you mark a method asoverride
in a derived class, it keeps the "box" from the base class but puts a new method in it.So here you have a class
A
and a method namedmVVirtual
that is marked asvirtual
. This says make a new "box" namedmVVirtual
and put a method in it with definitionThen you have a derived class
B
and a method namedmVVirtual
that is marked asvirtual
. This says make a new "box" namedmVVirtual
and put a method in it with definitionIn particular, the "box" inherited from
A
is hidden! It can not be seen by objects that are typed asB
s or classes that derive fromB
.Then you have a derived class
C
and a method namedmVVirtual
that is marked asoverride
. This says take the "box" namedmVVirtual
inherited fromB
and put a different method in it with definitionNow, when you have
you are telling the compiler that
b1
is aB
so thatb1.mVVirtual()
looks in the "box"mVVirtual
and finds the method with definitionbecause
b1
is really aC
and that is what is in the "box"mVVirtual
for instances ofC
.But when you have
you are telling the compiler that
a2
is anA
and so it looks in the "box" and findsThe compiler can not know that
a2
is really aC
(you've typed it as anA
) so it does not know thata2
is really an instance of a class that is derived from a class that has hidden the "box"mVVirtual
defined byA
. What it does know is thatA
has a "box" namedmVVirtual
and so it emits code to invoke the method in that "box".So, to try to put this succinctly:
defines a class that has a "box" with full name
A::mVVirtual
but that you can refer to by the namemVVirtual
.defines a class that has a "box" with full name
B::mVVirtual
but that you can refer to by the namemVVirtual
. Referring toB.mVVirtual
will not refer to the "box" with full nameA::mVVirtual
; that "box" can not be seen by objects that are typed asB
s (or classes that derive fromB
).defines a class that takes the "box" with full name
B::mVVirtual
and puts a different method in it.Then
says that
a2
is anA
so thata2.mVVirtual
looks in the "box" with full nameA::mVVirtual
and invokes the method in that "box". This is why you seeon the console.
There are two other method annotaters.
abstract
makes a new "box" does not put a method definition in the "box".new
makes a new "box" and puts a method definition in the "box" but does not allow derived classes to put their own definitions of the method in the "box" (usevirtual
if you want to do that).Sorry for being long-winded but I hope that helps.
更新:有关此语言功能的更多信息,请参阅此处的后续问题:有关虚拟/新...加上界面的更多信息!
Jason 的答案是正确的。总结得更简洁一些。
你有三种方法。称他们为 MA、MB 和 MC。
您有两个“盒子”,或者通常称为插槽。我们将坚持使用 Jason 的命名法。将它们称为 BOX1 和 BOX2。
“A”定义BOX1。
“B”定义了BOX2。
“C”定义无框;它重用了 BOX2。
当您说“new 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。
现在清楚了吗?请记住,重载分辨率只是选择该框。框中的内容取决于运行时的对象。
UPDATE : For a more information about this language feature, see the follow-up question here: More about Virtual / new...plus interfaces!
Jason's answer is correct. To sum it up a bit more succinctly.
You have three methods. Call them MA, MB and MC.
You have two "boxes", or, as they're usually called, slots. We'll stick with Jason's nomenclature. Call them BOX1 and BOX2.
"A" defines BOX1.
"B" defines BOX2.
"C" defines no box; it reuses BOX2.
When you say "new A()", BOX1 is filled in with MA.
When you say "new B()", BOX1 is filled in with MA and BOX2 is filled in with MB.
When you say "new C()", BOX1 is filled in with MA and BOX2 is filled in with MC.
Now suppose you have a variable of type A, and a call to the method. Reason like the compiler. The compiler says "are there any boxes on type A that match this name?" Yes, there is one: BOX1. Therefore, the compiler generates a call to the contents of BOX1.
As we've seen, the contents of BOX1 is always MA, so MA is always called no matter whether the variable is actually holding a reference to A, B, or C.
Now suppose you have a variable of type B, and a call to the method. Again, think like the compiler. The compiler says "are there any boxes on type B that matches this name?" Yes, there are TWO boxes that match by name. The compiler says "which of those two is more closely associated with B?" The answer is BOX2, because B declares BOX2. Therefore, the compiler generates a call to BOX2.
This will call MB if the variable contains a B, because in a B, BOX2 contains MB. This will call MC if the variable contains a C, because in a C, BOX2 contains MC.
Is that now clear? Remember, overload resolution just chooses the box. What the contents of the box are depend upon the object at runtime.
你有隐藏警告吗?当我做你所做的事情时,我收到以下警告:
如果你在B类中使用了
override
,你就不会遇到这个问题;这两种情况都会给你“C::mVVirtual”。由于您没有在 B 类中使用override
,因此该方法前面有一个隐式new
。这打破了继承链。您的代码正在调用类型 A 上的方法,并且由于隐式new
,没有继承类重写该方法。所以它必须调用A类的实现。Do you have warnings hidden? When I do what you've done, I get this warning:
If you used
override
in class B, you wouldn't have this problem; both cases would give you "C::mVVirtual". Since you're not usingoverride
in class B, there's an implicitnew
in front of the method. This breaks the inheritance chain. Your code is calling a method on type A, and there are no inheriting classes that override that method due to the implicitnew
. So it has to call class A's implementation.最好的思考方式是,虚拟方法使用对象的实际(或具体)类型来决定执行什么实现,其中非虚拟方法使用您用来访问该方法的变量的声明类型来决定运行哪个...
覆盖意味着您正在编写一个方法将“替换”继承链上方的虚拟或抽象方法(具有相同名称/签名)的实现。
new 当链上有一个具有相同名称/签名的非虚拟方法时使用,您要添加的方法将替换该方法...
区别如下,
但
您的第二次调用会打印 < code>A::mvVirtual 因为该方法 (
mVVirtual
) 实际上是不是虚拟的(尽管它的名称如此),因为它有 new 说明符...所以它根据变量类型决定,即 A。为了解释技术上发生的情况,每个类型都有一个“方法表”,其中包含指向所有变量的指针该类型中的方法。 (它不是具有此表的类型的实例,而是TYPE本身。)每个类型的方法表首先由所有可访问的虚拟方法构成,从
object
(继承链的最上面),到类型本身中声明的虚拟方法,最后。然后,在表示完所有虚拟方法之后,再次从object
中的任何非虚拟方法添加所有非虚拟方法,首先,一直添加到对象中的任何非虚拟方法。输入本身。该表的结构如此,以便所有虚拟方法的偏移量在所有派生类的方法表中都相同,因为编译器可以从声明为其他类型的变量调用这些方法,甚至可以从在基类中声明和实现的其他方法中的代码调用这些方法具体类的类型。当编译器解析虚拟方法调用时,它会转到对象本身类型的方法表(具体类型),而对于非虚拟调用它转到方法表以获取变量的声明类型。因此,如果您调用虚拟方法,即使是从基类型中的代码调用,如果实际的具体类型是从此基类型派生的类型,编译器也会转到该具体类型的方法表。
如果调用非虚拟方法(无论继承更改实际对象类型的程度如何),编译器都会访问 variab;es 声明类型的方法表。该表没有任何来自链中任何派生类型的内容。
Best way to think of it is that virtual methods use the actual (or concrete) type of the object to decide what implementation to execute, where non-Virtual methods use the 'declared type of the variabe you are using to access the method to decide which to run...
Override means you are writing a method that is going to 'replace' the implementation for a virtual or abstract method (with the same name/signature) higher up the inheritance chain.
new is used when there is a non-virtual method up the chain with the same name/signature, which the method you are adding will replace...
The difference is as follows
but
Your second call prints
A::mvVirtual
because that method (mVVirtual
) is actually not virtual, (in spite of it's name), because it has the new specifier... So it decides based on the variable type, which is A.To explain what is going on technically, Every Type has a "method Table" with pointers to all the methods in that type. (It is NOT the instance of a type that has this table, It is the TYPE itself.) Each Types' method table is structured with all acessible virtual methods first, from
object
(furthest up the inheritance chain) at the beginning, to the virtual methods declared in the type itself, at the end. Then, after all the virtual methods are represented, all the non-virtual methods are added, again, from any non-virtual methods inobject
, first, all the way to any non-virtual methods in the Type itself. The table is structured this way so that the offsets for all virtual metods will be identical in all derived classes' method tables, since the compiler may call these methods from variables declared as other types, even from code in other methods declared and implemented in base types of the concrete class.When the compiler resolves a virtual method call it goes to the method table for the Type of the object itself, (the concrete type), whereas for a non-virtual call it goes to the method table for declared type of the variable. So if you call a virtual method, even from code in a base type, if the actual concrete type is Type derived from this base type, the compiler goes to the method table for that concrete type.
If you call a non-virtual method, (no matter how far down the inheritance change the actual object's type might be), The compiler access the method table for the variab;es' declared type. This table has nothing in from any derived types furthur down the chain.
我是这样理解的
A 是基类
B 继承 A 但不覆盖它
C 继承 B 但重写它
由于您声明 A 但初始化 C,它将忽略重写,因为基类是 A 并且 A 永远不会被 B 重写。
This is how I understand it
A is the base class
B inherit A but doesn't override it
C inherit B but does override it
Since you are declaring A but initialize C, it will ignore the override because the base class is A and A never get overridden from B.