深入OOP(第2天):多态性和继承(继承)






4.80/5 (132投票s)
深入 OOP(第二天):我系列文章的第二部分将仅侧重于 OOP 中的继承概念。
引言
在我们文章的第一部分,我们学习了方法重载的不同场景,并进行了许多有趣的实际操作。我系列文章的第二部分将仅侧重于 OOP 中的继承概念。让我们用一些要点来定义继承。
路线图
我们仍然遵循在开始学习 OOP 系列之前定义的路线图。
- 深入 OOP(第一天):多态与继承(早期绑定/编译时多态)
- 深入OOP(第2天):多态性和继承(继承)
- 深入OOP(第3天):多态性和继承(动态绑定/运行时多态)
- 深入 OOP (第四天):多态与继承 (关于 C# 中的抽象类)
- 深入OOP(第5天):C#中访问修饰符的一切(Public/Private/Protected/Internal/Sealed/Constants/Readonly字段)
- 深入 OOP (第六天):理解 C# 中的枚举 (实践方法)
- 深入OOP(第7天):C#中的属性(一种实用方法)
- 深入OOP(第8天):C#中的索引器(一种实用方法)
- 深入OOP(第9天):理解C#中的事件(洞察)
- 学习C#(第10天):C#中的委托(一种实用方法)
- 学习C#(第11天):C#中的事件(一种实用方法)
继承的应用
好的。我们来做一些实际操作。创建一个名为 `InheritanceAndPolymorphism` 的控制台应用程序。添加一个名为 `ClassA` 的类和一个名为 `ClassB` 的类,使用以下代码:
注意:本文中编写的每一段代码片段都经过了尝试和测试。
ClassA:
class ClassA
{
}
ClassB:
class ClassB
{
public int x = 100;
public void Display1()
{
Console.WriteLine("ClassB Display1");
}
public void Display2()
{
Console.WriteLine("ClassB Display2");
}
}
我们看到 `ClassA` 类是空的,我们在 `ClassB` 类中添加了两个方法:`Display1` 和 `Display2`。我们还有一个变量 `x`,它被声明并赋值为 `100`。
现在,在Program.cs的 `Main` 方法中,编写以下代码:
Program.cs
class Program
{
static void Main(string[] args)
{
ClassA a = new ClassA();
a.Display1();
}
}
如果我们运行代码,会立即遇到编译时错误。
错误:`'InheritanceAndPolymorphism.ClassA'` 不包含 '`Display1`' 的定义,也找不到接受 '`InheritanceAndPolymorphism.ClassA`' 作为第一个参数的扩展方法 '`Display1`'(您是否缺少 `using` 指令或程序集引用?)。
即:太明显了,`ClassA` 类中没有 `Display1` 方法的定义,我们也无法通过 `ClassA` 实例访问同一个方法,因为它没有从任何包含 `Display1` 方法的类(如 `ClassB`)派生。`ClassA` 类不包含任何定义的代码或变量。一个空的类不会引发错误,因为我们可以实例化一个看起来像(`ClassA` 的实例)的对象。错误出现是因为 `ClassA` 类没有名为 `Display1` 的方法。但是 `ClassB` 类有一个名为 `Display1` 的方法。想象一下,如果我们能直接从 `ClassA` 本身访问 `classB` 的所有代码,那会多么有趣。
只需使用 `:` 运算符将 `ClassA` 类派生自 `ClassB`,代码如下所示:
ClassA:
class ClassA:ClassB
{
}
ClassB:
class ClassB
{
public int x = 100;
public void Display1()
{
Console.WriteLine("ClassB Display1");
}
public void Display2()
{
Console.WriteLine("ClassB Display2");
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
ClassA a = new ClassA();
a.Display1();
Console.ReadKey();
}
}
现在再次运行代码,我们会得到一个输出。
输出
ClassB Display1
即:现在 `ClassA` 可以访问 `ClassB` 的继承的 `public` 方法。错误消失,`ClassB` 中的 `Display1` 被调用。如果在类的名称后面指定了 `:` `ClassB`,即另一个类的名称,那么很多事情会立即改变。现在说 `ClassA` 已从 `ClassB` 派生。这意味着我们在 `ClassB` 中编写的所有代码现在都可以在 `ClassA` 中访问和使用。就像我们实际上在 `ClassA` 中编写了 `ClassB` 中包含的所有代码一样。如果我们创建了一个看起来像 `ClassB` 实例的对象,那么 `ClassB` 实例能做的一切,现在 `ClassA` 的实例也能做。但我们还没有在 `ClassA` 中写一行代码。我们被告知 `ClassA` 拥有一个变量 `x` 和两个函数 `Display1` 和 `Display2`,因为 `ClassB` 包含这两个函数。因此,我们进入了继承的概念,其中 `ClassB` 是基类,`ClassA` 是派生类。
让我们看另一个场景。假设我们遇到了一个情况,`ClassA` 也有一个与 `ClassB` 同名的方法。让我们也在 `ClassA` 中定义一个名为 `Derive1` 的方法,这样 `classA` 的代码将是:
class ClassA:ClassB
{
public void Display1()
{
System.Console.WriteLine("ClassA Display1");
}
}
ClassB:
class ClassB
{
public int x = 100;
public void Display1()
{
Console.WriteLine("ClassB Display1");
}
public void Display2()
{
Console.WriteLine("ClassB Display2");
}
}
现在,如果我们使用以下代码片段为 `Program.cs` 类运行应用程序:
class Program
{
static void Main(string[] args)
{
ClassA a = new ClassA();
a.Display1();
Console.ReadKey();
}
}
问题是现在会发生什么?输出是什么?会有输出或编译错误吗?好的,我们来运行它。
我们得到输出:
ClassA Display1
但你注意到一件事了吗?运行代码时,我们还收到了一条警告:
警告:'InheritanceAndPolymorphism.ClassA.Display1()
' 隐藏了继承的成员 'InheritanceAndPolymorphism.ClassB.Display1()
'。如果隐藏是故意的,请使用 `new` 关键字。
要记住的点:没有人可以阻止派生类拥有一个与其基类中已声明的同名方法。
所以,`ClassA` 无疑可以包含 `Display1` 方法,该方法已在 `ClassB` 中以相同的名称定义。
当我们调用 `a.Display1()` 时,C# 首先检查 `ClassA` 类是否有一个名为 `Display1` 的方法。如果找不到,它会在基类中查找。之前 `Display1` 方法仅在基类 `ClassB` 中可用,因此被执行。在这里,由于它在 `ClassA` 中,因此是从 `ClassA` 调用,而不是从 `ClassB` 调用。
要记住的点:派生类首先有机会执行,然后是基类。
原因是基类可能有许多方法,并且出于各种原因,我们可能对它们的功能不满意。我们应该有充分的权利调用我们自己的方法副本。换句话说,派生类的方法覆盖了基类中定义的方法。
如果我们使用派生类中的 `base` 关键字也调用基类 `Display1` 方法,即使用 `base.Display1()`,那么我们的 `ClassA` 代码将是:
ClassA:
class ClassA:ClassB
{
public void Display1()
{
Console.WriteLine("ClassA Display1");
base.Display1();
}
}
ClassB:
class ClassB
{
public int x = 100;
public void Display1()
{
Console.WriteLine("ClassB Display1");
}
public void Display2()
{
Console.WriteLine("ClassB Display2");
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
ClassA a = new ClassA();
a.Display1();
Console.ReadKey();
}
}
输出
ClassA Display1
ClassB Display1
我们看到,首先调用 `ClassA` 的 `Display1` 方法,然后调用 `ClassB` 的 `Display1` 方法。
现在,如果您想要两个类的最佳功能,您可能希望先调用基类(`ClassB`)的 `Display1`,然后再调用您的,或者反之亦然。为了实现这一点,C# 提供了一个免费关键字 `base`。`base` 关键字可以在任何派生类中使用。它意味着调用 `base` 类的那个方法。因此,`base.Display1` 将调用 `ClassA` 的基类 `ClassB` 中定义的 `Display1` 方法。
有关更多文章,请访问 A Practical Approach。
要记住的点:一个名为“`base`”的保留关键字可以在派生类中使用来调用 `base` 类的方法。
如果我们调用基类 `Display2` 方法,使用派生类 `ClassA` 的实例,会怎样?
/// <summary>
/// ClassB: acting as base class
/// </summary>
class ClassB
{
public int x = 100;
public void Display1()
{
Console.WriteLine("ClassB Display1");
}
public void Display2()
{
Console.WriteLine("ClassB Display2");
}
}
/// <summary>
/// ClassA: acting as derived class
/// </summary>
class ClassA : ClassB
{
public void Display1()
{
Console.WriteLine("ClassA Display1");
base.Display2();
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
class Program
{
static void Main(string[] args)
{
ClassA a = new ClassA();
a.Display1();
Console.ReadKey();
}
}
输出
ClassA Display1
ClassB Display2
在上面的代码中,我们只做了一个小的改动,将 `base.Display1` 替换为 `base.Display2`。在这种特定情况下,会调用 `ClassB` 类中的 `Display2` 方法。`Base` 通常用途非常广泛。它允许我们从派生类访问基类成员,如前所述。我们不能在 `ClassB` 中使用 `base`,因为根据我们的代码,`ClassB` 没有从任何类派生。所以,`base` 关键字只能在派生类中使用,对吗?
让我们看另一个例子:
/// <summary>
/// ClassB: acting as base class
/// </summary>
class ClassB
{
public int x = 100;
public void Display1()
{
Console.WriteLine("ClassB Display1");
}
}
/// <summary>
/// ClassA: acting as derived class
/// </summary>
class ClassA : ClassB
{
public void Display2()
{
Console.WriteLine("ClassA Display2");
}
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
class Program
{
static void Main(string[] args)
{
ClassB b = new ClassB();
b.Display2();
Console.ReadKey();
}
}
输出
错误:'InheritanceAndPolymorphism.ClassB
' 不包含 'Display2
' 的定义,也找不到接受 'InheritanceAndPolymorphism.ClassB
' 作为第一个参数的扩展方法 'Display2
'(您是否缺少 `using` 指令或程序集引用?)。
要记住的点:继承不能反向工作。
所以我们遇到了一个错误。因为我们看到 `ClassA` 是从 `ClassB` 派生的,也就是说 `ClassB` 是基类。因此,`ClassA` 类可以使用 `ClassB` 的所有成员。继承没有向后兼容性,`ClassA` 包含的任何成员都不会渗透到 `ClassB`。当我们尝试从 `ClassB` 类的实例访问 `classA` 的 `Display2` 方法时,它无法将其提供给 `ClassB` 类,因此会发生错误。
要记住的点:除了构造函数和析构函数,类会继承其 `base` 类中的所有内容。
如果 `ClassC` 类从 `ClassB` 类派生,而 `ClassB` 又从 `ClassA` 类派生,那么 `ClassC` 将继承 `ClassB` 中声明的所有成员以及 `ClassA` 的成员。这被称为继承中的传递概念。派生类可以继承基类的所有成员,但不能移除基类的成员。然而,派生类可以通过创建同名方法来隐藏基类的成员。基类中的原始成员/方法不会被派生类中的任何操作修改或影响。它在基类中保持不变,即在派生类中不可见。
类成员可以是两种类型,即直接属于类的 `static` 成员,或者通过类的实例访问并仅属于该特定实例的实例成员。实例成员只能通过类的对象访问,而不能直接通过类访问。类中声明的默认成员是非 `static` 的,我们只需使用 `static` 关键字使其成为 `static`。
所有类都派生自一个名为 `object` 的共同基类。所以 `Object` 是所有类的母亲。
如果我们不将任何类从任何其他类派生,C# 将负责自动向类定义添加 `:object`。`Object` 是唯一不从任何其他类派生的类。它是所有类的最终 `base` 类。
假设 `ClassA` 从 `ClassB` 派生,就像我们的情况一样,但 `ClassB` 没有从任何类派生,
public class ClassB
{
}
public class ClassA : ClassB
{
}
C# 自动将 `:object` 添加到 `ClassB`,即编译时代码变为:
public class ClassB:object
{
}
public class ClassA : ClassB
{
}
但根据理论,我们说 `ClassB` 是 `ClassA` 的直接基类,所以 `ClassA` 的类是 `ClassB` 和 `object`。
我们来看另一个例子:
public class ClassW : System.ValueType
{
}
public class ClassX : System.Enum
{
}
public class ClassY : System.Delegate
{
}
public class ClassZ : System.Array
{
}
在这里,我们定义了四个类,每个类都派生自 C# 中的一个内置类,让我们运行代码。
我们得到很多编译时错误。
错误
'InheritanceAndPolymorphism.ClassW' cannot derive from special class 'System.ValueType'
'InheritanceAndPolymorphism.ClassX' cannot derive from special class 'System.Enum'
'InheritanceAndPolymorphism.ClassY' cannot derive from special class 'System.Delegate'
'InheritanceAndPolymorphism.ClassZ' cannot derive from special class 'System.Array'
不要害怕。
你注意到“特殊类”这个词了吗?我们定义的类不能从 C# 的特殊内置类继承。
要记住的点:在 C# 的继承中,自定义类不能派生自 C# 的特殊内置类,如 `System.ValueType`、`System.Enum`、`System.Delegate`、`System.Array` 等。
再举一个例子:
public class ClassW
{
}
public class ClassX
{
}
public class ClassY : ClassW, ClassX
{
}
在上述情况下,我们看到三个类:`ClassW`、`ClassX` 和 `ClassY`。`ClassY` 从 `ClassW` 和 `ClassX` 派生。现在,如果我们运行代码,会得到什么?
编译时错误:类 'InheritanceAndPolymorphism.ClassY
' 不能有多个基类:'InheritanceAndPolymorphism.ClassW
' 和 'ClassX
'。
所以再多一个要记住的点:在 C# 中,一个类只能派生自一个类。C# 不支持通过 `class` 实现的多重继承。
*C# 中的多重继承可以通过使用接口来实现,本文不讨论接口。
我们不允许从一个以上的类派生,因此每个类只能有一个基类。
另一个例子:
假设我们尝试编写如下代码:
public class ClassW:ClassY
{
}
public class ClassX:ClassW
{
}
public class ClassY : ClassX
{
}
代码非常易读且简单:`ClassW` 派生自 `ClassY`,`ClassX` 派生自 `ClassW`,而 `ClassY` 又派生自 `ClassX`。所以没有多重继承的问题,我们的代码应该可以成功构建。让我们编译代码。我们得到什么?又是编译时错误。
错误:涉及 'InheritanceAndPolymorphism.ClassX
' 和 'InheritanceAndPolymorphism.ClassW
' 的循环基类依赖。
要记住的点:C# 中的继承不允许循环依赖。`ClassX` 派生自 `ClassW`,`ClassW` 派生自 `ClassY`,而 `ClassY` 又派生自 `ClassX`,这在三个类中造成了循环依赖,这在逻辑上是不可能的。
相等实例/对象
我们直接开始一个实际的例子:
ClassB:
public class ClassB
{
public int b = 100;
}
ClassA:
public class ClassA
{
public int a = 100;
}
Program.cs
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
ClassA classA = new ClassA();
classA = classB;
classB = classA;
}
}
我们试图使两个类的两个对象或两个实例相等。让我们编译代码:
我们得到编译时错误:
Error(错误)
Cannot implicitly convert type 'InheritanceAndPolymorphism.ClassB' to 'InheritanceAndPolymorphism.ClassA'
Cannot implicitly convert type 'InheritanceAndPolymorphism.ClassA' to 'InheritanceAndPolymorphism.ClassB'
`InheritanceAndPolymorphism` 是我在控制台应用程序中使用的命名空间,所以不必害怕这个词,忽略它即可。
C# 是遵循规则的,它永远不会允许你使两个不同独立类的对象相等。因此,我们不能使 `ClassA` 的对象 `classA` 等于 `ClassB` 的对象 `classB`,反之亦然。无论类包含相似的结构并且它们的变量被初始化为相似的整数值,即使我们这样做。
public class ClassB
{
public int a = 100;
}
public class ClassA
{
public int a = 100;
}
我只是将 `ClassB` 的 `int b` 改为 `int a`。在这种情况下,仍然不允许也不可能使对象相等。
C# 在处理数据类型时也非常谨慎。
不过,有一种方法可以做到这一点。通过我们将要讨论的方法,其中一个错误将消失。我们唯一被允许使不兼容数据类型相等的时候就是当我们从它们派生的时候?查看下面提到的代码。当我们通过声明 `new` 来创建一个 `ClassB` 的对象时,我们一次创建了两个对象:一个看起来像 `ClassB`,另一个看起来像 `object`,即派生自 `Object` 类(即最终基类)。C# 中的所有类最终都派生自 `object`。由于 `ClassA` 派生自 `ClassB`,当我们声明 `new ClassA` 时,我们创建了 3 个对象:一个看起来像 `ClassB`,一个看起来像 `ClassA`,最后是看起来像 `object` 类。
public class ClassB
{
public int b = 100;
}
public class ClassA:ClassB
{
public int a = 100;
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
ClassA classA = new ClassA();
classA = classB;
classB = classA;
}
}
我们只是将 `ClassA` 派生自 `ClassB`,这是我们可以做到的,我们在本文中学到了很多关于此的内容。现在编译代码,我们得到:
错误:无法隐式转换类型 'InheritanceAndPolymorphism.ClassB
' 到 'InheritanceAndPolymorphism.ClassA
'。存在显式转换(您是否缺少转换?)。
正如我所提到的,C# 对对象的相等性非常讲究。
因此,当我们写 `classA = classB` 时,`classA` 看起来像 `ClassA` 实例、`ClassB` 和 `object`,而 `classB` 看起来像 `ClassB`。在 `ClassB.Result` 处匹配?没有错误 :-) 。即使 `classB` 和 `classA` 具有相同的值,使用 `classB` 我们只能访问 `ClassB` 的成员,即使我们使用 `classA` 也可以访问 `ClassA`。我们已经降低了 `classB` 的效力。错误发生在 `classA = classB`。`ClassA` 类包含 `ClassB` 并且更多。我们不能让右侧是基类,左侧是派生类。`classB` 仅代表 `ClassB`,而 `classA` 需要一个 `ClassA`,它是一个 `ClassA` 并且是 `ClassB`。
要记住的点:我们可以让一个基类对象等于一个派生类对象,但不能反之。
另一个代码片段:
public class ClassB
{
public int b = 100;
}
public class ClassA:ClassB
{
public int a = 100;
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
ClassA classA = new ClassA();
classB=classA;
classA = (ClassA)classB;
}
}
尽管我们违反了 C# 关于对象相等的规则,但由于我们对对象进行了转换,因此没有收到编译器错误。`A()` 被称为转换。在括号内,放置类的名称。转换基本上是一个很好的“抹平器”。当我们打算写 `classA = classB` 时,C# 期望等号右侧是 `classA`,即 `ClassA` 实例。但它找到了 `classB`,即 `ClassB` 实例。因此,当我们应用转换时,我们实际上试图将 `ClassB` 的实例转换为 `ClassA` 的实例。这种方法满足了 C# 关于相等类型对象的规则。请记住,这仅在 `classB` 变成 `ClassA` 而不是 `ClassB` 的那一行上有效。
现在,如果我们像下面的代码一样,移除 `ClassB` 作为 `ClassA` 的基类,并尝试将 `classA` 转换为 `ClassB` 对象。
public class ClassB
{
public int b = 100;
}
public class ClassA // Removed ClassB as base class
{
public int a = 100;
}
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
ClassB classB = new ClassB();
ClassA classA = new ClassA();
classB = (ClassB)classA;
classA = (ClassA)classB;
}
}
输出
Error(错误)
Cannot convert type 'InheritanceAndPolymorphism.ClassA' to 'InheritanceAndPolymorphism.ClassB'
Cannot convert type 'InheritanceAndPolymorphism.ClassB' to 'InheritanceAndPolymorphism.ClassA'
*'InheritanceAndPolymorphism
':我在应用程序中使用的命名空间,所以请忽略它。
所以我们看到,只有当两个类中的一个派生自另一个时,转换才起作用。我们不能将任何两个对象相互转换。
最后一个例子:
/// <summary>
/// Program: used to execute the method.
/// Contains Main method.
/// </summary>
public class Program
{
private static void Main(string[] args)
{
int integerA = 10;
char characterB = 'A';
integerA = characterB;
characterB = integerA;
}
}
我们运行代码。
输出
错误:无法隐式转换类型 'int
' 到 'char
'。存在显式转换(您是否缺少转换?)。
要记住的点:我们不能隐式地将 `int` 转换为 `char`,但 `char` 可以转换为 `int`。
结论
在我们文章系列的这一部分,我们学习了继承。我们背靠背地研究了各种场景和实际示例,以深入理解该概念。在我的下一篇文章中,我们将讨论运行时多态。继承在运行时多态中起着非常重要的作用。
让我们列出所有需要记住的点:
- 没有人可以阻止派生类拥有一个与其基类中已声明的同名方法。
- 派生类首先有机会执行,然后是基类。
- 一个名为“`base`”的保留关键字可以在派生类中使用来调用 `base` 类的方法。
- 继承不能反向工作。
- 除了构造函数和析构函数,类会继承其 `base` 类中的所有内容。
- 在 C# 的继承中,自定义类不能派生自 C# 的特殊内置类,如 `System.ValueType`、`System.Enum`、`System.Delegate`、`System.Array` 等。
- 在 C# 中,一个类只能派生自一个类。C# 不支持通过类实现的多重继承。
- C# 的继承不允许循环依赖。`ClassX` 派生自 `ClassW`,`ClassW` 派生自 `ClassY`,而 `ClassY` 又派生自 `ClassX`,这在三个类中造成了循环依赖,这在逻辑上是不可能的。
- 我们可以让一个基类对象等于一个派生类对象,但不能反之。
- 我们不能隐式地将 `int` 转换为 `char`,但 `char` 可以转换为 `int`。
您可以在我的系列第一篇文章中阅读有关编译时多态的内容。请继续编码和学习。
我的其他系列文章
MVC: https://codeproject.org.cn/Articles/620195/Learning-MVC-Part-Introduction-to-MVC-Architectu
RESTful WebAPIs: https://codeproject.org.cn/Articles/990492/RESTful-Day-sharp-Enterprise-Level-Application
祝您编码愉快!