65.9K
CodeProject 正在变化。 阅读更多。
Home

深入OOP(第3天):多态性和继承(动态绑定/运行时多态)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (117投票s)

2014年5月18日

CPOL

18分钟阅读

viewsIcon

170938

downloadIcon

6594

本文系列将重点介绍运行时多态,也称为后期绑定。

1. 引言

在我们之前学习 OOP 系列的文章中,我们主要讨论了编译时多态、params 关键字、继承、base 关键字等。本文系列将重点介绍运行时多态,也称为后期绑定。我们将采用相同的学习方法,理论少,实践多。我们将通过小型代码片段来深入学习概念。掌握这个概念就等于掌握了 OOP 的 50% 以上。

Akhil_run time polymorphism

2. 先决条件

这是本系列的第三部分,我希望我的读者能够熟练掌握编译时多态和继承。虽然直接从本文开始学习也无妨,您之后可以再看其他文章。

3. 路线图

我们学习 OOP 的路线图已经很清楚了,让我们回顾一下

                                                          

  1. 深入 OOP (第一天):多态与继承 (早期绑定/编译时多态)
  2. 深入 OOP (第二天):多态与继承 (继承)
  3. 深入 OOP (第三天):多态与继承 (动态绑定/运行时多态)
  4. 深入 OOP (第四天):多态与继承 (关于 C# 中的抽象类)
  5. 深入OOP(第5天):C#中访问修饰符的一切(Public/Private/Protected/Internal/Sealed/Constants/Readonly字段)
  6. 深入 OOP (第六天):理解 C# 中的枚举 (实践方法)
  7. 深入OOP(第7天):C#中的属性(一种实用方法)
  8. 深入OOP(第8天):C#中的索引器(一种实用方法)
  9. 深入OOP(第9天):理解C#中的事件(洞察)
  10. 学习C#(第10天):C#中的委托(一种实用方法)
  11. 学习C#(第11天):C#中的事件(一种实用方法)

4. 运行时多态或后期绑定或动态绑定

用简单的 C# 语言来说,在运行时多态或方法重写中,我们可以在基类中重写一个方法,通过在派生类中创建同名方法来实现。这可以通过继承原则并使用 "virtual & override" 关键字来实现。

5. C# 中的 New 和 Override 关键字是什么?

                                                      

在 Visual Studio 中创建一个名为 InheritanceAndPolymorphism 的控制台应用程序。

只需添加两个类,并保持 Program.cs 不变。这两个类将命名为 ClassA.csClassB.cs,并在每个类中添加一个方法,如下所示

ClassA

public class ClassA
    {
        public void AAA()
        {
            Console.WriteLine("ClassA AAA");
        }

        public void BBB()
        {
            Console.WriteLine("ClassA BBB");
        }

        public void CCC()
        {
            Console.WriteLine("ClassA CCC");
        }
    }

ClassB

    public class ClassB
    {
        public void AAA()
        {
            Console.WriteLine("ClassB AAA");
        }

        public void BBB()
        {
            Console.WriteLine("ClassB BBB");
        }

        public void CCC()
        {
            Console.WriteLine("ClassB CCC");
        }
    }

Program.cs

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
          
        }
    }

我们看到 ClassAClassB 这两个类在两个类中都拥有相同数量的同名方法。现在,让我们让 ClassA 继承自 ClassB,创建类的实例并在 program.cs 中调用它们的方法。

因此,我们两个类的代码如下

    /// <summary>
    /// ClassB, acting as a base class
    /// </summary>
    public class ClassB
    {
        public void AAA()
        {
            Console.WriteLine("ClassB AAA");
        }

        public void BBB()
        {
            Console.WriteLine("ClassB BBB");
        }

        public void CCC()
        {
            Console.WriteLine("ClassB CCC");
        }
    }

    /// <summary>
    /// Class A, acting as a derived class
    /// </summary>
    public class ClassA : ClassB
    {
        public void AAA()
        {
            Console.WriteLine("ClassA AAA");
        }

        public void BBB()
        {
            Console.WriteLine("ClassA BBB");
        }

        public void CCC()
        {
            Console.WriteLine("ClassA CCC");
        }
    }

