继承(C# 编程指南)

更新: 2008 年 7 月

继承(加上封装和多态性)是面向对象的编程的三个主要特性(“支柱”)之一。继承用于创建可重用、扩展和修改在其他类中定义的行为的新类。其成员被继承的类称为“基类”,继承这些成员的类称为“派生类”。

说明:

结构不支持继承,但可以实现接口。有关更多信息,请参见接口(C# 编程指南)

从概念上来说,派生类是基类的专用化。例如,如果您有一个基类 Animal,则可以有一个名为 Mammal 的派生类和一个名为 Reptile 的派生类。Mammal 是一个 Animal,Reptile 也是一个 Animal,但每个派生类均表示基类的不同专用化。

定义一个类从其他类派生时,派生类隐式获得基类的除构造函数和析构函数以外的所有成员。因此,派生类可以重用基类中的代码而无需重新实现这些代码。可以在派生类中添加更多成员。派生类以这种方式扩展基类的功能。

下图演示一个 WorkItem 类,该类表示某业务流程中的一个工作项。和所有的类一样,该类派生自 System.Object 并继承其所有方法。WorkItem 添加了自己的五个成员。其中包括一个构造函数,因为构造函数不能继承。ChangeRequest 继承自 WorkItem 并表示特定种类的工作项。ChangeRequest 在它从 WorkItem 和 Object 继承的成员中另外添加了两个成员。它必须添加自己的构造函数,还要添加一个成员以实现 ChangeRequest 与应用更改的原始 WorkItem 之间的关联。

类继承

类继承

下面的示例演示如何以 C# 表示上图所示的类关系。该示例还演示 WorkItem 如何重写虚方法 Object.ToString 以及 ChangeRequest 类如何继承该方法的 WorkItem 实现。

// WorkItem implicitly inherits from Object class
public class WorkItem
{
    private static int nextID;
    protected int ID { get; set; }
    protected TimeSpan jobLength { get; set; }
    protected string Title { get; set; }
    protected string Description { get; set; }
    // Default constructor
    public WorkItem() 
    {
        ID = 0;
        Title = "Default title";
        Description = "Default description.";
        jobLength = new TimeSpan();
    }
    // Static constructor for static member.
    static WorkItem()
    {
        nextID = 0;
    }
    // Instance constructor.
    public WorkItem( string title, string desc, TimeSpan joblen)
    {
        this.ID = GetNextID();                
        this.Title = title;
        this.Description = desc;
        this.jobLength = joblen;
    }
    protected int GetNextID()
    {
       return ++nextID;
    }
    public void Update(string title, TimeSpan joblen)
    {
        this.Title = title;
        this.jobLength = joblen;
    }

    // Virtual method override.
    public override string ToString()
    {
        return String.Format("{0} - {1}", this.ID, this.Title); 
    }
}

// ChangeRequest derives from WorkItem and adds two of its own members.
public class ChangeRequest : WorkItem
{
    protected int originalItemID {get; set;}
    public ChangeRequest() { }
    public ChangeRequest(string title, string desc, TimeSpan jobLen, int originalID)
    {
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = jobLen;
        this.originalItemID = originalID;
    }
}

class Program
{
    static void Main()
    {
        WorkItem item = new WorkItem(                                
                        "Fix Bugs", 
                        "Fix all bugs in my source code branch",
                        new TimeSpan(3, 4, 0, 0));

        ChangeRequest change = new ChangeRequest("Change design of base class",
                                                 "Add members to base class",
                                                 new TimeSpan(4, 0, 0),
                                                 1);

        Console.WriteLine(item.ToString());

        // ChangeRequest inherits WorkItem's override of ToString
        Console.WriteLine(change.ToString()); 

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    1 - Fix Bugs
    2 - Change design of base class
*/

抽象方法和虚方法

当基类将方法声明为 virtual 时,派生类可以用自己的实现重写该方法。如果基类将成员声明为 abstract,则在直接继承自该类的任何非抽象类中都必须重写该方法。如果派生类自身是抽象的,则它继承抽象成员而不实现它们。抽象成员和虚成员是多态性的基础,多态性是面向对象的编程的第二个主要特性。有关更多信息,请参见多态性(C# 编程指南)

抽象基类

如果希望禁止通过 new 关键字直接进行实例化,可以将类声明为 abstract。如果这样做,则仅当从该类派生新类时才能使用该类。抽象类可以包含一个或多个自身声明为抽象的方法签名。这些签名指定参数和返回值,但没有实现(方法体)。抽象类不必包含抽象成员;但是,如果某个类确实包含抽象成员,则该类自身必须声明为抽象类。自身不是抽象类的派生类必须为抽象基类中的任何抽象方法提供实现。有关更多信息,请参见抽象类、密封类及类成员(C# 编程指南)抽象类设计

接口

“接口”是一种引用类型,有点像仅包含抽象成员的抽象基类。类在从接口派生时必须为该接口的所有成员提供实现。类虽然只能从一个直接基类派生,但可以实现多个接口。

接口用于为不一定具有“是”关系的类定义特定功能。例如,如果任何类或结构需要让客户端代码判断某类型的两个对象是否相等(无论该类型如何定义相等性),那么该类或结构就可以实现 IEquatable[`1] 接口。IEquatable<T> 不表示基类和派生类之间存在的同一种“是”关系(例如 Mammal 是 Animal)。有关更多信息,请参见接口(C# 编程指南)

派生类对基类成员的访问

派生类可以访问基类的公共成员、受保护成员、内部成员和受保护内部成员。即使派生类继承基类的私有成员,仍不能访问这些成员。但是,所有这些私有成员在派生类中仍然存在,且执行与基类自身中相同的工作。例如,假定一个受保护基类方法访问私有字段。要使继承的基类方法正常工作,派生类中必须有该字段。

禁止进一步派生

类可以将自身或其成员声明为 sealed,从而禁止其他类从该类自身或其任何成员继承。有关更多信息,请参见抽象类、密封类及类成员(C# 编程指南)

派生类隐藏基类成员

派生类可以通过以相同的名称和签名声明基类成员来隐藏这些成员。可以使用 new 修饰符显式指示成员不作为基类成员的重写。不是必须要使用 new,但如果不使用 new,将生成编译器警告。有关更多信息,请参见使用 Override 和 New 关键字进行版本控制(C# 编程指南)了解何时使用 Override 和 New 关键字(C# 编程指南)

请参见

概念

C# 编程指南

参考

类和结构(C# 编程指南)

class(C# 参考)

struct(C# 参考)

修订记录

日期

修订记录

原因

2008 年 7 月

增加了内容、图示和新示例。

信息补充。