继承是面向对象编程最重要的特性之一,它表示可以从一个类中派生出新的类,而且新类能继承基类的成员。在软件开发中,类的继承性使所建立的软件具有开放性、可扩充性等,并且增强了代码的可重用性。
在面向对象编程中,被继承的类称为父类或基类,继承的类称为子类或派生类。C#中提供了类的继承机制,但只支持单继承,而不支持多重继承,即在C#中一次只允许继承一个类,不能同时继承多个类,但是可以继承多个接口。
继承一个类时,类成员的可访问性是一个重要的问题。子类(派生类)不能访问基类的私有成员,但是可以访问其公共成员,这就是说,只要使用public声明类成员,就可以让一个类成员被基类和子类(派生类)同时访问,同时也可以被外部的代码访问。
为了解决基类成员访问问题,C#还提供了另外一种可访问性:protected,只有子类(派生类)才能访问protected成员,外部代码不能访问protected成员。
除了成员的保护级别外,还可以为成员定义其继承行为。基类的成员可以是虚拟的,成员可以由继承它的类重写。子类(派生类)可以提供成员的其他执行代码,这种执行代码不会删除原来的代码,仍可以在类中访问原来的代码,但外部代码不能访问他们。如果没有提供其他执行方式,外部代码就直接访问基类中成员的执行代码。
另外,基类还可以定义为抽象类。抽象类不能直接实例化,要使用抽象类就必须继承这个类,然后再实例化。
继承的基石——接口
接口是面向对象中一个非常重要的概念,而且,面向对象中的继承性和多态性主要都是通过接口来体现的。
1.接口概述
接口是一种用来定义程序的协议,它描述可属于任何类或结构的一组相关行为,可以把它看成是实现一组类的模板。接口可由方法、属性、事件和索引器或这4种成员类型的任何组合构成,但不能包含字段。
类和结构可以像类继承基类一样从接口继承,但是可以继承多个接口。当类或结构继承接口时,它继承成员定义但不继承实现。若要实现接口成员,类或结构中的对应成员必须是公共的、非静态的,并且与接口成员具有相同的名称和签名。类或结构的属性和索引器可以为接口中定义的属性或索引器定义额外的访问器。例如,接口可以声明一个带有get访问器的属性,而实现该接口的类可以声明同时带有get和set访问器的同一属性。但是,如果属性或索引器使用显式实现,则访问器必须匹配。
另外,接口也可以继承其他接口,类可以通过其继承的基类或接口多次继承某个接口,在这种情况下,如果将该接口声明为新类的一部分,则类只能实现该接口一次。如果没有将继承的接口声明为新类的一部分,其实现将由声明它的基类提供。基类可以使用虚拟成员实现接口成员;在这种情况下,继承接口的类可通过重写虚拟成员来更改接口行为。
综上所述,接口具有以下特征:
q 接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员;
q 不能直接实例化接口;
q 接口可以包含事件、索引器、方法和属性;
q 接口不包含方法的实现;
q 类和结构可从多个接口继承;
q 接口自身可从多个接口继承。
说明:
接口使得服务的协议与实现相分离,它是组件编程的基础,在组件编程中,接口是组件向外公布其功能的唯一方法。
2.声明接口
C#中使用interface关键字声明接口,其语法格式如下:
修饰符 interface 接口名称 :继承的接口列表
{
接口内容;
}
说明:
(1)声明接口时,通常以大写字母“I”开头;
(2)声明接口时,除interface关键字和接口名称外,其它的都是可选项;
(3)可以使用new 、public、protected、internal和private等修饰符声明接口,但接口成员必须是公共的。
例 声明一个接口,该接口中包含语言和版本两个属性,还包含一个自定义方法ShowInfo,该方法用来显示定义的语言和版本。代码如下:
interface IprogramDic //自定义接口
{
/// <summary>
/// 语言(可读可写)
/// </summary>
string Language
{
get;
set;
}
/// <summary>
/// 版本(可读可写)
/// </summary>
string Version
{
get;
set;
}
/// <summary>
/// 显示定义的语言和版本
/// </summary>
void ShowInfo();
}
3.接口的实现
接口的实现通过类继承来实现,一个类虽然只能继承一个基类,但可以继承任意多个接口。声明实现接口的类时,需要在基类列表中包含类所实现的接口的名称。
C#中实现继承的语法格式如下:
class DerivedClass: BaseClass { }
说明:
继承类时,必须在子类和基类之间用冒号(:),另外,如果继承多个接口,那么在继承的每个接口之间用逗号分割(,)。
例 创建一个控制台应用程序,该程序在上例的基础上实现,Program类继承自接口IprogramDic,并实现了该接口中的所有属性和方法,然后在Main方法中实例化Program类的一个对象,并使用该对象实例化IprogramDic接口,最后通过实例化的接口对象访问派生类中的属性和方法。代码如下:
class Program : IprogramDic //继承自接口
{
string language = "";
string version = "";
/// <summary>
/// 语言
/// </summary>
public string Language
{
get
{
return language;
}
set
{
language = value;
}
}
/// <summary>
/// 版本
/// </summary>
public string Version
{
get
{
return version;
}
set
{
version = value;
}
}
/// <summary>
/// 显示定义的语言和版本
/// </summary>
public void ShowInfo()
{
Console.WriteLine("语言\t 版本");
Console.WriteLine(Language + "\t" + Version);
}
static void Main(string[] args)
{
Program program = new Program(); //实例化Program类对象
IprogramDic iprogramdic = program; //使用派生类对象实例化接口ImyInterface
iprogramdic.Language = "C#编程词典"; //为派生类中的ID属性赋值
iprogramdic.Version = " 珍藏版"; //为派生类中的Name属性赋值
iprogramdic.ShowInfo(); //调用派生类中方法显示定义的属性值
Console.ReadLine();
}
}
显式接口成员实现
如果类实现两个接口,并且这两个接口包含具有相同签名的成员,那么在类中实现该成员时,将导致两个接口都使用该成员作为它们的实现,然而,如果两个接口成员实现不同的功能,那么这可能会导致其中一个接口的实现不正确或两个接口的实现都不正确,这时可以显式地实现接口成员,即创建一个仅通过该接口调用并且特定于该接口的类成员。显式接口成员实现是使用接口名称和一个句点命名该类成员来实现的。
例 创建一个控制台应用程序,其中声明了两个接口ImyInterface1和ImyInterface2,在这两个接口中声明了一个同名方法Add,然后定义一个类MyClass,该类继承自已经声明的两个接口,在MyClass类中实现接口中的方法时,由于ImyInterface1和ImyInterface2接口中声明的方法名相同,这里使用了显式接口成员实现,最后在主程序类Program的Main方法中使用接口对象调用接口中定义的方法。代码如下:
interface ImyInterface1
{
/// <summary>
/// 求和方法
/// </summary>
/// <returns>加法运算的和</returns>
int Add();
}
interface ImyInterface2
{
/// <summary>
/// 求和方法
/// </summary>
/// <returns>加法运算的和</returns>
int Add();
}
class myClass : ImyInterface1, ImyInterface2 //继承接口
{
/// <summary>
/// 求和方法
/// </summary>
/// <returns>加法运算的和</returns>
int ImyInterface1.Add() //显式接口成员实现
{
int x = 98;
int y = 368;
return x + y;
}
/// <summary>
/// 求和方法
/// </summary>
/// <returns>加法运算的和</returns>
int ImyInterface2.Add() //显式接口成员实现
{
int x = 98;
int y = 368;
int z = 698;
return x + y + z;
}
}
class Program
{
static void Main(string[] args)
{
myClass myclass = new myClass(); //实例化接口继承类的对象
ImyInterface1 imyinterface1 = myclass; //使用接口继承类的对象实例化接口
Console.WriteLine(imyinterface1.Add()); //使用接口对象调用接口中方法
ImyInterface2 imyinterface2 = myclass; //使用接口继承类的对象实例化接口
Console.WriteLine(imyinterface2.Add()); //使用接口对象调用接口中方法
Console.ReadLine();
}
}
注意:
(1)显式接口成员实现中不能包含访问修饰符、abstract、virtual、override或static修饰符;
(2)显式接口成员是属于接口的成员,而不是类的成员,因此,不能使用类对象直接访问,只能通过接口对象来访问。
继承的使用
程序中使用面向对象的继承特性时,主要分为单继承和多继承两种情况,下面分别进行介绍。
1.单继承
单继承一般用于类之间的继承,C#中的类只支持单继承,实现单继承时,使用“子类:基类”格式。下面通过一个实例讲解如何实现单继承。
例 创建一个控制台应用程序,首先定义一个Language类,并在其中定义一个string类型的Name属性;然后自定义一个ProgramDic类,该类继承自Language类,该类中定义了两个方法,分别用来输出“C#编程词典”和“ASP.NET编程词典”两个字符串;最后在Program主程序类中实例化派生类ProgramDic的一个对象,并通过该对象调用父类中的Name属性为其赋值,然后通过判断该属性的值,调用派生类中的相应方法输出字符串。代码如下:
class Language //自定义类
{
private string name = ""; //定义语言变量
public string Name //定义语言属性
{
get
{
return name;
}
set
{
name = value;
}
}
}
class ProgramDic : Language //继承自定义的类
{
public void Csharp() //自定义方法,用来输出C#编程词典
{
Console.WriteLine("C#编程词典\n");
}
public void Donet() //自定义方法,用来输出ASP.NET编程词典
{
Console.WriteLine("ASP.NET编程词典\n");
}
}
class Program
{
static void
{
ProgramDic programdic = new ProgramDic(); //实例化派生类对象
while (true)
{
Console.Write("请输入语言(C#或ASP.NET):");
programdic.Name = Console.ReadLine(); //为派生类中的 Name属性赋值
if (programdic.Name == "C#")
{
programdic.Csharp(); //调用Csharp方法
}
else if (programdic.Name == "ASP.NET")
{
programdic.Donet(); //调用Donet方法
}
}
}
}
2.多继承
如果要使用多继承,需要使用接口,因为C#中的类只支持单继承,而接口支持多继承,实现多继承时,继承的多个接口中间用逗号(,)隔开。
说明:
实现多继承时,继承的可以是多个接口,也可以是一个类及多个接口。
下面通过一个实例讲解如何实现多继承。
例 创建一个控制台应用程序,首先定义两个接口,分别表示人的实体接口和行为接口;然后声明一个类,并使这个类继承于定义的两个接口,在该类中实现接口中的成员属性和方法;最后在Main方法中实例化派生类的对象,并使用该派生类对象调用人的实体接口和行为接口中的属性和方法。代码如下:
interface IPeopleEntity //定义一个实体接口
{
string Name //定义名字属性
{
get;
set;
}
}
interface IPeopleActive //定义一个行为接口
{
void say(string str); //定义说话方法
}
class PeopleClass:IPeopleEntity,IPeopleActive //继承定义的两个接口
{
string name = ""; //定义名字变量
public string Name //实现接口中定义的名字属性
{
get
{
return name;
}
set
{
name = value;
}
}
bool CheckChinese(string str) //定义一个方法,用来判断字符串是不是中文
{
bool flag = false; //定义一个bool类型变量,作为标识
UnicodeEncoding encode = new UnicodeEncoding(); //实例化编码对象
byte[] b = encode.GetBytes(str); //将字符串转换为字节数组
for (int i = 0; i < b.Length; i++) //遍历字节数组
{
if (b[i] != 0) //判断字节数组中指定索引处的值是不是0
{
flag = true; //设置标识为true
}
else
{
flag = false; //设置标识为false
}
}
return flag; //返回定义的标识
}
public void say(string str) //实现接口中定义的说话方法
{
if (CheckChinese(str)) //判断输入的是不是中文
{
Console.WriteLine(str+"是中国人,请说汉语!");
}
else
{
Console.WriteLine(str + " is a american,please speak english!");
}
}
}
class Program
{
static void
{
PeopleClass peopleclass = new PeopleClass(); //实例化类对象
while (true)
{
Console.Write("请输入名字:");
peopleclass.Name = Console.ReadLine(); //记录用户输入的名字
peopleclass.say(peopleclass.Name); //根据用户输入显示相应的字符串
}
}
}
继承的原则
上面详细讲解了如何实现继承,包括单继承和多继承,本节主要总结一下C#中实现继承的几个主要原则,分别如下:
q 除了object类,每个类有且只有一个直接基类,如果没有显式指定类的直接基类,那么它的直接基类就隐含的设置为object。object类没有任何直接或间接基类,它是所有类的终极基类;
q 无论基类成员的可访问性如何,除构造函数和析构函数外,所有其他基类的成员都能被子类继承,然而,有些继承成员在子类中可能是不可访问的,比如,基类的private成员在子类中不可访问,但是,如果将子类的对象作为参数传入基类的方法内,那么在基类的代码内部,就可以通过子类或者子类的对象来访问基类的private成员;
q 子类可以扩展它的直接基类;
q 继承是可以传递的,比如C类从B类继承,而B类从A类继承,那么C类就会既继承B类中的成员,又继承A类中的成员;
q 类不能循环继承,比如A类继承于B类,而B类继承于C类,那么C类就不能再去继承A类,因为它们之间存在了一种循环关系;
q 类的直接基类必须至少与类本身具有同样的可访问性,比如,如果从private类派生一个public类,将会导致编译时错误;
q 在子类中可以声明具有相同名称或签名的新成员来隐藏从基类继承而来的成员,但是,隐藏继承而来的成员时并不移除该成员,而只是使被隐藏的成员在子类中不可以直接访问;
q 类中可以声明虚方法等,而子类可以重写这些虚方法的实现;
q C#中只支持类的单一继承,但是支持接口的多重继承;
q 类的实例包含在该类中及它的所有基类中声明的所有实例字段的集合,并且存在一个从子类到它的任何基类的隐式转换,因此,可以将子类的实例看成是其任何基类的实例的引用。