Program.cs

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            ClassA x = new ClassA();
            ClassB y=new ClassB();
            ClassB z=new ClassA();

            x.AAA(); x.BBB(); x.CCC();
            y.AAA(); y.BBB();y.CCC();
            z.AAA(); z.BBB(); z.CCC();
        }
    }

现在按 F5,即运行代码,我们会得到什么?

输出

ClassA AAA
ClassA BBB
ClassA CCC
ClassB AAA
ClassB BBB
ClassB CCC
ClassB AAA
ClassB BBB
ClassB CCC 

但是除了输出,我们还收到了三个警告。

警告

'InheritanceAndPolymorphism.ClassA.AAA()' hides inherited member 
'InheritanceAndPolymorphism.ClassB.AAA()'. Use the new keyword if hiding was intended.

'InheritanceAndPolymorphism.ClassA.BBB()' hides inherited member 
'InheritanceAndPolymorphism.ClassB.BBB()'. Use the new keyword if hiding was intended.

'InheritanceAndPolymorphism.ClassA.CCC()' hides inherited member 
'InheritanceAndPolymorphism.ClassB.CCC()'. Use the new keyword if hiding was intended.

要点: 在 C# 中,我们可以将基类对象赋值给派生类对象,但不能反过来。

ClassB 类是 ClassA 类的超类。这意味着 ClassA 是派生类,ClassB 是基类。ClassA 类包含 ClassB 类以及更多内容。所以我们可以得出结论,ClassA 对象比 ClassB 对象更大。由于 ClassA 继承自 ClassB,它包含自己的方法和属性。此外,它还将包含从 ClassB 继承的方法/属性。

让我们看对象 y 的情况。它看起来像 ClassB,并通过创建一个看起来像 ClassB 的对象来初始化,这很好。现在,当我们通过对象 y 调用 AAABBBCCC 方法时,我们知道它将从 ClassB 中调用它们。

对象 x 看起来像 ClassA,即派生类。它被初始化为一个看起来像 ClassA 的对象。当我们通过 x 调用 AAABBBCCC 方法时,它会从 ClassA 调用 AAABBBCCC

现在我们遇到一个有点棘手的情况

对象 z 看起来像 ClassB,但现在它被初始化为一个看起来像 ClassA 的对象,这不会像前面解释的那样产生错误。但是输出没有任何改变,行为与对象 y 完全相同。因此,初始化为一个看起来像 ClassBClassA 的对象似乎并不重要。

6. 实验

让我们来做个代码实验,在 ClassA,即派生类的 AAA 方法后面加上 override,在 BBB 方法后面加上 new。

我们的代码如下

ClassB

    /// <summary>
    /// ClassB, acting as a base class
    /// </summary>
    public class ClassB
    {
        public void AAA()
        {
            Console.WriteLine("ClassB AAA");
        }

        public void BBB()
        {
            Console.WriteLine("ClassB BBB");
        }

        public void CCC()
        {
            Console.WriteLine("ClassB CCC");
        }
    }

ClassA

    /// <summary>
    /// Class A, acting as a derived class
    /// </summary>
    public class ClassA : ClassB
    {
        public override void AAA()
        {
            Console.WriteLine("ClassA AAA");
        }

        public new void BBB()
        {
            Console.WriteLine("ClassA BBB");
        }

        public void CCC()
        {
            Console.WriteLine("ClassA CCC");
        }
    }

Program.cs

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            ClassB y = new ClassB();
            ClassA x = new ClassA();
            ClassB z = new ClassA();

            y.AAA(); y.BBB(); y.CCC();
            x.AAA(); x.BBB(); x.CCC();
            z.AAA(); z.BBB(); z.CCC();

            Console.ReadKey();
        }
    }

我们得到的输出是

Error:  'InheritanceAndPolymorphism.ClassA.AAA()': cannot override inherited member 
'InheritanceAndPolymorphism.ClassB.AAA()' because it is not marked virtual, abstract, or override

* InheritanceAndPolymorphism 这是我用于控制台应用程序的命名空间,所以您可以忽略它。

