c#编程
类比解释
继承的好处是什么?
- 它节省了大量的打字工作
- 它避免了代码的重复
通过类比解释继承
假设你想创建一只鹰、一只猎鹰和一只秃鹰。为了创建这些飞行生物,你会注意到每种生物都有相同的特征:
- 飞行
- 繁殖
- 吃饭
假设我们假设这三种鸟:鹰、猎鹰和秃鹰,飞行、繁殖和吃饭的方式完全相同。
如果没有继承,你将不得不重复代码。例如,控制鹰飞行的代码将会被复制到秃鹰的飞行功能中。对于程序员来说,这是个常识——我们不喜欢重复代码——重复代码几乎总是一个坏主意。
但注意,鹰、猎鹰和秃鹰实际上都是鸟。因此,你可以说鸟类一般具有吃、繁殖和飞行的特征。所以,使用“继承”你可以创建一个通用的“鸟”类原型,该原型包含飞行、繁殖和吃饭的功能。然后,一旦这个原型被定义,你可以让其他所有特定品种的鸟类继承这些特征。换句话说,使用这个原型,你可以在它的基础上设计出其他特定种类的鸟类。
这意味着,猎鹰会自动知道如何飞行,因为它继承了来自通用“鸟”类的行为。你基本上不需要重复代码。
继承
继承是从另一个类(“父类”)创建一个新类的能力,扩展父类的功能和状态到派生类(或“子类”)。它允许派生类重载其父类的方法。
继承是面向对象编程的支柱之一。它是一种从另一个类设计新类的机制,并且是代码重用的一个概念,支持层级分类的概念。C# 程序由类组成,其中新类可以从头开始创建,或者通过使用现有类的一部分或全部属性来创建。
与继承和代码重用相关的另一个特性是多态,它允许相同的方法名在不同的数据类型上执行不同的操作。因此,C# 通过这两种特性来支持代码的重用。
继承的主要特性包括:
- 派生类扩展其基类。也就是说,它包含父类的方法和数据,同时也可以包含自己特有的数据成员和方法。
- 派生类不能更改继承成员的定义。
- 构造函数和析构函数不被继承,其他基类成员会被继承。
- 派生类中成员的可访问性取决于其在基类中声明的可访问性。
- 派生类可以重写继承来的成员。
继承示例:
using System;
using System.Text;
namespace ContainmentInheritance
{
class Room
{
public int length;
public int width;
public int height;
public Room(int l, int w, int h)
{
length = l;
width = w;
height = h;
}
}
class Home
{
int numberOfRooms;
int plotSize;
string locality;
string name;
// 在 Home 类中创建一个 Room 类的对象
Room studyRoom = new Room(10, 12, 12);
public Home()
{
numberOfRooms = 1;
plotSize = 1000;
locality = "Versova";
name = "study room";
}
public void Display()
{
Console.WriteLine("MyHome has {0} rooms", numberOfRooms);
Console.WriteLine("Plot size is {0}", plotSize);
Console.WriteLine("Locality is {0}", locality);
int area = studyRoom.length * studyRoom.width;
Console.WriteLine("Area of the {0} room is {1}", name, area);
}
}
class Program
{
static void Main(string[] args)
{
Home myhome = new Home();
myhome.Display();
Console.ReadLine();
}
}
}
在这个示例中:
Room类表示房间,它包含长度、宽度和高度等属性。Home类表示一个房子,它包含一个Room对象studyRoom,并通过构造函数来初始化其属性。Display方法用于展示房子的相关信息,包括房间的面积。
总结
继承使得你可以创建类的层次结构,避免了重复代码,并且使得不同类之间能够共享相同的行为和属性。
子类型继承
子类型继承指的是派生类继承父类的属性和方法,并可以重写(override)父类的某些方法以实现不同的功能。在C#中,使用virtual关键字声明可以被重写的方法,使用override关键字在派生类中实现新逻辑。
示例:Employee 类和 Executive 类
在以下代码示例中,我们展示了一个基本的员工类 Employee 和一个继承自 Employee 的执行者类 Executive,执行者类重写了 GetPayCheck 方法,并添加了一个额外的方法 AdministerEmployee。
public class Employee
{
// 声明一个方法为虚方法,以便子类 Executive 可以重写它。
public virtual void GetPayCheck()
{
// 获取薪水的逻辑
Console.WriteLine("Employee paycheck logic.");
}
// 员工和执行者都会工作,所以这里不需要虚方法
public void Work()
{
// 工作逻辑
Console.WriteLine("Employee working.");
}
}
public class Executive : Employee
{
// 使用 override 关键字表示我们想要为 GetPayCheck 方法提供新的实现
public override void GetPayCheck()
{
// 执行者的获取薪水逻辑
Console.WriteLine("Executive paycheck logic.");
}
// 实现额外的方法
public void AdministerEmployee()
{
// 管理员工的逻辑
Console.WriteLine("Executive administering employee.");
}
}
public class Program
{
static void Main(string[] args)
{
Employee emp = new Employee();
Executive exec = new Executive();
emp.Work(); // Employee 工作
exec.Work(); // Executive 继承 Employee 的 Work 方法
emp.GetPayCheck(); // Employee 的薪水获取逻辑
exec.GetPayCheck(); // Executive 的薪水获取逻辑
exec.AdministerEmployee(); // Executive 的管理员工逻辑
}
}
代码解析:
Employee类:它包含两个方法,一个是GetPayCheck(虚方法),另一个是Work(普通方法)。GetPayCheck方法被声明为virtual,允许派生类重写它。Work方法不需要重写,因为Employee和Executive都可以使用这个方法。Executive类:它继承自Employee,并使用override关键字重写了GetPayCheck方法。它还添加了一个新的方法AdministerEmployee,该方法只在Executive类中存在。
在 Main 方法中,创建了 Employee 和 Executive 对象,调用了他们的 Work 和 GetPayCheck 方法,演示了继承和重写的行为。
虚方法与多态性
当基类包含一个虚方法,并且派生类重写了该方法时,基类在调用该方法时,会实际调用派生类的重写版本。这是面向对象编程中多态性的一种表现。
示例:虚方法与多态
在这个示例中,基类 Resource 定义了一个虚方法 Close,并在 Dispose 方法中调用它。如果派生类 AnotherTypeOfResource 重写了 Close 方法,当基类的 Dispose 方法被调用时,实际调用的是派生类的 Close 方法。
public class Resource : IDisposable
{
private bool _isClosed = false; // 良好的编程实践,初始化变量,尽管默认值也可以
protected virtual void Close()
{
Console.WriteLine("Base resource closer called!");
}
~Resource()
{
Dispose();
}
public void Dispose()
{
if (!_isClosed)
{
Console.WriteLine("Disposing resource and calling the Close() method...");
_isClosed = true;
Close();
}
}
}
public class AnotherTypeOfResource : Resource
{
// 重写基类的 Close 方法
protected override void Close()
{
Console.WriteLine("Another type of resource closer called!");
}
}
public class VirtualMethodDemo
{
public static void Main()
{
Resource res = new Resource();
AnotherTypeOfResource res2 = new AnotherTypeOfResource();
res.Dispose(); // 调用 Resource.Close()
res2.Dispose(); // 即使 Dispose() 是 Resource 类的一部分,
// 它会调用 AnotherTypeOfResource.Close()!
}
}
代码解析:
Resource类:包含一个虚方法Close,并在Dispose方法中调用它。Dispose方法用于释放资源并调用Close方法进行清理。AnotherTypeOfResource类:重写了Close方法,改变了Close的行为。Dispose方法:尽管Dispose是Resource类的一部分,但由于Close方法是虚方法,并且在派生类中被重写,实际调用的是派生类中的Close方法。
总结
- 继承允许子类重用父类的代码,并可以根据需要重写(override)父类的方法以提供不同的实现。
- **虚方法(virtual method)**使得父类的方法可以被子类重写,提供多态性支持。父类调用虚方法时,会实际调用子类中重写后的方法。
构造函数
派生类不会自动继承基类的构造函数,除非它提供自己的构造函数。在实例化派生类时,必须通过使用 base 关键字调用基类的构造函数。
示例:
public class MyBaseClass
{
public MyBaseClass(string text)
{
Console.WriteLine(text);
}
}
public class MyDerivedClass : MyBaseClass
{
public MyDerivedClass(int number)
: base(number.ToString()) // 调用基类的构造函数
{ }
public MyDerivedClass(string text) // 即使与 MyBaseClass 的构造函数相同,仍然需要定义
: base(text) // 调用基类的构造函数
{ }
}
在上述示例中,MyDerivedClass 类提供了两个构造函数,并且通过 base 关键字调用了 MyBaseClass 的构造函数。即使 MyDerivedClass 的构造函数参数与基类的构造函数相同,它们也不能直接继承父类的构造函数,因此需要显式地调用父类构造函数。
继承关键字
在 C# 中,使用 : 操作符来表示从另一个类继承。
示例:
public class Executive : Employee
这表示 Executive 类继承自 Employee 类。
如果要标记一个方法为可以被重写的,使用 virtual 关键字。
示例:
public virtual void Write(string text)
{
System.Console.WriteLine("Text: {0}", text);
}
要重写一个方法,使用 override 关键字:
示例:
public override void Write(string text)
{
System.Console.WriteLine(text);
}
如果派生类的方法缺少 new 或 override 关键字,可能会导致编译时的错误或警告。
示例:
abstract class ShapesA
{
abstract public int Area(); // 抽象方法!
}
class Square : ShapesA
{
int x, y;
public int Area() // 错误:缺少 'override' 或 'new'
{
return x * y;
}
}
class Shapes
{
virtual public int Area() { return 0; } // 现在是虚方法!
}
class Square : Shapes
{
int x, y;
public int Area() // 不需要显式的 'override' 或 'new'
{
return x * y;
}
}
编译错误与警告
-
从抽象类继承时:
如果
Square类从ShapesA类继承并且没有使用override关键字来重写Area方法,编译时会产生错误:error CS0534: 'ConsoleApplication3.Square' does not implement inherited abstract member 'ConsoleApplication3.Shapes.Area()' -
从虚方法类继承时:
如果
Square类从Shapes类继承且没有使用override关键字来重写Area方法,则会产生编译警告:warning CS0114: 'ConsoleApplication3.Square.Area()' hides inherited member 'ConsoleApplication3.Shapes.Area()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.这是因为
Area方法在基类Shapes中是虚方法(virtual),而Square类中的Area方法没有明确使用override或new关键字。如果希望Square类中的Area方法重写基类的方法,必须加上override关键字;如果不想覆盖基类的方法,可以使用new关键字来隐藏它。