简体   繁体   English

为什么在我的派生类中调用方法会调用基类方法?

[英]Why does calling a method in my derived class call the base class method?

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 .但是,您可以看到它是Teacher的实例,而不是Person的实例。 Why does the code do that?为什么代码会这样做?

There's a difference between new and virtual / override . newvirtual / 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 abstract methods simply point to nowhere: abstract方法只是指向无处:

抽象类的插图

If your class contains abstract members, it also needs to be marked as abstract , otherwise the compiler will not compile your application. 如果您的类包含抽象成员,则还需要将其标记为abstract ,否则编译器将不会编译您的应用程序。 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. 您不能创建abstract类的实例,但可以从它们继承并创建继承的类的实例,并使用基类定义对其进行访问。 In your example this would look like: 在您的示例中,它看起来像:

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. StudentTeacher都是Person ,但是当要求他们提示有关自己的信息时,它们的行为会有所不同。 However, the way to ask them to prompt their information, is the same: Using the Person class interface. 但是,要求他们提示信息的方法是相同的:使用Person类接口。

So what happens behind the scenes, when you inherit from Person ? 那么,当您继承自Person时,幕后会发生什么? When implementing ShowInfo , the pointer is not pointing to nowhere any longer, it now points to the actual implementation! 当实现ShowInfo ,指针不再指向无处 ,而是指向实际的实现! When creating a Student instance, it points to Student s ShowInfo : 创建Student实例时,它指向StudentShowInfo

继承方法的说明

2. Virtual methods 2.虚方法