在派生类方法中添加这两个修饰符后,我们收到一个错误。该错误提示我们在基类中将方法标记为 virtualabstractoverride

好吧,这对我有什么影响?

                                                            

我将基类所有方法都标记为 virtual。

现在我们的代码和输出看起来像

/// <summary>
    /// ClassB, acting as a base class
    /// </summary>
    public class ClassB
    {
        public virtual void AAA()
        {
            Console.WriteLine("ClassB AAA");
        }

        public virtual void BBB()
        {
            Console.WriteLine("ClassB BBB");
        }

        public virtual void CCC()
        {
            Console.WriteLine("ClassB CCC");
        }
    }

    /// <summary>
    /// Class A, acting as a derived class
    /// </summary>
    public class ClassA : ClassB
    {
        public override void AAA()
        {
            Console.WriteLine("ClassA AAA");
        }

        public new void BBB()
        {
            Console.WriteLine("ClassA BBB");
        }

        public void CCC()
        {
            Console.WriteLine("ClassA CCC");
        }
    }

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            ClassB y = new ClassB();
            ClassA x = new ClassA();
            ClassB z = new ClassA();

            y.AAA(); y.BBB(); y.CCC();
            x.AAA(); x.BBB(); x.CCC();
            z.AAA(); z.BBB(); z.CCC();

            Console.ReadKey();
        }
    }

输出

ClassB AAA
ClassB BBB
ClassB CCC
ClassA AAA
ClassA BBB
ClassA CCC
ClassA AAA
ClassB BBB
ClassB CCC

要点: 需要 override 修饰符,因为派生类的方法将获得最高优先级并被调用。

我们在这里看到,只有对象 z 的工作方式有微小变化,而 xy 则没有。这个奇怪的输出只在我们为基类方法添加 virtual 修饰符后出现。区别在于对象 zz 看起来像基类 ClassB,但被初始化为一个看起来像派生类 ClassA 的实例。C# 知道这个事实。当我们运行 z.AAA() 时,C# 会记住实例 z 是由 ClassA 对象初始化的,因此它首先会去 ClassA 类,这很明显。这里的 `AAA` 方法有一个 override 修饰符,这意味着,忽略 z 的数据类型 ClassB,从 ClassA 调用 AAA,因为它覆盖了基类的 AAA。需要 override 修饰符,因为派生类的方法将获得最高优先级并被调用。

我们想要覆盖基类 ClassBAAA 方法。我们实际上是在告诉 C#,这个 AAA 方法与基类中的 AAA 方法相似。

New 关键字的作用与 override 关键字完全相反。我们看到的 BBB 方法具有 new 修饰符。z.BBB() 调用 ClassB 中的 BBB,而不是 ClassA 中的。New 意味着该方法 BBB 是一个新方法,与基类中的 BBB 完全无关。它可能具有与基类相同的名称 BBB,但这仅仅是巧合。由于 z 看起来像 ClassB,因此即使 ClassA 中有 BBB 方法,也会调用 ClassB 中的 BBB。当我们不写任何修饰符时,就假定我们写了 new。所以每次我们写一个方法时,C# 都假定它与基类无关。

要点: new 和 override 等修饰符只能在基类中的方法是 virtual 方法时使用。Virtual 意味着基类允许我们从派生类调用该方法,而不是基类本身。但是,如果我们的派生类方法需要被调用,我们就必须添加 override 修饰符。

7. 三个类的运行时多态

