C# 为什么在我的派生类中调用方法会调用基类方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17717570/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why does calling a method in my derived class call the base class method?
提问by
Consider this code:
考虑这个代码:
class Program
{
static void Main(string[] args)
{
Person person = new Teacher();
person.ShowInfo();
Console.ReadLine();
}
}
public class Person
{
public void ShowInfo()
{
Console.WriteLine("I am Person");
}
}
public class Teacher : Person
{
public new void ShowInfo()
{
Console.WriteLine("I am Teacher");
}
}
When I run this code, the following is outputted:
当我运行此代码时,输出以下内容:
I am Person
我是人
However, you can see that it is an instance of Teacher
, not of Person
. Why does the code do that?
但是,您可以看到它是 的实例Teacher
,而不是Person
。为什么代码会这样做?
采纳答案by Carsten
There's a difference between new
and virtual
/override
.
new
和virtual
/之间有区别override
。
You can imagine, that a class, when instantiated, is nothing more than a table of pointers, pointing to the actual implementation of its methods. The following image should visualize this pretty well:
您可以想象,一个类在实例化时只不过是一个指针表,指向其方法的实际实现。下图应该可以很好地形象化:
Now there are different ways, a method can be defined. Each behaves different when it is used with inheritance. The standard way always works like the image above illustrates. If you want to change this behavior, you can attach different keywords to your method.
现在有不同的方法,可以定义一个方法。当它与继承一起使用时,每个行为都不同。标准方式始终如上图所示。如果您想更改此行为,您可以将不同的关键字附加到您的方法中。
1. Abstract classes
1. 抽象类
The first one is abstract
. abstract
methods simply point to nowhere:
第一个是abstract
。abstract
方法只是指向无处:
If your class contains abstract members, it also needs to be marked as abstract
, otherwise the compiler will not compile your application. You cannot create instances of abstract
classes, but you can inherit from them and create instances of your inherited classes and access them using the base class definition. In your example this would look like:
如果您的类包含抽象成员,则还需要将其标记为abstract
,否则编译器将不会编译您的应用程序。您不能创建abstract
类的实例,但可以从它们继承并创建继承类的实例,并使用基类定义访问它们。在您的示例中,这看起来像:
public abstract class Person
{
public abstract void ShowInfo();
}
public class Teacher : Person
{
public override void ShowInfo()
{
Console.WriteLine("I am a teacher!");
}
}
public class Student : Person
{
public override void ShowInfo()
{
Console.WriteLine("I am a student!");
}
}
If called, the behavior of ShowInfo
varies, based on the implementation:
如果调用,则行为会ShowInfo
因实现而异:
Person person = new Teacher();
person.ShowInfo(); // Shows 'I am a teacher!'
person = new Student();
person.ShowInfo(); // Shows 'I am a student!'
Both, Student
s and Teacher
s are Person
s, but they behave different when they are asked to prompt information about themselves. However, the way to ask them to prompt their information, is the same: Using the Person
class interface.
这两个,Student
S和Teacher
s为Person
S,但他们的行为不同,当他们被要求对自己的提示信息。但是,让他们提示他们信息的方式是一样的:使用Person
类接口。
So what happens behind the scenes, when you inherit from Person
? When implementing ShowInfo
, the pointer is not pointing to nowhereany longer, it now points to the actual implementation! When creating a Student
instance, it points to Student
s ShowInfo
:
那么,当您从 继承时,幕后会发生什么Person
?在实现时ShowInfo
,指针不再指向任何地方,它现在指向实际的实现!创建Student
实例时,它指向Student
s ShowInfo
:
2. Virtual methods
2. 虚拟方法
The second way is to use virtual
methods. The behavior is the same, except you are providing an optionaldefault implementation in your base class. Classes with virtual
members can be instanciated, however inherited classes can provide different implementations. Here's what your code should actually look like to work:
第二种方法是使用virtual
方法。行为是相同的,除了您在基类中提供可选的默认实现。virtual
可以实例化具有成员的类,但是继承的类可以提供不同的实现。以下是您的代码实际工作时的样子:
public class Person
{
public virtual void ShowInfo()
{
Console.WriteLine("I am a person!");
}
}
public class Teacher : Person
{
public override void ShowInfo()
{
Console.WriteLine("I am a teacher!");
}
}
The key difference is, that the base member Person.ShowInfo
isn't pointing to nowhereany longer. This is also the reason, why you can create instances of Person
(and thus it does not need to be marked as abstract
any longer):
关键的区别在于,基本成员Person.ShowInfo
不再指向任何地方。这也是为什么您可以创建以下实例的原因Person
(因此它不再需要标记为abstract
):
You should notice, that this doesn't look different from the first image for now. This is because the virtual
method is pointing to an implementation "the standard way". Using virtual
, you can tell Persons
, that they can(not must) provide a different implementation for ShowInfo
. If you provide a different implementation (using override
), like I did for the Teacher
above, the image would look the same as for abstract
. Imagine, we did not provide a custom implementation for Student
s:
您应该注意到,目前这看起来与第一张图像没有什么不同。这是因为该virtual
方法指向一个实现“标准方式”。使用virtual
,您可以看出Persons
,他们可以(不是必须)为ShowInfo
. 如果您提供不同的实现(使用override
),就像我在Teacher
上面所做的那样,图像看起来与abstract
. 想象一下,我们没有为Student
s提供自定义实现:
public class Student : Person
{
}
The code would be called like this:
代码将这样调用:
Person person = new Teacher();
person.ShowInfo(); // Shows 'I am a teacher!'
person = new Student();
person.ShowInfo(); // Shows 'I am a person!'
And the image for Student
would look like this:
图像Student
看起来像这样:
3. The magic `new` keyword aka "Shadowing"
3.神奇的`new`关键字又名“Shadowing”
new
is more a hack around this. You can provide methods in generalized classes, that have the same names as methods in the base class/interface. Both point to their own, custom implementation:
new
更像是一个解决这个问题的黑客。您可以在通用类中提供方法,这些方法与基类/接口中的方法具有相同的名称。两者都指向自己的自定义实现:
The implementation looks like the one, you provided. The behavior differs, based on the way you access the method:
实现看起来像你提供的那个。根据您访问方法的方式,行为会有所不同:
Teacher teacher = new Teacher();
Person person = (Person)teacher;
teacher.ShowInfo(); // Prints 'I am a teacher!'
person.ShowInfo(); // Prints 'I am a person!'
This behavior can be wanted, but in your case it is misleading.
这种行为可能是需要的,但在您的情况下,它具有误导性。
I hope this makes things clearer to understand for you!
我希望这能让你更清楚地理解!
回答by Cole Johnson
The compiler does this because it doesn't know that it is a Teacher
. All it knows is that it is a Person
or something derived from it. So all it can do is call the Person.ShowInfo()
method.
编译器这样做是因为它不知道它是一个Teacher
. 它只知道它是一个Person
或从它派生的东西。所以它所能做的就是调用Person.ShowInfo()
方法。
回答by Adil
You have to make the method virtualand you have to override the function in the child class, in order to call the method of class object you put in parent class reference.
您必须使方法成为虚拟方法,并且必须覆盖子类中的函数,以便调用您放入父类引用中的类对象的方法。
public class Person
{
public virtual void ShowInfo()
{
Console.WriteLine("I am Person");
}
}
public class Teacher : Person
{
public override void ShowInfo()
{
Console.WriteLine("I am Teacher");
}
}
Virtual Methods
虚拟方法
When a virtual method is invoked, the run-time type of the object is checked for an overriding member. The overriding member in the most derived class is called, which might be the original member, if no derived class has overridden the member. By default, methods are non-virtual. You cannot override a non-virtual method. You cannot use the virtual modifier with the static, abstract, private or override modifiers, MSDN.
当调用虚拟方法时,将检查对象的运行时类型是否有覆盖成员。调用最派生类中的覆盖成员,如果没有派生类覆盖该成员,则该成员可能是原始成员。默认情况下,方法是非虚拟的。您不能覆盖非虚拟方法。您不能将 virtual 修饰符与 static、abstract、private 或 override 修饰符MSDN 一起使用。
Using New for Shadowing
使用 New 进行阴影处理
You are using new key word instead of override, this is what new does
您正在使用 new 关键字而不是覆盖,这就是 new 的作用
If the method in the derived class is not preceded by new or override keywords, the compiler will issue a warning and the method will behave as if the new keyword were present.
If the method in the derived class is preceded with the new keyword, the method is defined as being independent of the method in the base class, This MSDN articleexplains it very well.
如果派生类中的方法前面没有 new 或 override 关键字,编译器将发出警告,并且该方法的行为就像存在 new 关键字一样。
如果派生类中的方法前面有new关键字,则该方法被定义为独立于基类中的方法,这篇MSDN文章很好地解释了。
Early binding VS Late binding
早期绑定 VS 晚期绑定
We have early binding at compile time for normal method (not virtual) which is the currrent case the compiler will bind call to method of base class that is method of reference type (base class) instead of the object is held in the referece of base class i.e. derived class object. This is because ShowInfo
is not a virtual method. The late binding is performed at runtime for (virtual / overridden method) using virtual method table(vtable).
我们在编译时对普通方法(非虚拟)进行了早期绑定,这是当前情况下编译器会将调用绑定到基类的方法,即引用类型(基类)的方法,而不是将对象保存在基类的引用中类即派生类对象。这是因为ShowInfo
不是虚方法。使用虚拟方法表(vtable)在运行时为(虚拟/覆盖方法)执行后期绑定。
For a normal function, the compiler can work out the numeric location of it in memory. Then it when the function is called it can generate an instruction to call the function at this address.
For an object that has any virtual methods, the compiler will generate a v-table. This is essentially an array that contains the addresses of the virtual methods. Every object that has a virtual method will contain a hidden member generated by the compiler that is the address of the v-table. When a virtual function is called, the compiler will work out what the position is of the appropriate method in the v-table. It will then generate code to look in the objects v-table and call the virtual method at this position, Reference.
对于普通函数,编译器可以计算出它在内存中的数字位置。然后当函数被调用时,它可以生成一个指令来调用这个地址的函数。
对于具有任何虚方法的对象,编译器将生成一个 v-table。这本质上是一个包含虚拟方法地址的数组。每个具有虚方法的对象都将包含一个由编译器生成的隐藏成员,即 v-table 的地址。当一个虚函数被调用时,编译器会计算出相应方法在 v-table 中的位置。然后它将生成代码以查看对象 v-table 并在此位置调用虚拟方法Reference。
回答by Adil
Subtype polymorphism in C# uses explicit virtuality, similar to C++ but unlike Java. This means that you explicitly have to mark methods as overridable (i.e. virtual
). In C# you also have to explicitly mark overriding methods as overriding (i.e. override
) to prevent typos.
C# 中的子类型多态使用显式虚拟性,类似于 C++ 但与 Java 不同。这意味着您必须明确地将方法标记为可覆盖(即virtual
)。在 C# 中,您还必须将覆盖方法显式标记为覆盖(即override
)以防止输入错误。
public class Person
{
public virtual void ShowInfo()
{
Console.WriteLine("I am Person");
}
}
public class Teacher : Person
{
public override void ShowInfo()
{
Console.WriteLine("I am Teacher");
}
}
In the code in your question, you use new
, which does shadowinginstead of overriding. Shadowing merely affects the compile-time semantics rather than the runtime semantics, hence the unintended output.
在您问题的代码中,您使用new
, 进行阴影而不是覆盖。阴影仅影响编译时语义而不是运行时语义,因此会产生意外输出。
回答by Jay Patel
You need to make it virtual
and then override that function in Teacher
. As you're inheriting and using the base pointer to refer to a derived class, you need to override it using virtual
. new
is for hiding the base
class method on a derived class reference and not a base
class reference.
您需要创建它virtual
,然后在Teacher
. 当您继承并使用基指针来引用派生类时,您需要使用virtual
. new
用于隐藏base
派生类引用上的类方法,而不是base
类引用。
回答by Emil
Please read about polymorphism in C#: Polymorphism (C# Programming Guide)
请阅读 C# 中的多态性:多态性(C# 编程指南)
This is an example from there:
这是那里的一个例子:
When the new keyword is used, the new class members are called instead of the base class members that have been replaced. Those base class members are called hidden members. Hidden class members can still be called if an instance of the derived class is cast to an instance of the base class. For example:
使用 new 关键字时,将调用新的类成员,而不是已替换的基类成员。这些基类成员称为隐藏成员。如果派生类的实例被强制转换为基类的实例,则仍然可以调用隐藏的类成员。例如:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
回答by Adrian Salazar
C# is different to java in the parent/child class override behavior. By default in Java all methods are virtual, so the behavior that you want is supported out of the box.
C# 在父/子类覆盖行为上与 java 不同。默认情况下,Java 中的所有方法都是虚拟的,因此开箱即用地支持您想要的行为。
In C# you have to mark a method as virtual in the base class, then you will get what you want.
在 C# 中,您必须在基类中将方法标记为虚拟方法,然后您将获得所需的内容。
回答by Vi Ki
The type of variable 'teacher' here is typeof(Person)
and this type does not know anything about Teacher class and does not try to look for any methods in derived types. To call method of Teacher class you should cast your variable: (person as Teacher).ShowInfo()
.
这里的变量 'teacher' 的typeof(Person)
类型是,这个类型对教师类一无所知,也不会尝试在派生类型中寻找任何方法。要调用教师类的方法,你应该投您的变量:(person as Teacher).ShowInfo()
。
To call specific method based on value type you should use keyword 'virtual' in your base class and override virtual methods in derived classes. This approach allows to implement derived classes with or without overriding of virtual methods. Methods of base class will be called for types without overided virtuals.
要根据值类型调用特定方法,您应该在基类中使用关键字“virtual”并覆盖派生类中的虚拟方法。这种方法允许在覆盖或不覆盖虚拟方法的情况下实现派生类。对于没有覆盖虚拟的类型,将调用基类的方法。
public class Program
{
private static void Main(string[] args)
{
Person teacher = new Teacher();
teacher.ShowInfo();
Person incognito = new IncognitoPerson ();
incognito.ShowInfo();
Console.ReadLine();
}
}
public class Person
{
public virtual void ShowInfo()
{
Console.WriteLine("I am Person");
}
}
public class Teacher : Person
{
public override void ShowInfo()
{
Console.WriteLine("I am Teacher");
}
}
public class IncognitoPerson : Person
{
}
回答by KeithS
I want to build off of Achratt's answer. For completeness, the difference is that the OP is expecting the new
keyword in the derived class's method to override the base class method. What it actually does is hidethe base class method.
我想以Achratt 的回答为基础。为了完整起见,不同之处在于 OP 期望new
派生类方法中的关键字覆盖基类方法。它实际上做的是隐藏基类方法。
In C#, as another answer mentioned, traditional method overriding must be explicit; the base class method must be marked as virtual
and the derived class must specifically override
the base class method. If this is done, then it doesn't matter whether the object is treated as being an instance of the base class or derived class; the derived method is found and called. This is done in a similar fashion as in C++; a method marked "virtual" or "override", when compiled, is resolved "late" (at runtime) by determining the referenced object's actual type, and traversing the object hierarchy downward along the tree from the variable type to the actual object type, to find the most derived implementation of the method defined by the variable type.
在 C# 中,正如另一个答案所提到的,传统方法覆盖必须是显式的;基类方法必须标记为virtual
,而派生类必须专门标记override
基类方法。如果这样做了,那么对象是被视为基类还是派生类的实例都没有关系;找到并调用派生方法。这是以与 C++ 类似的方式完成的;标记为“virtual”或“override”的方法在编译时通过确定引用对象的实际类型并沿树从变量类型到实际对象类型向下遍历对象层次结构来“延迟”(在运行时)解析,找到由变量类型定义的方法的最派生实现。
This differs from Java, which allows "implicit overrides"; for instance methods (non-static), simply defining a method of the same signature (name and number/type of parameters) will cause the subclass to override the superclass.
这与 Java 不同,Java 允许“隐式覆盖”;对于实例方法(非静态),简单地定义一个具有相同签名(名称和参数的数量/类型)的方法将导致子类覆盖超类。
Because it's often useful to extend or override the functionality of a non-virtual method you do not control, C# also includes the new
contextual keyword. The new
keyword "hides" the parent method instead of overriding it. Any inheritable method can be hidden whether it's virtual or not; this allows you, the developer, to leverage the members you want to inherit from a parent, without having to work around the ones you don't, while still allowing you to present the same "interface" to consumers of your code.
因为扩展或覆盖您无法控制的非虚拟方法的功能通常很有用,所以 C# 还包括new
contextual 关键字。所述new
关键字“隐藏”父方法,而不是覆盖它。任何可继承的方法都可以隐藏,无论它是否是虚拟的;这使您,开发人员,可以利用您想要从父级继承的成员,而不必处理您不需要的成员,同时仍然允许您向代码的使用者呈现相同的“界面”。
Hiding works similarly to overriding from the perspective of a person using your object at or below the level of inheritance at which the hiding method is defined. From the question's example, a coder creating a Teacher and storing that reference in a variable of the Teacher type will see the behavior of the ShowInfo() implementation from Teacher, which hides the one from Person. However, someone working with your object in a collection of Person records (as you are) will see the behavior of the Person implementation of ShowInfo(); because Teacher's method doesn't override its parent (which would also require Person.ShowInfo() to be virtual), code working at the Person level of abstraction won't find the Teacher implementation and won't use it.
在定义隐藏方法的继承级别或以下,从使用对象的人的角度来看,隐藏的工作方式类似于覆盖。从问题的示例中,创建教师并将该引用存储在教师类型的变量中的编码员将看到教师的 ShowInfo() 实现的行为,这对人员隐藏了该实现。但是,在 Person 记录集合中使用您的对象的人(就像您一样)将看到 ShowInfo() 的 Person 实现的行为;因为Teacher 的方法不会覆盖其父类(这也需要Person.ShowInfo() 是虚拟的),在Person 抽象级别工作的代码将找不到Teacher 实现并且不会使用它。
In addition, not only will the new
keyword do this explicitly, C# allows implicit method hiding; simply defining a method with the same signature as a parent class method, without override
or new
, will hide it (though it will produce a compiler warning or a complaint from certain refactoring assistants like ReSharper or CodeRush). This is the compromise C#'s designers came up with between C++'s explicit overrides vs Java's implicit ones, and while it's elegant, it doesn't always produce the behavior you would expect if you come from a background in either of the older languages.
此外,new
关键字不仅会显式执行此操作,C# 还允许隐式方法隐藏;简单地定义一个与父类方法具有相同签名的方法,没有override
或new
,将隐藏它(尽管它会产生编译器警告或来自某些重构助手(如 ReSharper 或 CodeRush)的投诉)。这是 C# 的设计者在 C++ 的显式覆盖与 Java 的隐式覆盖之间提出的折衷方案,虽然它很优雅,但如果您来自任一旧语言的背景,它并不总是产生您期望的行为。
Here's the new stuff:This gets complex when you combine the two keywords in a long inheritance chain. Consider the following:
这是新内容:当您将两个关键字组合在一个长继承链中时,这会变得复杂。考虑以下:
class Foo { public virtual void DoFoo() { Console.WriteLine("Foo"); } }
class Bar:Foo { public override sealed void DoFoo() { Console.WriteLine("Bar"); } }
class Baz:Bar { public virtual void DoFoo() { Console.WriteLine("Baz"); } }
class Bai:Baz { public override void DoFoo() { Console.WriteLine("Bai"); } }
class Bat:Bai { public new void DoFoo() { Console.WriteLine("Bat"); } }
class Bak:Bat { }
Foo foo = new Foo();
Bar bar = new Bar();
Baz baz = new Baz();
Bai bai = new Bai();
Bat bat = new Bat();
foo.DoFoo();
bar.DoFoo();
baz.DoFoo();
bai.DoFoo();
bat.DoFoo();
Console.WriteLine("---");
Foo foo2 = bar;
Bar bar2 = baz;
Baz baz2 = bai;
Bai bai2 = bat;
Bat bat2 = new Bak();
foo2.DoFoo();
bar2.DoFoo();
baz2.DoFoo();
bai2.DoFoo();
Console.WriteLine("---");
Foo foo3 = bak;
Bar bar3 = bak;
Baz baz3 = bak;
Bai bai3 = bak;
Bat bat3 = bak;
foo3.DoFoo();
bar3.DoFoo();
baz3.DoFoo();
bai3.DoFoo();
bat3.DoFoo();
Output:
输出:
Foo
Bar
Baz
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat
---
Bar
Bar
Bai
Bai
Bat
The first set of five is all to be expected; because each level has an implementation, and is referenced as an object of the same type as was instantiated, the runtime resolves each call to the inheritance level referenced by the variable type.
第一组五个是可以预料的;因为每个级别都有一个实现,并且被引用为与实例化的类型相同的对象,所以运行时将每个调用解析为变量类型引用的继承级别。
The second set of five is the result of assigning each instance to a variable of the immediate parent type. Now, some differences in behavior shake out; foo2
, which is actually a Bar
cast as a Foo
, will still find the more derived method of the actual object type Bar. bar2
is a Baz
, but unlike with foo2
, because Baz doesn't explicitly override Bar's implementation (it can't; Bar sealed
it), it's not seen by the runtime when looking "top-down", so Bar's implementation is called instead. Notice that Baz doesn't have to use the new
keyword; you'll get a compiler warning if you omit the keyword, but the implied behavior in C# is to hide the parent method. baz2
is a Bai
, which overrides Baz
's new
implementation, so its behavior is similar to foo2
's; the actual object type's implementation in Bai is called. bai2
is a Bat
, which again hides its parent Bai
's method implementation, and it behaves the same as bar2
even though Bai's implementation isn't sealed, so theoretically Bat could have overridden instead of hidden the method. Finally, bat2
is a Bak
, which has no overriding implementation of either kind, and simply uses that of its parent.
第二组五个是将每个实例分配给直接父类型的变量的结果。现在,行为上的一些差异正在消失;foo2
,它实际上是一个Bar
cast as a Foo
,仍然会找到实际对象类型 Bar 的更多派生方法。bar2
是 a Baz
,但与 with 不同foo2
,因为 Baz 没有显式覆盖 Bar 的实现(它不能;Bar sealed
it),所以在“自上而下”查看时它不会被运行时看到,因此调用的是 Bar 的实现。请注意,Baz 不必使用new
关键字;如果省略关键字,您将收到编译器警告,但 C# 中的隐含行为是隐藏父方法。baz2
是 a Bai
,它覆盖了Baz
'snew
实现,所以它的行为类似于foo2
's; 调用 Bai 中实际对象类型的实现。bai2
是 a Bat
,它再次隐藏了其父Bai
的方法实现,它的行为与bar2
即使 Bai 的实现没有密封一样,所以理论上 Bat 可以覆盖而不是隐藏该方法。最后,bat2
是 a Bak
,它没有任何一种的覆盖实现,只是使用其父级的实现。
The third set of five illustrates the full top-down resolution behavior. Everything is actually referencing an instance of the most derived class in the chain, Bak
, but resolution at every level of variable type is performed by starting at that level of the inheritance chain and drilling down to the most derived explicitoverride of the method, which are those in Bar
, Bai
, and Bat
. Method hiding thus "breaks" the overriding inheritance chain; you have to be working with the object at or below the level of inheritance that hides the method in order for the hiding method to be used. Otherwise, the hiddenmethod is "uncovered" and used instead.
第三组五个说明了完整的自上而下分辨率行为。一切实际上都在引用链中派生最多的类的实例Bak
,但是变量类型的每个级别的解析都是通过从继承链的该级别开始并深入到方法的派生最多的显式覆盖来执行的,它们是那些Bar
,Bai
和Bat
。方法隐藏因此“打破”了覆盖的继承链;您必须在隐藏方法的继承级别或以下使用对象,以便使用隐藏方法。否则,隐藏的方法将被“发现”并改为使用。
回答by Shruti Kapoor
Just wanted to give a brief answer -
只想简单回答一下——
You should use virtual
and override
in classes that could be overridden. Use virtual
for methods that can be overriden by child classes and use override
for methods that should override such virtual
methods.
您应该在可能被覆盖的类中使用virtual
和override
。使用virtual
了可以通过子类和使用可重写的方法override
对于应覆盖这些方法virtual
的方法。