The second way is to use virtual methods. 第二种方法是使用virtual方法。 The behavior is the same, except you are providing an optional default implementation in your base class. 除了在基类中提供可选的默认实现之外,其行为是相同的。 Classes with virtual members can be instanciated, however inherited classes can provide different implementations. 可以实例化具有virtual成员的类,但是继承的类可以提供不同的实现。 Here's what your code should actually look like to work: 这是您的代码实际上应该看起来像的样子:

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 nowhere any longer. 关键区别在于,基本成员Person.ShowInfo不再指向无处 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实例的原因(因此不再需要将其标记为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 ". 这是因为virtual方法指向实现“ 标准方式 ”。 Using virtual , you can tell Persons , that they can (not must ) provide a different implementation for ShowInfo . 使用virtual ,您可以告诉Persons他们可以 (不是必须 )为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 . 如果您提供不同的实现(使用override ),就像我对上面的Teacher所做的那样,则图像将与abstract相同。 Imagine, we did not provide a custom implementation for Student s: 想象一下,我们没有为Student提供定制的实现:

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. new的东西更多地围绕着这个。 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: 两者都指向自己的自定义实现:

使用新关键字的“方式”插图

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! 我希望这会使您更容易理解!

Subtype polymorphism in C# uses explicit virtuality, similar to C++ but unlike Java. C#中的子类型多态性使用显式虚拟性,类似于C ++,但不同于Java。 This means that you explicitly have to mark methods as overridable (ie virtual ). 这意味着您必须明确地将方法标记为可重写(即virtual )。 In C# you also have to explicitly mark overriding methods as overriding (ie override ) to prevent typos. 在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 shadowing instead of overriding. 在您问题的代码中,您使用new ,它执行阴影而不是覆盖。 Shadowing merely affects the compile-time semantics rather than the runtime semantics, hence the unintended output. 阴影只会影响编译时的语义,而不会影响运行时的语义,因此会产生意想不到的输出。

You have to make the method virtual and 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 . 您不能将虚拟修饰符与静态,抽象,私有或替代修饰符MSDN一起使用

Using New for Shadowing 使用新的阴影

You are using new key word instead of override, this is what new does 您使用的是新关键字而不是替代关键字,这就是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. 如果派生类中的方法前面没有new或override关键字,则编译器将发出警告,并且该方法的行为就像存在new关键字一样。

  • 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 article explains it very well. 如果派生类中方法前面带有new关键字,则该方法被定义为独立于基类中的方法 ,此MSDN文章对此进行了很好的解释。

Early binding VS Late binding 早期绑定与后期绑定

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 ie derived class object . 我们在编译时就对普通方法(非虚拟方法)进行了早期绑定,这是当前情况, 编译器会将调用绑定到基类的方法上,该基类是引用类型的方法(基类),而不是将对象保存在基类的引用中类,即派生类对象 This is because ShowInfo is not a virtual method. 这是因为ShowInfo不是虚拟方法。 The late binding is performed at runtime for (virtual / overridden method) using virtual method table (vtable). 后期绑定是在运行时使用虚拟方法表 (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. 对于具有任何虚拟方法的对象,编译器将生成一个v表。 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. 每个具有虚拟方法的对象都将包含由编译器生成的隐藏成员,即v表的地址。 When a virtual function is called, the compiler will work out what the position is of the appropriate method in the v-table. 调用虚拟函数时,编译器将确定v表中适当方法的位置。 It will then generate code to look in the objects v-table and call the virtual method at this position, Reference . 然后,它将生成代码以查看对象v表并在此位置调​​用虚拟方法Reference

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. 为了完整起见,不同之处在于OP希望派生类的方法中的new关键字覆盖基类方法。 What it actually does is hide the base class method. 它实际上所做的是隐藏基类方法。

In C#, as another answer mentioned, traditional method overriding must be explicit; 在C#中,作为另一个答案,传统方法的覆盖必须是明确的。 the base class method must be marked as virtual and the derived class must specifically override the base class method. 必须将基类方法标记为virtual ,并且派生类必须专门override基类方法。 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++; 这是用与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. 标记为“虚拟”或“覆盖”的方法在编译时通过确定引用对象的实际类型,并沿树从变量类型向下遍历到对象实际类型,从而在运行时“延迟”解析,查找由变量类型定义的方法的最派生实现。

This differs from Java, which allows "implicit overrides"; 这不同于Java,后者允许“隐式覆盖”。 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. 对于实例方法(非静态),仅定义具有相同签名(名称和参数的数量/类型)的方法将导致子类覆盖超类。

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. 由于扩展或覆盖不受您控制的非虚拟方法的功能通常很有用,因此C#还包括new上下文关键字。 The new keyword "hides" the parent method instead of overriding it. new关键字“隐藏”父方法,而不是覆盖它。 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. 这样,开发人员可以利用您想要从父级继承的成员,而不必解决那些您不需要的成员,同时仍然允许您向​​代码使用者提供相同的“接口”。

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. 从问题的示例中,一个编码器创建了一个Teacher并将该引用存储在Teacher类型的变量中,这将对Teacher看到ShowInfo()实现的行为,而该行为对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(); 但是,某人在一个Person记录集合中使用您的对象(就像您一样)将看到ShowInfo()的Person实现的行为。 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. 因为Teacher的方法不会覆盖其父类(这也要求Person.ShowInfo()是虚拟的),所以在Person的抽象级别工作的代码不会找到Teacher的实现,也不会使用它。

In addition, not only will the new keyword do this explicitly, C# allows implicit method hiding; 此外, new关键字不仅会显式地执行此操作,而且C#允许隐式方法隐藏。 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). 只需定义一个与父类方法具有相同签名的方法,而无需overridenew将其隐藏(尽管它会产生编译器警告或某些重构助手(如ReSharper或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. 这是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. foo2实际上是转换为FooBar ,仍然会找到实际对象类型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. bar2是一个Baz ,但是与foo2不同,因为Baz不会显式覆盖Bar的实现(它不能; Bar对其进行了sealed ),因此在运行时查看“自上而下”时不会看到它,因此调用Bar的实现。 Notice that Baz doesn't have to use the new keyword; 注意,Baz不必使用new关键字。 you'll get a compiler warning if you omit the keyword, but the implied behavior in C# is to hide the parent method. 如果省略关键字,则会收到编译器警告,但是C#中的隐含行为是隐藏父方法。 baz2 is a Bai , which overrides Baz 's new implementation, so its behavior is similar to foo2 's; baz2Bai ,它重写了Baznew实现,因此其行为类似于foo2的行为。 the actual object type's implementation in Bai is called. 称为Bai的实际对象类型的实现。 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. bai2是一个Bat ,它再次隐藏了其父Bai的方法实现,即使Bai的实现未密封,它的行为也与bar2相同,因此从理论上说Bat可以重写而不是隐藏该方法。 Finally, bat2 is a Bak , which has no overriding implementation of either kind, and simply uses that of its parent. 最后, bat2是一个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 explicit override of the method, which are those in Bar , Bai , and Bat . 实际上,所有内容都引用链中最派生类的实例Bak ,但是通过从继承链的该级别开始并向下钻取到该方法的最派生的显式覆盖,可以执行每个变量类型级别的解析。是BarBaiBat中的那些。 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 hidden method is "uncovered" and used instead. 否则, 隐藏的方法将被“发现”并使用。

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. 使用new关键字时,将调用新的类成员,而不是已替换的基类成员。 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: 例如:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

You need to make it virtual and then override that function in Teacher . 您需要将其virtual ,然后在Teacher覆盖该功能。 As you're inheriting and using the base pointer to refer to a derived class, you need to override it using virtual . 在继承并使用基指针引用派生类时,需要使用virtual重写它。 new is for hiding the base class method on a derived class reference and not a base class reference. new用于在派生类引用而不是base类引用上隐藏base类方法。

I would like to add a couple of more examples to expand on the info around this. 我想添加更多示例,以扩展有关此方面的信息。 Hope this helps too: 希望这也会有所帮助:

Here is a code sample that clears the air around what happens when a derived type is assigned to a base type. 这是一个代码示例,该示例清除了将派生类型分配给基本类型时发生的事情。 Which methods are available and the difference between overridden and hidden methods in this context. 在这种情况下,哪些方法可用以及覆盖方法和隐藏方法之间的区别。

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.foo();        // A.foo()
            a.foo2();       // A.foo2()

            a = new B();    
            a.foo();        // B.foo()
            a.foo2();       // A.foo2()
            //a.novel() is not available here

            a = new C();
            a.foo();        // C.foo()
            a.foo2();       // A.foo2()

            B b1 = (B)a;    
            b1.foo();       // C.foo()
            b1.foo2();      // B.foo2()
            b1.novel();     // B.novel()

            Console.ReadLine();
        }
    }


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

        public void foo2()
        {
            Console.WriteLine("A.foo2()");
        }
    }

    class B : A
    {
        public override void foo()
        {
            // This is an override
            Console.WriteLine("B.foo()");
        }

        public new void foo2()      // Using the 'new' keyword doesn't make a difference
        {
            Console.WriteLine("B.foo2()");
        }

        public void novel()
        {
            Console.WriteLine("B.novel()");
        }
    }

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

        public new void foo2()
        {
            Console.WriteLine("C.foo2()");
        }
    }
}

