继承是面向对象程序设计中最重要的概念之一。继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易。同时也有利于重用代码和节省开发时间。
当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承了已有的类的成员即可。这个已有的类被称为的基类,这个新的类被称为派生类。
继承的思想实现了 属于(IS-A) 关系。例如,哺乳动物 属于(IS-A) 动物,狗 属于(IS-A) 哺乳动物,因此狗 属于(IS-A) 动物。
基类和派生类
一个类可以派生自多个类或接口,这意味着它可以从多个基类或接口继承数据和函数。
C# 中创建派生类的语法如下:
<acess-specifier> class <base_class> { ... } class <derived_class> : <base_class> { ... }假设,有一个基类 Shape,它的派生类是 Rectangle:
using System; namespace InheritanceApplication { class Shape { public void setWidth(int w) { width = w; } public void setHeight(int h) { height = h; } protected int width; protected int height; } // 派生类 class Rectangle: Shape { public int getArea() { return (width * height); } } class RectangleTester { static void Main(string[] args) { Rectangle Rect = new Rectangle(); Rect.setWidth(5); Rect.setHeight(7); // 打印对象的面积 Console.WriteLine("总面积: {0}", Rect.getArea()); Console.ReadKey(); } } }当上面的代码被编译和执行时,它会产生下列结果:
总面积: 35
基类的初始化
派生类继承了基类的成员变量和成员方法。因此父类对象应在子类对象创建之前被创建。您可以在成员初始化列表中进行父类的初始化。
下面的程序演示了这点:
using System; namespace RectangleApplication { class Rectangle { // 成员变量 protected double length; protected double width; public Rectangle(double l, double w) { length = l; width = w; } public double GetArea() { return length * width; } public void Display() { Console.WriteLine("长度: {0}", length); Console.WriteLine("宽度: {0}", width); Console.WriteLine("面积: {0}", GetArea()); } }//end class Rectangle class Tabletop : Rectangle { private double cost; public Tabletop(double l, double w) : base(l, w) { } public double GetCost() { double cost; cost = GetArea() * 70; return cost; } public void Display() { base.Display(); Console.WriteLine("成本: {0}", GetCost()); } } class ExecuteRectangle { static void Main(string[] args) { Tabletop t = new Tabletop(4.5, 7.5); t.Display(); Console.ReadLine(); } } }当上面的代码被编译和执行时,它会产生下列结果:
长度: 4.5 宽度: 7.5 面积: 33.75 成本: 2362.5
C# 多重继承
多重继承指的是一个类别可以同时从多于一个父类继承行为与特征的功能。与单一继承相对,单一继承指一个类别只可以继承自一个父类。
C# 不支持多重继承。但是,您可以使用接口来实现多重继承。下面的程序演示了这点:
using System; namespace InheritanceApplication { class Shape { public void setWidth(int w) { width = w; } public void setHeight(int h) { height = h; } protected int width; protected int height; } // 基类 PaintCost public interface PaintCost { int getCost(int area); } // 派生类 class Rectangle : Shape, PaintCost { public int getArea() { return (width * height); } public int getCost(int area) { return area * 70; } } class RectangleTester { static void Main(string[] args) { Rectangle Rect = new Rectangle(); int area; Rect.setWidth(5); Rect.setHeight(7); area = Rect.getArea(); // 打印对象的面积 Console.WriteLine("总面积: {0}", Rect.getArea()); Console.WriteLine("油漆总成本: ${0}" , Rect.getCost(area)); Console.ReadKey(); } } }当上面的代码被编译和执行时,它会产生下列结果:
总面积: 35 油漆总成本: $2450
C# 可继承成员
C# 只支持单一继承。 也就是说,类只能继承自一个类。 不过,继承是可传递的。这样一来,就可以为一组类型定义继承层次结构。 换言之,类型 D 可继承自类型 C,其中类型 C 继承自类型 B,类型 B 又继承自基类类型 A。 由于继承是可传递的,因此类型 D 继承了类型 A 的成员。
并非所有基类成员都可供派生类继承。以下成员无法继承:
静态构造函数:用于初始化类的静态数据。
实例构造函数:在创建类的新实例时调用。 每个类都必须定义自己的构造函数。
析构函数:由运行时的垃圾回收器调用,用于销毁类实例。
C# 可访问成员
using System; public class A { private int value = 10; public class B : A { public int GetValue() { return this.value; } } } public class C : A { // public int GetValue() // { // return this.value; // } } public class Example { public static void Main(string[] args) { var b = new A.B(); Console.WriteLine(b.GetValue()); } } // The example displays the following output: // 10
受保护成员仅在派生类中可见。
内部成员仅在与基类同属一个程序集的派生类中可见, 在与基类属于不同程序集的派生类中不可见。
公共 成员在派生类中可见,并且属于派生类的公共接口。 可以调用继承的公共成员,就像它们是在派生类中定义一样。 在以下示例中,类 A 定义 Method1 方法,类 B 继承自类 A。 然后,以下示例调用 Method1,就像它是 B 中的实例方法一样。
public class A { public void Method1() { // Method implementation. } } public class B : A { } public class Example { public static void Main() { B b = new B(); b.Method1(); } }
派生类还可以通过提供重写实现代码来重写继承的成员。 基类成员必须标记有 virtual 关键字,才能重写继承的成员。 默认情况下,基类成员没有 virtual 标记,因此无法被重写。 如果尝试重写非虚成员(如以下示例所示),则会生成编译器错误 CS0506:“ 无法重写继承的成员 ,因为继承的成员没有 virtual、abstract 或 override 标记。”
public class A { public void Method1() { // Do something. } } public class B : A { public override void Method1() // Generates CS0506. { // Do something else. } }
在某些情况下,派生类必须重写基类实现代码。 标记有 abstract 关键字的基类成员要求派生类必须重写它们。 如果尝试编译以下示例,则会生成编译器错误 CS0534:“ 不实现继承的抽象成员 ,因为类 B 没有提供 A.Method1 的实现代码。”
public abstract class A { public abstract void Method1(); } public class B : A // Generates CS0534. { public void Method3() { // Do something. } }
继承仅适用于类和接口。 其他各种类型(结构、委托和枚举)均不支持继承。 因此,如果尝试编译以下代码,则会生成编译器错误 CS0527:“接口列表中的类型 'ValueType' 不是接口。” 此错误消息指明,尽管可以定义结构实现的接口,但不支持继承。
using System; public struct ValueStructure : ValueType // Generates CS0527. { }