【问题标题】:Why does calling a method in my derived class call the base class method?为什么在我的派生类中调用方法会调用基类方法?
【发布时间】:2013-07-17 01:20:15
【问题描述】:

考虑这段代码:

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");
    }
}

当我运行这段代码时,会输出以下内容:

我是人

但是,您可以看到它是 Teacher 的实例,而不是 Person 的实例。为什么代码会这样做?

【问题讨论】:

  • 一个Java人的问题:Console.ReadLine();这个例子有必要吗?
  • @Shahrooz 我无法回答您的问题 - 我不懂 C#。我在问一个非常琐碎的 C# 问题,即是否需要在 main 方法中调用 ReadLine 才能在 Person 和 Teacher 类中调用 WriteLine。
  • 是的,当 Main() 退出时,.Net 会自动关闭控制台窗口。为了解决这个问题,我们使用 Console.Read() 或 Console.Readline() 来等待额外的输入,以便控制台保持打开状态。
  • @Rich 不,这不是必要的,但您经常会看到它,原因是:从 Visual Studio 运行控制台程序时,程序终止时命令窗口直接关闭离开,所以如果你想看到程序输出你需要告诉它等待。
  • @AakashM 谢谢 - 我把时间花在 Eclipse 中,控制台是 Eclipse 窗口的一部分,因此不会关闭。这很有道理。

标签: c# class derived-class


【解决方案1】:

newvirtual/override 之间存在差异。

你可以想象,当一个类被实例化时,只不过是一个指针表,指向它的方法的实际实现。下图应该可以很好地显示这一点:

现在有不同的方法,可以定义一个方法。当与继承一起使用时,每个行为都不同。标准方式始终如上图所示。如果你想改变这种行为,你可以在你的方法中附加不同的关键字。

1。抽象类

第一个是abstractabstract 方法只是指向无处:

如果你的类包含抽象成员,也需要标记为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!");
    }
}

如果被调用,ShowInfo 的行为会有所不同,具体取决于实现:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a student!'

Students 和 Teachers 都是 Persons,但是当他们被要求提示有关自己的信息时,它们的行为不同。但是,要求他们提示信息的方式是一样的:使用Person类接口。

那么,当您从Person 继承时,幕后会发生什么?在实现ShowInfo 时,指针不再指向nowhere,它现在指向实际的实现!创建Student实例时,它指向Students ShowInfo

2。虚拟方法

第二种方法是使用virtual 方法。行为是相同的,除了您在基类中提供 optional 默认实现。可以实例化具有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!");
    }
}

关键区别在于,基本成员Person.ShowInfo 不再指向nowhere。这也是为什么您可以创建Person 实例的原因(因此不再需要将其标记为abstract):

您应该注意到,这与第一张图片目前看起来并没有什么不同。这是因为virtual 方法指向一个实现“标准方式”。使用virtual,您可以告诉Persons,他们可以(不是必须)为ShowInfo 提供不同的实现。如果您提供不同的实现(使用override),就像我为上面的Teacher 所做的那样,图像看起来与abstract 相同。想象一下,我们没有为Students 提供自定义实现:

public class Student : Person
{
}

代码会这样调用:

Person person = new Teacher();
person.ShowInfo();    // Shows 'I am a teacher!'

person = new Student();
person.ShowInfo();    // Shows 'I am a person!'

Student 的图像看起来像这样:

3。神奇的 `new` 关键字又名“Shadowing”

new 更多的是解决这个问题。您可以在通用类中提供与基类/接口中的方法同名的方法。两者都指向自己的自定义实现:

实现看起来像您提供的那个。行为会有所不同,具体取决于您访问方法的方式:

Teacher teacher = new Teacher();
Person person = (Person)teacher;

teacher.ShowInfo();    // Prints 'I am a teacher!'
person.ShowInfo();     // Prints 'I am a person!'

这种行为可能是需要的,但在你的情况下它具有误导性。

我希望这能让你更清楚地理解事情!

【讨论】:

  • 感谢您的精彩回答
  • 你用什么来生成这些图表?
  • 优秀且非常彻底的答案。
  • tl;dr 您使用了new,它破坏了函数的继承并使新函数与超类的函数分开
  • @Taymon:实际上不是......我只是想明确表示,现在这个电话反对Person,而不是Student;)
【解决方案2】:

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");
    }
}

在您问题的代码中,您使用new,它执行shadowing 而不是覆盖。阴影仅影响编译时语义而不是运行时语义,因此会产生意外输出。

【讨论】:

  • 谁说 OP 知道这些是什么意思。
  • @ColeJohnson 我会澄清一下。
【解决方案3】:

您必须创建方法virtual,并且您必须覆盖子类中的函数,以便调用您放在父类引用中的类对象的方法。

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");
    }
}

