关于虚拟/新建/覆盖的混淆

发布于 2024-08-17 11:59:34 字数 910 浏览 3 评论 0原文

我对 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. 虚拟方法?然后,让我们检查 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":

  1. 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!
  2. 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 -> Executes C::mVVirtual!

Where is my "algorithm" wrong? I really am confused by this, and would greatly appreciate some help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

Oo萌小芽oO 2024-08-24 11:59:35

这是您对虚拟方法的看法。类的每个实例都有保存方法的“盒子”。当您将一个方法标记为虚拟时,它会说创建一个新的“盒子”并在其中放入一个方法。当您在派生类中将方法标记为override时,它会保留基类中的“框”,但会在其中放入一个新方法。

因此,这里有一个类 A 和一个名为 mVVirtual 的方法,该方法被标记为 virtual。这表示创建一个名为 mVVirtual 的新“盒子”,并在其中添加一个带有定义的方法

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

然后您就有一个派生类 B 和一个名为 mVVirtual 的方法> 被标记为虚拟。这表示创建一个名为 mVVirtual 的新“盒子”,并在其中放入一个带有定义的方法

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

特别是,从 A 继承的“盒子”被隐藏了!类型为 B 的对象或从 B 派生的类无法看到它。

那么您就有一个派生类 C 和一个名为 mVVirtual 的方法,该方法被标记为 override。这表示采用从 B 继承的名为 mVVirtual 的“盒子”,并在其中添加一个不同的方法和定义。

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

现在,当您拥有时,

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

您将告诉编译器 b1 是一个 B,因此 b1.mVVirtual() 会在“盒子”mVVirtual 中查找并找到具有定义的方法,

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

因为 < code>b1 实际上是一个 C,这就是 C 实例的“盒子”mVVirtual 中的内容。

但是,当您

A a2 = new C(); 
a2.mVVirtual();

告诉编译器 a2A 时,它会在“盒子”中查找并找到

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

编译器无法知道 a2< /code> 实际上是一个 C (您将其输入为 A),因此它不知道 a2 实际上是一个实例派生自隐藏了由 A 定义的“盒子”mVVirtual 的类。它所知道的是 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();

a2A,以便 a2.mVVirtual 在全名 A::mVVirtual< 的“盒子”中查找/code> 并调用该“盒子”中的方法。这就是您

A::mVVirtual

在控制台上看到的原因。

还有另外两个方法注释器。 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 as override 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 named mVVirtual that is marked as virtual. This says make a new "box" named mVVirtual and put a method in it with definition

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

Then you have a derived class B and a method named mVVirtual that is marked as virtual. This says make a new "box" named mVVirtual and put a method in it with definition

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

In particular, the "box" inherited from A is hidden! It can not be seen by objects that are typed as Bs or classes that derive from B.

Then you have a derived class C and a method named mVVirtual that is marked as override. This says take the "box" named mVVirtual inherited from B and put a different method in it with definition

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

Now, when you have

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

you are telling the compiler that b1 is a B so that b1.mVVirtual() looks in the "box" mVVirtual and finds the method with definition

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

because b1 is really a C and that is what is in the "box" mVVirtual for instances of C.

But when you have

A a2 = new C(); 
a2.mVVirtual();

you are telling the compiler that a2 is an A and so it looks in the "box" and finds

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

The compiler can not know that a2 is really a C (you've typed it as an A) so it does not know that a2 is really an instance of a class that is derived from a class that has hidden the "box" mVVirtual defined by A. What it does know is that A has a "box" named mVVirtual and so it emits code to invoke the method in that "box".

So, to try to put this succinctly:

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

defines a class that has a "box" with full name A::mVVirtual but that you can refer to by the name 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"); }
}  

defines a class that has a "box" with full name B::mVVirtual but that you can refer to by the name mVVirtual. Referring to B.mVVirtual will not refer to the "box" with full name A::mVVirtual; that "box" can not be seen by objects that are typed as Bs (or classes that derive from B).

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

defines a class that takes the "box" with full name B::mVVirtual and puts a different method in it.

Then

A a2 = new C(); 
a2.mVVirtual();

says that a2 is an A so that a2.mVVirtual looks in the "box" with full name A::mVVirtual and invokes the method in that "box". This is why you see

A::mVVirtual

on 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" (use virtual if you want to do that).

Sorry for being long-winded but I hope that helps.

萌辣 2024-08-24 11:59:35

更新:有关此语言功能的更多信息,请参阅此处的后续问题:有关虚拟/新...加上界面的更多信息!

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.

熊抱啵儿 2024-08-24 11:59:35

你有隐藏警告吗?当我做你所做的事情时,我收到以下警告:

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

如果你在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:

'ProjectName.ClassName.B.mVVirtual()' hides inherited member 'ProjectName.ClassName.A.mVVirtual()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.

If you used override in class B, you wouldn't have this problem; both cases would give you "C::mVVirtual". Since you're not using override in class B, there's an implicit new 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 implicit new. So it has to call class A's implementation.

世态炎凉 2024-08-24 11:59:35

最好的思考方式是,虚拟方法使用对象的实际(或具体)类型来决定执行什么实现,其中非虚拟方法使用您用来访问该方法的变量的声明类型来决定运行哪个...

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

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. 

您的第二次调用会打印 < 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

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

but

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. 

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 in object, 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.

如梦 2024-08-24 11:59:35

我是这样理解的

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文