让我们进行更多操作。让我们再引入一个类。添加一个名为 ClassC 的类,并如下设计我们的三个类和 program.cs 文件

    /// <summary>
    /// ClassB, acting as a base class
    /// </summary>
    public class ClassB
    {
        public  void AAA()
        {
            Console.WriteLine("ClassB AAA");
        }

        public virtual void BBB()
        {
            Console.WriteLine("ClassB BBB");
        }

        public virtual void CCC()
        {
            Console.WriteLine("ClassB CCC");
        }
    }

    /// <summary>
    /// Class A, acting as a derived class
    /// </summary>
    public class ClassA : ClassB
    {
        public virtual void AAA()
        {
            Console.WriteLine("ClassA AAA");
        }

        public new void BBB()
        {
            Console.WriteLine("ClassA BBB");
        }

        public override void CCC()
        {
            Console.WriteLine("ClassA CCC");
        }
    }

    /// <summary>
    /// Class C, acting as a derived class
    /// </summary>
    public class ClassC : ClassA
    {
        public override void AAA()
        {
            Console.WriteLine("ClassC AAA");
        }

        public void CCC()
        {
            Console.WriteLine("ClassC CCC");
        }
    }

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            ClassB y = new ClassA();
            ClassB x = new ClassC();
            ClassA z = new ClassC();

            y.AAA(); y.BBB(); y.CCC();
            x.AAA(); x.BBB(); x.CCC();
            z.AAA(); z.BBB(); z.CCC();

            Console.ReadKey();
        }
    }

输出

ClassB AAA
ClassB BBB
ClassA CCC
ClassB AAA
ClassB BBB
ClassA CCC
ClassC AAA
ClassA BBB
ClassA CCC

不要害怕我们举的长例子。这将帮助您详细学习该概念。我们已经了解到,我们可以将基类对象初始化为派生类对象。但反之则会导致错误。这就导致基类实例被初始化为派生类实例。那么现在的问题是,什么时候会调用哪个方法?是基类的方法还是派生类的方法?

要点: 如果基类对象声明了 virtual 方法,并且派生类使用了 override 修饰符,那么将调用 derived 类的方法。否则,将执行 base 类的方法。因此,对于 virtual 方法,创建的数据类型仅在运行时决定。

要点: 所有未标记为 virtual 的方法都是非虚拟的,调用哪个方法取决于对象的 static 数据类型,在编译时决定。

如果一个类的对象被初始化为相同的数据类型,上述规则都不适用。每当我们出现不匹配时,我们总是需要规则来解决不匹配。因此,我们可能会遇到这种情况:基类对象可以调用派生类中的方法。

对象 y 看起来像 ClassB,但在这里被初始化为派生类,即 ClassA

y.AAA() 首先查看 ClassB 类。在这里,它验证方法 AAA 是否被标记为 virtual。答案是明确的“否”,因此一切都停止了,并且从 ClassB 类调用了方法 AAA

y.BBB 也做了同样的事情,但是现在方法在 ClassB 类中被定义为 virtual。因此,C# 会查看 ClassA 类,即它被初始化为的那个类。在这里,BBB 被标记为“new”修饰符。这意味着 BBB 是一个新方法,与基类中的方法无关。它们只是碰巧共享相同的名称。所以,由于派生类中没有名为 BBB 的方法(因为它是一个新的 BBB),因此调用了基类中的方法。在 y.CCC() 的场景中,同样的上述步骤又执行了一遍,但在 ClassA 类中,我们看到了 override 修饰符,它通过行为覆盖了基类中的方法。我们实际上是在告诉 C# 调用 ClassA 类中的这个方法,而不是基类 ClassB 中的。

                                                          

我刚从网上找到一张图片,描绘了我们当前的类的情况。我们现在正在轻松地学习这个概念。OOP 正在变得简单。

对象 x 也看起来像 ClassB 类,但现在被初始化为一个看起来像我们新引入的 ClassC 类的对象,而不是像以前那样是 ClassA。由于 AAA 是一个非虚方法,它从 ClassB 调用。对于方法 BBB,C# 现在查看 ClassC 类。在这里,它找不到名为 BBB 的方法,因此最终会向上传播并查找 ClassA 类。因此,上述规则不断重复,它从 ClassB 类调用。对于 x.CCC,在 ClassC 类中,它默认标记为 new,因此此方法与 ClassB 类中声明的方法无关。所以,不是调用 ClassC 中的方法,而是调用 ClassB 中的方法,其中它被标记为 override

现在,如果我们稍微修改 ClassC 中的 CCC 方法,并将其更改为如下代码

        public override void CCC()
        {
            Console.WriteLine("ClassC CCC");
        }

我们将默认的 new 改为 overrideClassCCCC 将被调用。