虚拟方法

当调用虚方法时,对象的运行时类型为 检查覆盖成员。最重要的成员 派生类被调用,它可能是原始成员,如果没有 派生类已覆盖该成员。默认情况下,方法是 非虚拟的。您不能覆盖非虚拟方法。你不能使用 带有 static、abstract、private 或 override 的 virtual 修饰符 修饰符,MSDN

使用新的阴影

您正在使用 new 关键字而不是 override,这就是 new 所做的

  • 如果派生类中的方法前面没有 new 或 override 关键字,编译器将发出警告,并且该方法的行为就像存在 new 关键字一样。

  • 如果派生类中的方法前面带有new关键字,则该方法定义为独立于基类中的方法,这个MSDN article很好解释.

早绑定VS晚绑定

我们在编译时对普通方法(非虚拟)进行了早期绑定,这是当前的情况,编译器会将调用绑定到引用类型(基类)的方法而不是对象的基类方法保存在基类的引用中,即派生类对象。这是因为ShowInfo 不是虚拟方法。后期绑定是在运行时使用virtual method table (vtable) 为(虚拟/覆盖方法)执行的。

对于普通函数,编译器可以计算出数字位置 它在记忆中。然后它在调用函数时可以生成 在此地址调用函数的指令。

对于具有任何虚方法的对象,编译器会生成 一个 v 表。这本质上是一个包含地址的数组 虚方法。每个拥有虚方法的对象都会 包含由编译器生成的隐藏成员,即地址 的 v 表。当调用虚函数时,编译器将 找出合适的方法在 v 表。然后它将生成代码以查看对象 v-table 和 在这个位置调用虚方法Reference