Another little anomaly is that, for the following line of code: 对于以下代码行,另一个小异常是:

A a = new B();    
a.foo(); 

The VS compiler (intellisense) would show a.foo() as A.foo(). VS编译器(intellisense)将a.foo()显示为A.foo()。

Hence, it is clear that when a more derived type is assigned to a base type, the 'base type' variable acts as the base type until a method that is overridden in a derived type is referenced. 因此,很明显,当将更多派生类型分配给基本类型时,“基本类型”变量将充当基本类型,直到引用了派生类型中覆盖的方法为止。 This may become a little counter-intuitive with hidden methods or methods with the same name (but not overridden) between the parent and child types. 对于隐藏的方法或在父类型和子类型之间具有相同名称(但不被覆盖)的方法,这可能会有点违反直觉。

This code sample should help delineate these caveats! 此代码示例应有助于描述这些警告!

C# is different to java in the parent/child class override behavior. C#与Java在父/子类重写行为方面有所不同。 By default in Java all methods are virtual, so the behavior that you want is supported out of the box. 在Java中,默认情况下所有方法都是虚拟的,因此开箱即用地支持所需的行为。

In C# you have to mark a method as virtual in the base class, then you will get what you want. 在C#中,您必须在基类中将方法标记为虚方法,然后您将获得所需的内容。