最后一个对象 z 看起来像 ClassA,但现在被初始化为一个看起来像派生类 ClassC 的对象,我们知道我们可以这样做。所以,当调用 z.AAA() 时,它首先查看 ClassA 类,在那里它被标记为 virtual。您是否还记得 AAAClassB 类中是非虚的,但在 ClassA 中被标记为 virtual。从现在开始,AAA 方法在 ClassC 类中也是 virtual 的,但在 ClassB 类中不是。Virtual 总是像瀑布一样从上流向下。由于 AAA() 被标记为 virtual,我们现在查看 ClassC 类。在这里,它被标记为 override,因此 AAA()ClassC 类调用。对于 BBB()BBB()ClassB 类中被标记为 virtual,在 ClassA 中被标记为 new,但由于 ClassC 中没有 BBB 方法,因此在这种情况下,任何修饰符都无关紧要。最终,它从 ClassA 类调用。最后,对于方法 CCC,在 ClassC 类中,它被标记为 new。因此,它与 ClassA 中的 CCC 方法无关,导致 CCC 方法从 ClassA 调用,而不是从 ClassB 调用。

再举一个例子

    internal class A
    {
        public virtual void X()
        {
        }
    }

    internal class B : A
    {
        public new void X()
        {
        }
    }

    internal class C : B
    {
        public override void X()
        {
        }
    }

在上面的例子中,代码非常直观,我们将获得的输出是

Error: 'InheritanceAndPolymorphism.C.X()': cannot override inherited member 
'InheritanceAndPolymorphism.B.X()' because it is not marked virtual, abstract, or override

奇怪!我们收到一个错误,因为 B 类中的方法 X() 被标记为 new。这意味着它隐藏了 A 类中的 X()。如果我们谈论 C 类,B 没有提供名为 X 的方法。B 类中定义的 X 方法与 A 类中定义的 X 方法无关。这意味着 B 类中的 X 方法不会从 A 类中的 X() 方法继承 virtual 修饰符。这就是编译器抱怨的原因。由于 B 中的 X 方法没有 virtual 修饰符,在 C 中我们不能使用 override 修饰符。但是,我们可以使用 new 修饰符并消除警告?

8. 切断关系

    internal class A
    {
        public virtual void X()
        {
            Console.WriteLine("Class: A ; Method X");
        }
    }

    internal class B : A
    {
        public new virtual void X()
        {
            Console.WriteLine("Class: B ; Method X");
        }
    }

    internal class C : B
    {
        public override void X()
        {
            Console.WriteLine("Class: C ; Method X");
        }
    }

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            A a = new C();
            a.X();
            B b = new C();
            b.X();

            Console.ReadKey();
        }
    }

输出

Class: A ; Method X
Class: C ; Method X

如果在上面的代码中,我们从 C 类中的 X() 中删除了 override 修饰符,我们会得到

输出

Class: A ; Method X
Class: B ; Method X

                                 

是的,这就是 virtual 方法的问题。有时,它们太令人困惑了,结果与我们预期的完全不同。对象 a 看起来像 A,但被初始化为派生类 C。由于 X 是 virtual 的,C# 现在会去查看 C 类。但在查看 C 类之前,它意识到在 B 类中,Xnew。就这样,这个东西切断了与 A 类中的 X 的所有连接。因此,如果 new 关键字前面没有 virtual,否则 override 修饰符会在 C 类中给我们带来错误。由于 B 类中的 X 被标记为 new 方法,与 A 类无关,C 类继承了一个也与 A 类无关的 newC 类中的 XB 类中的 X 相关,而不是与 A 类相关。因此,调用了 A 类中的 X

在第二种情况下,对象 b 看起来像 B 类,但又被初始化为 C 类的对象。C# 首先查看 B 类。在这里,X 同时是 newvirtual,这使其成为一个独特的 X 方法。不幸的是,C 中的 X 具有 override 修饰符,它使得 C 中的 X 隐藏了 B 中的 X。这会调用 C 中的 X 而不是 B 中的。如果我们从 C 类中的 override 修饰符中删除 override,默认将是 new,这会切断与 BX 的关系。因此,按原样,一个 new 方法,调用了 B 中的 X