【讨论】:

    【解决方案4】:

    我想以Achratt's answer 为基础。为了完整起见,不同之处在于 OP 期望派生类方法中的 new 关键字覆盖基类方法。它实际上所做的是隐藏基类方法。

    在 C# 中,正如另一个答案所提到的,传统的方法覆盖必须是显式的;基类方法必须标记为virtual,派生类必须明确override 基类方法。如果这样做了,那么对象是否被视为基类或派生类的实例都无关紧要。找到并调用派生方法。这以与 C++ 类似的方式完成;标记为“虚拟”或“覆盖”的方法在编译时通过确定引用对象的实际类型并沿树从变量类型向下遍历对象层次结构到实际对象类型来“延迟”(在运行时)解析,查找由变量类型定义的方法的最派生实现。

    这与 Java 不同,后者允许“隐式覆盖”;例如方法(非静态),简单地定义相同签名的方法(名称和参数的数量/类型)将导致子类覆盖超类。

    因为扩展或覆盖您无法控制的非虚拟方法的功能通常很有用,所以 C# 还包括 new 上下文关键字。 new 关键字“隐藏”父方法而不是覆盖它。任何可继承的方法都可以被隐藏,不管它是不是虚拟的;这使您,开发人员,可以利用您想要从父级继承的成员,而无需解决您不想要的成员,同时仍然允许您向​​代码的使用者提供相同的“界面”。

    从使用对象的人的角度来看,隐藏的工作方式类似于覆盖,其继承级别或低于定义隐藏方法的继承级别。从问题的示例中,编码人员创建一个 Teacher 并将该引用存储在 Teacher 类型的变量中,将从 Teacher 中看到 ShowInfo() 实现的行为,这对 Person 隐藏了。但是,在 Person 记录集合中使用您的对象的人(就像您一样)将看到 ShowInfo() 的 Person 实现的行为;因为 Teacher 的方法不会覆盖其父方法(这也需要 Person.ShowInfo() 是虚拟的),所以在 Person 抽象级别工作的代码将找不到 Teacher 实现并且不会使用它。

    此外,new 关键字不仅会显式执行此操作,C# 还允许隐式方法隐藏;简单地定义一个与父类方法具有相同签名的方法,没有overridenew,将隐藏它(尽管它会产生编译器警告或来自某些重构助手如 ReSharper 或 CodeRush 的投诉)。这是 C# 的设计者在 C++ 的显式覆盖与 Java 的隐式覆盖之间提出的折衷方案,虽然它很优雅,但如果您来自任何一种较旧的语言背景,它并不总是会产生您所期望的行为。

    这是新的东西:当您将两个关键字组合在一个长的继承链中时,这会变得复杂。考虑以下几点:

    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();
    

    输出:

    Foo
    Bar
    Baz
    Bai
    Bat
    ---
    Bar
    Bar
    Bai
    Bai
    Bat
    ---
    Bar
    Bar
    Bai
    Bai
    Bat
    

    第一组五人都在意料之中;因为每个级别都有一个实现,并且被引用为与实例化类型相同的对象,所以运行时会将每个调用解析为变量类型引用的继承级别。

    第二组五个是将每个实例分配给直接父类型的变量的结果。现在,行为上的一些差异已经消除; foo2,实际上是将Bar 转换为Foo,仍然会找到实际对象类型Bar 的更多派生方法。 bar2Baz,但与 foo2 不同,因为 Baz 没有显式覆盖 Bar 的实现(它不能;Bar sealed 它),运行时在“自上而下”时看不到",因此改为调用 Bar 的实现。请注意,Baz 不必使用 new 关键字;如果省略关键字,则会收到编译器警告,但 C# 中的隐含行为是隐藏父方法。 baz2 是一个Bai,它覆盖了Baznew 实现,所以它的行为类似于foo2 的;调用 Bai 中实际对象类型的实现。 bai2 是一个 Bat,它再次隐藏了它的父 Bai 的方法实现,它的行为与 bar2 相同,即使 Bai 的实现不是密封的,所以理论上 Bat 可以覆盖而不是隐藏方法。最后,bat2 是一个 Bak,它没有任何一种覆盖实现,只是使用其父类的实现。

    第三组五个展示了完整的自上而下的分辨率行为。一切实际上都在引用链中派生最多的类的实例Bak,但变量类型的每一级的解析都是通过从继承链的该级别开始并深入到最派生的显式 方法的覆盖,即BarBaiBat 中的方法。方法隐藏因此“破坏”了覆盖的继承链;您必须在隐藏方法的继承级别或以下级别使用对象才能使用隐藏方法。否则,hidden 方法被“发现”并被使用。

    【讨论】:

      【解决方案5】:

      请阅读 C# 中的多态性:Polymorphism (C# Programming Guide)

      这是一个例子:

      当使用 new 关键字时,会调用新的类成员 已被替换的基类成员。那些基类 成员称为隐藏成员。隐藏的班级成员仍然可以 如果派生类的实例被强制转换为 基类。例如:

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

      【讨论】:

        【解决方案6】:

        您需要将其设为virtual,然后在Teacher 中覆盖该函数。当您继承并使用基指针来引用派生类时,您需要使用virtual 覆盖它。 new 用于隐藏派生类引用上的base 类方法,而不是base 类引用。

        【讨论】:

          【解决方案7】:

          我想添加更多示例来扩展有关此信息的信息。希望这也有帮助:

          这是一个代码示例,它清楚地说明了将派生类型分配给基类型时会发生什么。哪些方法可用,以及在此上下文中覆盖方法和隐藏方法之间的区别。

          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()");
                  }
              }
          }
          

          另一个小异常是,对于以下代码行:

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

          VS 编译器(智能感知)会将 a.foo() 显示为 A.foo()。

          因此,很明显,当一个更派生的类型被分配给一个基类型时,“基类型”变量充当基类型,直到在派生类型中被覆盖的方法被引用。 对于隐藏方法或在父类型和子类型之间具有相同名称(但未覆盖)的方法,这可能会变得有点违反直觉。

          此代码示例应该有助于描述这些注意事项!

          【讨论】:

            【解决方案8】:

            C# 在父/子类覆盖行为方面与 java 不同。默认情况下,Java 中的所有方法都是虚拟的,因此您想要的行为是开箱即用的。

            在 C# 中,您必须在基类中将方法标记为虚拟,然后您将得到您想要的。

            【讨论】:

              【解决方案9】:

              new 关键字告诉当前类中的方法只有在将类 Teacher 类的实例存储在类型为 Teacher 的变量中时才有效。或者您可以使用强制转换触发它: ((Teacher)Person).ShowInfo()

              【讨论】:

                【解决方案10】:

                这里的变量'teacher'的类型是typeof(Person),这个类型对Teacher类一无所知,也不会尝试在派生类型中寻找任何方法。要调用 Teacher 类的方法,您应该转换变量:(person as Teacher).ShowInfo()

                要根据值类型调用特定方法,您应该在基类中使用关键字“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
                {
                
                }
                

                【讨论】:

                  【解决方案11】:

                  可能为时已晚......但问题很简单,答案应该具有相同的复杂程度。

                  在您的代码变量中,人对 Teacher.ShowInfo() 一无所知。 无法从基类引用中调用 last 方法,因为它不是虚拟的。

                  有一些有用的继承方法——试着想象一下你想用你的代码层次结构表达什么。还要尝试想象一个或另一个工具对自身的描述。例如。如果您将虚函数添加到您认为的基类中: 1. 它可以具有默认实现; 2.它可能在派生类中重新实现。如果你添加抽象函数,它只意味着一件事——子类必须创建一个实现。但如果你有简单的功能 - 你不希望任何人改变它的实现。

                  【讨论】:

                    【解决方案12】:

                    只是想简单回答一下-

                    您应该在可能被覆盖的类中使用virtualoverride。使用virtual 表示可以被子类覆盖的方法,使用override 表示应该覆盖virtual 方法的方法。

                    【讨论】:

                      【解决方案13】:

                      我在 java 中编写了与您在上面提到的相同的代码,除了一些更改之外,它工作正常,除了例外。基类的方法被覆盖,因此显示的输出是“我是老师”。

                      原因: 因为我们正在创建一个基类的引用(它能够引用派生类的实例),它实际上包含派生类的引用。我们知道,实例总是首先查看它的方法,如果它在那里找到它就会执行它,如果它在那里找不到定义,它会在层次结构中上升。

                      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");
                          }
                      }
                      

                      【讨论】:

                        【解决方案14】:

                        在 Keith S. 的出色演示和其他所有人的高质量答案的基础上,为了超级完整性,让我们继续并抛出显式接口实现来演示它是如何工作的。考虑以下内容:

                        命名空间 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());
                            }
                        }
                        

                        }

                        这是输出:

                        人:我是人 == LinqConsoleApp.Teacher

                        老师:我是老师 == LinqConsoleApp.Teacher

                        person1:我是老师 == LinqConsoleApp.Teacher

                        person2:我是老师 == LinqConsoleApp.Teacher

                        teacher1:我是老师 == LinqConsoleApp.Teacher

                        person4:我是 Person == LinqConsoleApp.Person

                        person3:我是接口 Person == LinqConsoleApp.Person

                        有两点需要注意:
                        Teacher.ShowInfo() 方法省略了 new 关键字。当省略 new 时,方法行为与显式定义 new 关键字时相同。

                        您只能将 override 关键字与 virtual 关键字结合使用。基类方法必须是虚拟的。或者抽象,在这种情况下,类也必须是抽象的。

                        person 获取 ShowInfo 的基本实现,因为 Teacher 类无法覆盖基本实现(无虚拟声明),而 person 是 .GetType(Teacher),因此它隐藏了 Teacher 类的实现。

                        teacher 获取 ShowInfo 的派生 Teacher 实现,因为 teacher 因为它是 Typeof(Teacher) 并且不在 Person 继承级别上。

                        person1 获取派生的 Teacher 实现,因为它是 .GetType(Teacher) 并且隐含的 new 关键字隐藏了基本实现。

                        person2 也获得派生的 Teacher 实现,即使它确实实现了 IPerson 并且它获得了 IPerson 的显式转换。这又是因为 Teacher 类没有显式实现 IPerson.ShowInfo() 方法。

                        teacher1 还获取派生的 Teacher 实现,因为它是 .GetType(Teacher)。

                        只有 person3 获得 ShowInfo 的 IPerson 实现,因为只有 Person 类显式实现了该方法,而 person3 是 IPerson 类型的一个实例。

                        为了显式实现接口,您必须声明目标接口类型的 var 实例,并且类必须显式实现(完全限定)接口成员。

                        请注意,甚至 person4 都没有获得 IPerson.ShowInfo 实现。这是因为即使 person4 是 .GetType(Person) 并且即使 Person 实现了 IPerson,person4 也不是 IPerson 的实例。

                        【讨论】:

                        • 我看到正确格式化代码是一个挑战。现在没时间美化它......
                        【解决方案15】:

                        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();
                            }
                        }
                        

                        【讨论】:

                          【解决方案16】:
                              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
                          

                          适合那些想了解 CLR 如何在内部调用 C# 中的新方法和虚方法的人。

                          当使用 new 关键字时,会为 CC.Print() 分配一个新的内存槽,并且它不会覆盖基类内存槽因为派生类前面带有 new 关键字,方法是定义为独立于基类中的方法

                          当使用覆盖时,内存槽被派生类成员覆盖,在这种情况下,AA.Print() 插槽被BB.Print() 覆盖; BB.Print()DD.Print() 覆盖。 当我们拨打AA aa = new CC()时;编译器将为CC.Print() 创建新的内存槽,但是当它转换为 AA 时,然后根据 Vtable Map,调用 AA 可覆盖对象 DD。

                          参考 - c# - Exact difference between overriding and hiding - Stack Overflow .NET Framework Internals: How the CLR Creates Runtime Objects | Microsoft Docs

                          【讨论】:

                            猜你喜欢
                            • 2014-09-05
                            • 1970-01-01
                            • 2013-10-08
                            • 2013-03-23
                            • 2016-01-01
                            • 1970-01-01
                            • 2014-03-17
                            • 2017-10-31
                            • 1970-01-01
                            相关资源
                            最近更新 更多