The new keyword tell that the method in the current class will only work if you have an instance of the class Teacher stored in a variable of type Teacher. new关键字表明,仅当您将教师类的实例存储在类型为Teacher的变量中时,当前类中的方法才有效。 Or you can trigger it using castings: ((Teacher)Person).ShowInfo() 或者您可以使用强制转换触发它:((Teacher)Person).ShowInfo()

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. 这里的变量“老师”的类型是typeof(Person) ,这种类型对Teacher类不了解,也不试图在派生类型中查找任何方法。 To call method of Teacher class you should cast your variable: (person as Teacher).ShowInfo() . 要调用Teacher类的方法,您应该(person as Teacher).ShowInfo()变量:( (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. 要基于值类型调用特定的方法,应在基类中使用关键字“ virtual”,并在派生类中覆盖虚拟方法。 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. 对于没有过多虚数的类型,将调用基类的方法。

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
{

}

Might be too late... But the question is simple and the answer should have the same level of complexity. 可能为时已晚...但是问题很简单,答案应该具有相同的复杂度。

In your code variable person doesn't know anything about Teacher.ShowInfo(). 在您的代码变量中,人对Teacher.ShowInfo()一无所知。 There is no way to call last method from base class reference, because it's not virtual. 无法从基类引用中调用last方法,因为它不是虚拟的。

There is useful approach to inheritance - try to imagine what do you want to say with your code hierarchy. 有一种有用的继承方法-尝试想象您想对代码层次结构说些什么。 Also try to imagine what does one or another tool says about itself. 还要尝试想象一个或另一种工具对自己的看法。 Eg if you add virtual function into a base class you suppose: 1. it can have default implementation; 例如,如果您将虚函数添加到基类中,您应该:1.它可以具有默认实现; 2. it might be reimplemented in derived class. 2.可以在派生类中重新实现。 If you add abstract function it means only one thing - subclass must create an implementation. 如果添加抽象函数,则仅意味着一件事-子类必须创建一个实现。 But in case you have plain function - you do not expect anyone to change its implementation. 但是,如果您具有普通功能-您不要期望任何人更改其实现。

The compiler does this because it doesn't know that it is a Teacher . 编译器之所以这样做是因为它不知道它是Teacher All it knows is that it is a Person or something derived from it. 它所知道的只是它是一个Person或从它派生而来的东西。 So all it can do is call the Person.ShowInfo() method. 因此,它所能做的就是调用Person.ShowInfo()方法。

Just wanted to give a brief answer - 只是想给出一个简短的答案-

You should use virtual and override in classes that could be overridden. 您应该在可能被覆盖的类中使用virtualoverride Use virtual for methods that can be overriden by child classes and use override for methods that should override such virtual methods. 使用virtual表示可以被子类override的方法,并将override为应该覆盖此类virtual方法的方法。

I wrote the same code as u have mentioned above in java except some changes and it worked fine as excepted. 除了一些更改外,我用Java编写了与您上面提到的代码相同的代码,但除此以外,它均能正常工作。 Method of the base class is overridden and so output displayed is "I am Teacher". 基类的方法被覆盖,因此显示的输出为“我是老师”。

Reason: As we are creating a reference of the base class (which is capable of having referring instance of the derived class) which is actually containing the reference of the derived class. 原因:当我们创建基类的引用(该类能够具有派生类的引用实例)时,该基类实际上包含派生类的引用。 And as we know that the instance always look upon its methods first if it finds it there it executes it, and if it doesn't find the definition there it goes up in the hierarchy. 而且我们知道,实例总是先在其方法中查找其方法,然后再执行它,而在实例中找不到定义的情况下,它会在层次结构中向上移动。

public class inheritance{

    public static void main(String[] args){

        Person person = new Teacher();
        person.ShowInfo();
    }
}

class Person{

    public void ShowInfo(){
        System.out.println("I am Person");
    }
}

class Teacher extends Person{

    public void ShowInfo(){
        System.out.println("I am Teacher");
    }
}

Building on Keith S.'s excellent demonstration and every one else's quality answers and for the sake of uber completeness lets go ahead and toss explicit interface implementations in to demonstrate how that works. 基于Keith S.的出色演示和其他每个人的质量回答,并且为了更加完整,我们继续前进,并抛弃显式的接口实现以演示其工作原理。 Consider the below: 考虑以下内容:

namespace LinqConsoleApp { 名称空间LinqConsoleApp {

class Program
{

    static void Main(string[] args)
    {


        Person person = new Teacher();
        Console.Write(GetMemberName(() => person) + ": ");
        person.ShowInfo();

        Teacher teacher = new Teacher();
        Console.Write(GetMemberName(() => teacher) + ": ");
        teacher.ShowInfo();

        IPerson person1 = new Teacher();
        Console.Write(GetMemberName(() => person1) + ": ");
        person1.ShowInfo();

        IPerson person2 = (IPerson)teacher;
        Console.Write(GetMemberName(() => person2) + ": ");
        person2.ShowInfo();

        Teacher teacher1 = (Teacher)person1;
        Console.Write(GetMemberName(() => teacher1) + ": ");
        teacher1.ShowInfo();

        Person person4 = new Person();
        Console.Write(GetMemberName(() => person4) + ": ");
        person4.ShowInfo();

        IPerson person3 = new Person();
        Console.Write(GetMemberName(() => person3) + ": ");
        person3.ShowInfo();

        Console.WriteLine();

        Console.ReadLine();

    }

    private static string GetMemberName<T>(Expression<Func<T>> memberExpression)
    {
        MemberExpression expressionBody = (MemberExpression)memberExpression.Body;
        return expressionBody.Member.Name;
    }

}
interface IPerson
{
    void ShowInfo();
}
public class Person : IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Person == " + this.GetType());
    }
    void IPerson.ShowInfo()
    {
        Console.WriteLine("I am interface Person == " + this.GetType());
    }
}
public class Teacher : Person, IPerson
{
    public void ShowInfo()
    {
        Console.WriteLine("I am Teacher == " + this.GetType());
    }
}

} }