virtual 方法不能被 staticabstractoverride 修饰符标记。非虚方法被认为是不可变的。这意味着,无论基类或派生类中是否存在该方法,总是会调用相同的方法。在虚方法中,对象的运行时类型决定调用哪个方法,而不是编译时类型,如非虚方法中的情况。对于虚方法,存在一个最派生的实现,该实现总是会被调用。

9. 四个类的运行时多态

好的!我们进行了大量的编码。如果我告诉您我们将在代码中再添加一个类,是的,那就是 ClassD 类。因此,我们将更深入地研究多态和继承的概念。

我们将我们正在处理的三类解决方案(还记得吗?)中再添加一个类。所以我们的新类名为 ClassD

让我们将新类投入实际使用

/// <summary>
    /// Class A
    /// </summary>
    public class ClassA
    {
        public virtual void XXX()
        {
            Console.WriteLine("ClassA XXX");
        }
    }

    /// <summary>
    /// ClassB
    /// </summary>
    public class ClassB:ClassA 
    {
        public override void XXX()
        {
            Console.WriteLine("ClassB XXX");
        }
    }

    /// <summary>
    /// Class C
    /// </summary>
    public class ClassC : ClassB
    {
        public virtual new void XXX()
        {
            Console.WriteLine("ClassC XXX");
        }
    }

    /// <summary>
    /// Class D
    /// </summary>
    public class ClassD : ClassC
    {
        public override void XXX()
        {
            Console.WriteLine("ClassD XXX");
        }
    }

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            ClassA a = new ClassD();
            ClassB b = new ClassD();
            ClassC c=new ClassD();
            ClassD d=new ClassD();
           
            a.XXX();
            b.XXX();
            c.XXX();
            d.XXX();

            Console.ReadKey();
        }
    }

输出

ClassB XXX
ClassB XXX
ClassD XXX
ClassD XXX

解释

最后对 virtualoverride 的解释会有点复杂。

第一个输出 ClassB XXX 是语句 a.XXX(); 的结果。我们在 ClassA 类中将方法 XXX 标记为 virtual。因此,在使用 new 关键字时,我们将继续前往 ClassB 类,而不是 ClassD 类,如前所述。在这里,XXX 有一个 override,并且由于 C# 知道 ClassC 类继承了这个函数 XXX。在 ClassC 类中,由于它被标记为 new,C# 现在将回退,而不是继续前往 ClassD 类。最后,方法 XXXClassB 调用,如上面的输出所示。

如果我们更改 ClassC 中的方法 XXX 为 override,那么 C# 将前往 ClassD 类并调用 ClassD 中的 XXX,因为它覆盖了 ClassC 中的 XXX

    /// <summary>
    /// Class C
    /// </summary>
    public class ClassC : ClassB
    {
        public override void XXX()
        {
            Console.WriteLine("ClassC XXX");
        }
    }

ClassD 中的 XXX 中删除 override,该方法将从 ClassC 调用,因为默认值是 new

当我们谈论对象 b 时,一切看起来都与对象 a 相似,因为它覆盖了 ClassA 类中的 XXX

当我们恢复默认设置时,让我们看看第三行。对象 c 在这里看起来像 ClassC。在 ClassC 类中,XXX()new 的,因此它与之前的 XXX 方法没有任何联系。在 ClassD 类中,我们实际上覆盖了 ClassC 中的 XXX,因此调用了 ClassD 中的 XXX。只需删除 override,然后它将从 ClassC 调用。对象 d 不遵循上述任何协议,因为等号的两边都是相同的数据类型。

要点: override 方法是包含 override 修饰符的方法。它引入了对方法的新的实现。我们不能将 newstaticvirtual 等修饰符与 override 一起使用。但是 abstract 是允许的。

另一个例子

