继承(inherit)是面向对象编程中一个非常重要的概念,其主要功能就是对已有代码的重复利用,以达到简化开发、灵活扩展的目的。

类的继承体系中,最基本的概念是继承与被继承的关系。我们先来看下面的代码。

using System;

namespace ConsoleTest
{
    public class Ca
    {
        public string Name { get; set; }
        public void DoWork()
        {
            Console.WriteLine("Ca.DoWork");
        }
    }
}

下面,我们定义一个Cb类,它继承于Ca类,如下面的代码。

using System;

namespace ConsoleTest
{
    public class Cb : Ca
    {
    }
}

我们可以看到,Cb类的定义,“Cb : Ca”中的冒号(:)在这里的含义就是继承;接下来,我们在Program.cs文件中测试Cb类的使用,如下面的代码。

static void Main(string[] args)
{
        Cb b = new Cb();
        b.DoWork();
}

代码执行结果如下图所示。

enter image description here

从这个示例中,我们可以看到,Cb类并没有定义任何成员,但其对象可以调用DoWork()方法,而这个方法就是从Ca类中继承而来的。此时,Ca就是Cb的父类(parent class),也可以称为超类(super class)或基类(base class);而Cb则是Ca的子类(sub class),它继承于Ca类。

说到C#中的继承,我们不提到Object类,它之所以特殊是因为在整个.NET Framework架构中,Object类是唯一一个没有父类的类,而它也是其他所有类型的最终极父类,当一个类没有指定父类时,它实际就继承于Object类。也就是说,在.NET Framework架构中的所有类型都可以使用Object类中的非私有成员,比如GetType()方法、Equals()方法、ToString()方法等,当然,在子类中也可以修改父类成员的实现,从而更适合工作。

实际上,在这里的Object类、Ca类和Cb的继承关系就是Cb类继承于Ca类、Ca类继承于Object类,这样,我们就可以在Cb类的对象中调用ToString()方法了,如下面的代码。

static void Main(string[] args)
{
        Cb b = new Cb();
        Console.WriteLine(b.ToString());
}

代码会显示b对象的类型,如下图所示。

enter image description here

实际应用中,如果一个类不允许被继承,可以在定义是使用sealed关键将其“密封”,如下面的代码。

public sealed class Ca

在我们在定义Ca类时添加sealed关键字后就会出现Cb不能继承Ca的提示,如下图所示。

enter image description here

现在,我们已经了解了关于继承的一些基本概念,接下来将讨论在使用继承关系时需要注意的地方。

构造函数

前面的课程,我们已经了解了如何灵活地创建构造函数,只是在继承关系中,又多了一些选择,下面的代码,首先在Ca类中创建两个构造函数,如下面的代码。

using System;

namespace ConsoleTest
{
    public class Ca
    {
        // 构造函数
        public Ca(string sName)
        {
            Name = sName;
        }
        //
        public Ca() : this("noname") { }
        //
        public string Name { get; set; }
        public void DoWork()
        {
        vConsole.WriteLine("Ca.DoWork");
        }
    }
}

默认情况下,在Cb类中只会继承Ca类的无参数构造函数,如下面的代码。

static void Main(string[] args)
{
        Cb b = new Cb();
        Console.WriteLine(b.Name);
}

代码会显示noname。但是,如果我们使用Cb b = new Cb("Tom");代码创建Cb类的对象就会出现错误,如下图所示。

enter image description here

下面的代码,我们通过Ca类中包含一个参数的构造函数来快速创建Cb类中的构造函数。

using System;

namespace ConsoleTest
{
    public class Cb : Ca
    {
        // 构造函数
        public Cb(string sName) : base(sName) { }
        public Cb() : this("Cb_noname") { }
    }
}

这里,我们通过:符号和base(sName)调用了Cb父类(基类),也就是Ca类的单参数构造函数,再次执行Cb b = new Cb("Tom");就没有问题了。请注意,当添加了任何有参数构造函数后,就不会自动生成无参数的构造函数,如果有需要再次添加,就像上面的代码一样。

这里,我们使用了base和this关键字,大家应注意,this表示当前实例,而base则用于调用父类(基类)中的非私有成员。