Here's the output: 这是输出:

person: I am Person == LinqConsoleApp.Teacher 人员:我是人员== LinqConsoleApp.Teacher

teacher: I am Teacher == LinqConsoleApp.Teacher 老师:我是老师== LinqConsoleApp.Teacher

person1: I am Teacher == LinqConsoleApp.Teacher person1:我是老师== LinqConsoleApp.Teacher

person2: I am Teacher == LinqConsoleApp.Teacher person2:我是老师== LinqConsoleApp.Teacher

teacher1: I am Teacher == LinqConsoleApp.Teacher 老师1:我是老师== LinqConsoleApp.Teacher

person4: I am Person == LinqConsoleApp.Person person4:我是Person == LinqConsoleApp.Person

person3: I am interface Person == LinqConsoleApp.Person person3:我是接口Person == LinqConsoleApp.Person

Two things to note: 需要注意的两件事:
The Teacher.ShowInfo() method omits the the new keyword. Teacher.ShowInfo()方法忽略了new关键字。 When new is omitted the method behavior is the same as if the new keyword was explicitly defined. 省略new时,方法的行为与显式定义new关键字的行为相同。

You can only use the override keyword in conjunction with the virtual key word. 您只能将override关键字与虚拟关键字一起使用。 The base class method must be virtual. 基类方法必须是虚拟的。 Or abstract in which case the class must also be abstract. 或抽象,在这种情况下,该类也必须是抽象的。

person gets the the base implementation of ShowInfo because the Teacher class can't override the base implementation (no virtual declaration) and person is .GetType(Teacher) so it hides the the Teacher class's implementation. person获得ShowInfo的基本实现,因为Teacher类不能覆盖基本实现(无虚拟声明),并且person是.GetType(Teacher),因此它隐藏了Teacher类的实现。

teacher gets the derived Teacher implementation of ShowInfo because teacher because it is Typeof(Teacher) and it's not on the Person inheritance level. Teacher之所以获得ShowInfo的派生Teacher的实现,是因为Teacher是Typeof(Teacher),而不是在Person继承级别上。

person1 gets the derived Teacher implementation because it is .GetType(Teacher) and the implied new keyword hides the base implementation. person1获得派生的Teacher实现,因为它是.GetType(Teacher),并且隐含的new关键字隐藏了基本实现。

person2 also gets the derived Teacher implementation even though it does implement IPerson and it gets an explicit cast to IPerson. 即使person2确实实现了IPerson并获得了对IPerson的显式转换,它也获得了派生的Teacher实现。 This is again because the Teacher class does not explicitly implement the IPerson.ShowInfo() method. 再次这是因为Teacher类没有显式实现IPerson.ShowInfo()方法。