/// <summary>
    /// Class A
    /// </summary>
    public class ClassA
    {
        public virtual void XXX()
        {
            Console.WriteLine("ClassA XXX");
        }
    }

    /// <summary>
    /// ClassB
    /// </summary>
    public class ClassB:ClassA 
    {
        public override void XXX()
        {
            base.XXX();
            Console.WriteLine("ClassB XXX");
        }
    }

    /// <summary>
    /// Class C
    /// </summary>
    public class ClassC : ClassB
    {
        public override void XXX()
        {
            base.XXX();
            Console.WriteLine("ClassC XXX");
        }
    }

    /// <summary>
    /// Class D
    /// </summary>
    public class ClassD : ClassC
    {
        public override void XXX()
        {
            Console.WriteLine("ClassD XXX");
        }
    }

    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            ClassA a = new ClassB();
            a.XXX();
            ClassB b = new ClassC();
            b.XXX();
            Console.ReadKey();
        }
    }

输出

ClassA XXX
ClassB XXX
ClassA XXX
ClassB XXX
ClassC XXX

当我们使用保留关键字 base 时,我们可以访问基类方法。在这里,无论 XXX 是否是 virtual,它都将被 base 关键字视为非 virtual。因此,将始终调用基类 XXX。对象 a 已经知道 XXX 是 virtual 的。当它到达 ClassB 时,它看到 base.XXX(),因此它调用 ClassAXXX 方法。但在第二种情况下,它首先转到 ClassC 类,在这里它调用 base.XXX,即 ClassBXXX 方法,后者又调用 ClassAXXX 方法。

10. 无限循环

/// <summary>
    /// Class A
    /// </summary>
    public class ClassA
    {
        public virtual void XXX()
        {
            Console.WriteLine("ClassA XXX");
        }
    }

    /// <summary>
    /// ClassB
    /// </summary>
    public class ClassB:ClassA 
    {
        public override void XXX()
        {
            ((ClassA)this).XXX();
            Console.WriteLine("ClassB XXX");
        }
    }

   
    /// <summary>
    /// Program: used to execute the method.
    /// Contains Main method.
    /// </summary>
    public class Program
    {
        private static void Main(string[] args)
        {
            ClassA a = new ClassB();
            a.XXX();
           
        }
    }

输出

错误:{无法评估表达式,因为当前线程处于堆栈溢出状态。}

在这种情况下,任何类型转换都无法阻止无限循环。因此,即使它被转换为 ClassA 类,它也始终从 ClassB 类调用 XXX,而不是 ClassA。所以我们没有输出。

11. 总结

让我们总结一下我们在长文章中获得的所有要点。

  1. 在 C# 中,我们可以将基类对象赋值给派生类对象,但不能反过来。
  2. 需要 override 修饰符,因为派生类的方法将获得最高优先级并被调用。
  3. 这些修饰符如 newoverride 只能在基类中的方法是 virtual 方法时使用。Virtual 意味着基类允许我们从派生类调用该方法,而不是基类本身。但是,如果我们的 derived 类方法需要被调用,我们就必须添加 override 修饰符。
  4. 如果基类对象声明了 virtual 方法,并且 derived 类使用了 override 修饰符,那么将调用 derived 类的方法。否则,将执行 base 类的方法。因此,对于 virtual 方法,创建的数据类型仅在运行时决定。
  5. 所有未标记为 virtual 的方法都是非虚拟的,调用哪个方法取决于对象的 static 数据类型,在编译时决定。
  6. override 方法是包含 override 修饰符的方法。它引入了对方法的新的实现。我们不能将 newstaticvirtual 等修饰符与 override 一起使用。但是 abstract 是允许的。

12. 结论

长文,我的读者建议我写短文,但我忍不住。我一直写,直到我完全理解了概念,我才想停下来。我的目的是以简单的方式分享每一个细节,让您开始爱上 OOP。然而,在本文中,为了便于阅读,我尝试将各部分归类在不同的标题下。

在本文中,我们学习了运行时多态和继承的概念。我们通过动手实验覆盖了大多数场景。我们将在接下来的文章中涵盖更多主题。欢迎随时发表评论或提问。如果您觉得我的文章有帮助,请点赞并评分,这会让我很高兴。编码愉快。

我的其他系列文章

MVChttps://codeproject.org.cn/Articles/620195/Learning-MVC-Part-Introduction-to-MVC-Architectu

RESTful WebAPIshttps://codeproject.org.cn/Articles/990492/RESTful-Day-sharp-Enterprise-Level-Application

祝您编码愉快!

© . All rights reserved.