成员的访问级别

访问级别的概念,在前面的课程中已经讨论过,具体到类的继承关系中需要注意的是,定义在父类中的非私有成员,在其子类中可以使用base关键字访问;这里,父类的非私有成员在大多数情况下是指定义为受保护(protected)或公共(public)的成员。

抽象类及重写

抽象类不能被实例化,也就是说,我们不能创建抽象类类型的对象。在C#中,定义抽象类时需要使用abstract关键字,如下面的代码,我们创建了Cx类,它被定义为抽象类。

using System;

namespace ConsoleTest
{
    public abstract class Cx
    {
        public abstract string Name { get; set; }
        public abstract void DoWork();
        //
        public void SayHello()
        {
            Console.WriteLine("Hello, {0}.", Name);
        }
    }
}

Cx类中共定义了三个成员,包括抽象属性Name、抽象方法DoWork()和非抽象方法SayHello()。抽象类是不能创建实例的,所以,代码Cx x = new Cx();是不能执行的。

实际应用中,抽象类可以用于定义一系列类型的基本结构,其中可以包含一些共性操作;对于通用的操作可以定义为非抽象成员,如Cx类中的SayHello()方法,一些需要子类具体实现的成员则定义为抽象成员,如Cx类中的DoWork()方法等。需要注意的是,如果类中定义了一个抽象成员,那么,类就必须定义为抽象类。

下面的代码,我们创建两个Cx类的子类,分别是Cx1类和Cx2类。

//
public class Cx1 : Cx
{
    public override string Name { get; set; }
    public override void DoWork()
    {
        Console.WriteLine("Cx1.DoWork()");
    }
}
//
public class Cx2 : Cx
{
    public override string Name { get; set; }
    public override void DoWork()
    {
        Console.WriteLine("Cx2.DoWork()");
    }
}

在重写抽象成员时,需要使用override关键字,如上述代码所示。下面的代码,我们使用Cx1和Cx2类。

static void Main(string[] args)
{
        Cx1 x1 = new Cx1() { Name = "Tom" };
        Cx2 x2 = new Cx2() { Name = "Jerry" };
        x1.SayHello();
        x1.DoWork();
        x2.SayHello();
        x2.DoWork();
}

代码执行结果如下图所示。

enter image description here

虚拟成员及重写

在类中定义为虚拟成员时需要使用virtual关键字,与抽象成员不同,虚拟成员可以有自己的实现,也可以在子类进行重写,如下面的代码,我们定义Cy类。

using System;

namespace ConsoleTest
{
    public class Cy
    {
        public void DoWork1()
        {
            Console.WriteLine("Cy.DoWork1");
        }
        //
        public virtual void DoWork2()
        {
            Console.WriteLine("Cy.DoWork2");
        }
    }
}

请注意,Cy类中的DoWork2()方法使用了virtual关键字,而DoWork1()方法没有使用virtual关键字。下面的代码,我们定义Cy类的子类Cy1类。

using System;

namespace ConsoleTest
{
    public class Cy1 : Cy
    {
        new public void DoWork1()
        {
            Console.WriteLine("Cy1.DoWork1");
        }
        //
        public override void DoWork2()
        {
            base.DoWork2();
            Console.WriteLine("Cy1.DoWork2");
        }
    }
}

这里,我们定义的Cy1类作为Cy类的子类,其中包含同名的DoWork1()方法和DoWork2()方法,请注意它们的不同点。

Cy类中的DoWork1()方法定义时没有使用virtual关键字,在其子类Cy1类中,如果需要重写同名的方法,就应该在方法定义时使用new关键字,明确这是一个全新实现的同名成员。

Cy类中的DoWork2()方法定义时使用了virtual关键字,在其子类Cy1类中,则可以通过override关键字指明是在重写父类中的方法。

下面的代码,我们测试Cy1类的使用。

static void Main(string[] args)
{
        Cy1 y1 = new Cy1();
        y1.DoWork1();
        y1.DoWork2();
}

代码执行结果如下图所示。

enter image description here

CHY软件小屋原创作品!