teacher1 also gets the derived Teacher implementation because it is .GetType(Teacher). Teacher1还获得了派生的Teacher实现,因为它是.GetType(Teacher)。

Only person3 gets the IPerson implementation of ShowInfo because only the Person class explicitly implements the method and person3 is an instance of the IPerson type. 因为只有Person类显式实现了该方法,并且person3是IPerson类型的实例,所以只有person3获得了ShowInfo的IPerson实现。

In order to explicitly implement an interface you must declare a var instance of the target interface type and a class must explicitly implement (fully qualify) the interface member(s). 为了显式实现接口,您必须声明目标接口类型的var实例,并且类必须显式实现(完全限定)接口成员。

Notice not even person4 gets the IPerson.ShowInfo implementation. 请注意,甚至person4都没有获得IPerson.ShowInfo实现。 This is because even though person4 is .GetType(Person) and even though Person implements IPerson, person4 is not an instance of IPerson. 这是因为即使person4是.GetType(Person)并且即使Person实现IPerson,person4也不是IPerson的实例。

LinQPad sample to launch blindly and reduce duplication of code Which I think is what you were trying to do. LinQPad示例可盲目启动并减少代码重复,我认为这是您正在尝试做的事情。

void Main()
{
    IEngineAction Test1 = new Test1Action();
    IEngineAction Test2 = new Test2Action();
    Test1.Execute("Test1");
    Test2.Execute("Test2");
}

public interface IEngineAction
{
    void Execute(string Parameter);
}

public abstract class EngineAction : IEngineAction
{
    protected abstract void PerformAction();
    protected string ForChildren;
    public void Execute(string Parameter)
    {  // Pretend this method encapsulates a 
       // lot of code you don't want to duplicate 
      ForChildren = Parameter;
      PerformAction();
    }
}

public class Test1Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Performed: " + ForChildren).Dump();
    }
}

public class Test2Action : EngineAction
{
    protected override void PerformAction()
    {
        ("Actioned: " + ForChildren).Dump();
    }
}
    class Program
    {
        static void Main(string[] args)
        { 
            AA aa = new CC();
            aa.Print();                      
        }
    }
    
    public class AA {public virtual void Print() => WriteLine("AA");}
    public class BB : AA {public override void Print() => WriteLine("BB");}
    public class DD : BB {public override void Print() => WriteLine("DD");}
    public class CC : DD {new public void Print() => WriteLine("CC");}
OutPut - DD

For those who wants to know how CLR is internally calling the new and virtual methods in C#.对于那些想知道 CLR 如何在内部调用 C# 中的 new 和 virtual 方法的人。

When new keyword is used a new-slot of memory is get allocated for CC.Print() and it will not over-ride the base class memory-slot as derived class is preceded with the new keyword, the method is defined as being independent of the method in the base class .当使用 new 关键字时,为CC.Print()分配了新的内存槽,并且它不会覆盖基类内存槽,因为派生类前面有 new 关键字,该方法被定义为独立的基类中的方法

When override is used the memory-slot is being override by derived class member, in this case by AA.Print() slot over-ride by BB.Print() ;当使用覆盖时,内存插槽被派生类成员覆盖,在这种情况下,由AA.Print()插槽覆盖BB.Print() BB.Print() is over-ride by DD.Print() . BB.Print()是超驰由DD.Print() When we call AA aa = new CC() ;当我们调用AA aa = new CC() compiler will create new memory-slot for CC.Print() but when it cast as AA, then as per Vtable Map, AA overridable object DD is called.编译器将为CC.Print()创建新的内存槽,但是当它转换为 AA 时,然后根据 Vtable Map,调用 AA 可覆盖对象 DD。

Reference - c# - Exact difference between overriding and hiding - Stack Overflow .NET Framework Internals: How the CLR Creates Runtime Objects |参考 - c# - 覆盖和隐藏之间的确切区别 - 堆栈溢出.NET Framework 内部:CLR 如何创建运行时对象 | Microsoft Docs 微软文档

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM