1 继承
1.1 基类与派生类
C#中的类不支持多继承。只有在类和接口之间可以实现多继承。
public static void Main() { Business c1 = new Business(); c1.Name = "李明"; c1["办公电话"] = "07188888888"; c1.Output(); ClassMate c2 = new ClassMate(); c2.Name = "张鹏"; c2.Birthday = new DateTime(1978, 12, 31); c2.Output(); }
/// <summary> /// 基类:联系人Contact /// </summary> public class Contact { //字段 protected string m_name; protected string m_homePhone = "未知"; protected string m_busiPhone = "未知"; protected string m_mobilePhone = "未知"; //属性 public string Name { get { return m_name; } set { m_name = value; } } //索引函数 public string this[string sType] { get { string type = sType.ToUpper(); switch (type) { case "住宅电话": return m_homePhone; case "办公电话": return m_busiPhone; case "手机": return m_mobilePhone; default: return null; } } set { string type = sType.ToUpper(); switch (type) { case "住宅电话": m_homePhone = value; break; case "办公电话": m_busiPhone = value; break; case "手机": m_mobilePhone = value; break; default: throw new ArgumentOutOfRangeException(); } } } //构造函数 public Contact() { } //方法 public void Output() { Console.WriteLine("姓名:{0}", m_name); Console.WriteLine("住宅电话:{0}", m_homePhone); Console.WriteLine("办公电话:{0}", m_busiPhone); Console.WriteLine("手机:{0}", m_mobilePhone); Console.WriteLine(); } }
/// <summary> /// 派生类:商务Business /// </summary> public class Business:Contact { //字段 protected string m_busiFax = "未知"; protected string m_title = "先生/女士"; //属性 public string Title { get { return m_title; } set { m_title = value; } } }
/// <summary> /// 派生类:同学ClassMate /// </summary> public class ClassMate:Contact { //字段 protected DateTime m_birthday; //属性 public DateTime Birthday { get { return m_birthday; } set { m_birthday = value; } } }
1.2 隐藏基类的成员
通过new关键字来隐藏基类中的成员,来存储不同的信息或执行不同的操作。
new关键字放在访问限制修饰符的前后均可,但一定要在成员的类型说明之间
隐藏基类成员时所使用的new关键字属于一种修饰符,与创建对象的new操作符是完全不同的。
1.3 base关键字
this关键字可以用来访问当前对象,而base关键字则可以用来访问基类对象,调用基类对象的成员。
//方法 public new void Output() { base.Output(); Console.WriteLine("..."); }
1.4 继承中的构造函数和析构函数
创建一个派生类的实例,在执行其构造函数代码之前,会隐式的调用基类的构造函数。
析构函数的调用顺序相反,首先调用当前对象的析构函数,依次调用各级基灶的析构函数。
对于不带参数的默认构造函数,如果基类中已经有了定义,派生类可以隐式的继承。一旦基类中定义了带参数的构造函数,派生类的继承就不是隐式的了,而是要提供参数数量和类型都是基类构造函数相同的构造函数。例如基类Contact中定义了下面的构造函数:
public Contact(string sName) { m_name=sName; }
那么下面的代码是错误的:
Business c1= new Business("李四"); Classmate c2= new Classmate("张鹏");
下面是一个完整的示例:
public class NewInheritSample { static void Main() { //Business c2 = new Business();错误的代码 Business c1 = new Business("李明"); c1["办公电话"] = "01088888888"; c1["商务传真"] = "01066666666"; c1.Output(); ClassMate c2 = new ClassMate("张鹏"); c2.Birthday = new DateTime(1977, 02, 19); c2.Output(); } }
/// <summary> /// 基类:联系人Contact /// </summary> public class Contact { //字段 protected string m_name; protected string m_homePhone = "未知"; protected string m_busiPhone = "未知"; protected string m_mobilePhone = "未知"; //属性 public string Name { get { return m_name; } set { m_name = value; } } //索引函数 public string this[string sType] { get { string type = sType.ToUpper(); switch (type) { case "住宅电话": return m_homePhone; case "办公电话": return m_busiPhone; case "手机": return m_mobilePhone; default: return null; } } set { string type = sType.ToUpper(); switch (type) { case "住宅电话": m_homePhone = value; break; case "办公电话": m_busiPhone = value; break; case "手机": m_mobilePhone = value; break; default: throw new ArgumentOutOfRangeException(); } } } //构造函数 public Contact() { } public Contact(string sName) { m_name = sName; } //方法 public void Output() { Console.WriteLine("姓名:{0}", m_name); Console.WriteLine("住宅电话:{0}", m_homePhone); Console.WriteLine("办公电话:{0}", m_busiPhone); Console.WriteLine("手机:{0}", m_mobilePhone); Console.WriteLine(); } } /// <summary> /// 派生类:商务Business /// </summary> public class Business:Contact { //字段 protected string m_busiFax = "未知"; protected string m_title = "先生/女士"; //属性 public string Title { get { return m_title; } set { m_title = value; } } //索引函数 public new string this[string sType] { get { string type=sType.ToUpper(); switch(type) { case "商务传真": return m_busiFax; default: return base[sType]; } } set { string type = sType.ToUpper(); switch (type) { case "商务传真": m_busiFax = value; break; default: base[sType] = value; break; } } } //构造函数 public Business(string sName) : base(sName) { } //方法 public new void Output() { base.Output(); Console.WriteLine("商务传真:{0}", m_busiFax); Console.WriteLine(); } } /// <summary> /// 派生类:同学ClassMate /// </summary> public class ClassMate:Contact { //字段 protected DateTime m_birthday; //属性 public DateTime Birthday { get { return m_birthday; } set { m_birthday = value; } } //构造函数 public ClassMate(string sName) : base(sName) { } //方法 public new void Output() { base.Output(); Console.WriteLine("生日:{0}", m_birthday.ToString()); } }
2 多态性
2.1 虚拟方法和重载方法
派生类很少一成不变的去继承基类中的所有成员。一种情况是:派生类中的方法成员隐藏基类中同名的方法成员,通过new关键字对成员修饰。另一种更普遍和灵活的情况是:将基类的方法成员定义为虚拟方法,而在派生类中对虚拟方法进行重载。后者的优势在于它可以实现运行时的多态性。
基类的虚拟方法通过关键字virtual进行定义。下面是一个完整的例子:
class VirtualInheritSample { static void Main(string[] args) { Business c1 = new Business("李明"); c1["办公电话"] = "01088888888"; c1["商务传真"] = "01066666666"; ClassMate c2 = new ClassMate("张鹏"); c2.Birthday = new DateTime(1977, 02, 19); Contact c = c1; c.Output();//for Business c = c2; c.Output();//for Classmate } } /// <summary> /// 基类:联系人Contact /// </summary> public class Contact { //字段 protected string m_name; protected string m_homePhone = "未知"; protected string m_busiPhone = "未知"; protected string m_mobilePhone = "未知"; //属性 public string Name { get { return m_name; } set { m_name = value; } } //虚拟索引函数 public virtual string this[string sType] { get { string type = sType.ToUpper(); switch (type) { case "住宅电话": return m_homePhone; case "办公电话": return m_busiPhone; case "手机": return m_mobilePhone; default: return null; } } set { string type = sType.ToUpper(); switch (type) { case "住宅电话": m_homePhone = value; break; case "办公电话": m_busiPhone = value; break; case "手机": m_mobilePhone = value; break; default: throw new ArgumentOutOfRangeException(); } } } //构造函数 public Contact() { } public Contact(string sName) { m_name = sName; } //虚拟方法 public virtual void Output() { Console.WriteLine("姓名:{0}", m_name); Console.WriteLine("住宅电话:{0}", m_homePhone); Console.WriteLine("办公电话:{0}", m_busiPhone); Console.WriteLine("手机:{0}", m_mobilePhone); Console.WriteLine(); } } /// <summary> /// 派生类:商务Business /// </summary> public class Business:Contact { //字段 protected string m_busiFax = "未知"; protected string m_title = "先生/女士"; //属性 public string Title { get { return m_title; } set { m_title = value; } } //重载索引函数 public override string this[string sType] { get { string type=sType.ToUpper(); switch(type) { case "商务传真": return m_busiFax; default: return base[sType]; } } set { string type = sType.ToUpper(); switch (type) { case "商务传真": m_busiFax = value; break; default: base[sType] = value; break; } } } //构造函数 public Business(string sName) : base(sName) { } //重载方法 public override void Output() { base.Output(); Console.WriteLine("商务传真:{0}", m_busiFax); Console.WriteLine(); } } /// <summary> /// 派生类:同学ClassMate /// </summary> public class ClassMate:Contact { //字段 protected DateTime m_birthday; //属性 public DateTime Birthday { get { return m_birthday; } set { m_birthday = value; } } //构造函数 public ClassMate(string sName) : base(sName) { } //重载方法 public override void Output() { base.Output(); Console.WriteLine("生日:{0}", m_birthday.ToString()); } }
提示:
--如果派生类中使用override关键字定义了重载方法,那么也就允许该类自己的派生类继续重载这个方法,因此重载方法也默认是一种虚拟方法,但不能同时使用virtual和override修饰一个方法。
--虚拟方法可以在派生类中重载,因此虚拟方法不能是私有的
--在基类和派生类中,对同一个虚拟方法和重载方法的访问限制应当相同,要么都使用public,要么都使用protected。
2.2 抽象类和抽象方法
基类中的虚拟方法允许在派生类中进行重载,并在调用时动态地决定是执行基类的方法代码,还是执行哪一个派生类的方法代码。
此外,还可以为基类定义抽象方法,它强制性的要求所有派生类必须重载该方法。
抽象方法使用关键字abstract定义,并且不提供方法的执行体,如:
public abstract void Output();
包含抽象方法的类必须是抽象类,它也需要使用关键字abstract加以定义:
public abstract class Contact { // }
抽象类表达的是抽象的概念,它本身不与具体的对象相联系,其作用是为派生类提供一个公共的界面。
抽象类不允许创建类的实例,定义了抽象类Contact后,下面的代码是错误的:
Contact c = new Contact("李明");//错误
但可以将抽象类与其非抽象的派生类实例相关联:
Contact c= new Business("李明"); Classmate c1 = new Classmate("张鹏"); c = c1;
--抽象类之间也可以继承
--抽象类要求其所有派生类都继承它的抽象方法
--如果派生类不是抽象类,它就必须重载这些抽象方法并提供实现代码
--派生类中对抽象方法的重载通过关键字override进行
--抽象方法不能是私有的,而且抽象方法及其重载方法的访问限制应当相同。
--抽象方法不能同时用virtual关键字进行修饰。
下面是一个完整的例子:
class AbstractInheritSample { static void Main() { Business c1 = new Business("李明"); c1["办公电话"] = "01088888888"; ClassMate c2 = new ClassMate("张鹏"); c2.Birthday = new DateTime(1977, 2, 19); Contact c = c1; c.Output(); c = c2; c.Output(); } } /// <summary> /// 基类:联系人Contact /// </summary> public abstract class Contact { //字段 protected string m_name; protected string m_homePhone = "未知"; protected string m_busiPhone = "未知"; protected string m_mobilePhone = "未知"; //属性 public string Name { get { return m_name; } set { m_name = value; } } //虚拟索引函数 public virtual string this[string sType] { get { string type = sType.ToUpper(); switch (type) { case "住宅电话": return m_homePhone; case "办公电话": return m_busiPhone; case "手机": return m_mobilePhone; default: return null; } } set { string type = sType.ToUpper(); switch (type) { case "住宅电话": m_homePhone = value; break; case "办公电话": m_busiPhone = value; break; case "手机": m_mobilePhone = value; break; default: throw new ArgumentOutOfRangeException(); } } } //构造函数 public Contact() { } public Contact(string sName) { m_name = sName; } //抽象方法 public abstract void Output(); } /// <summary> /// 派生类:商务Business /// </summary> public class Business:Contact { //字段 protected string m_busiFax = "未知"; protected string m_title = "先生/女士"; //属性 public string Title { get { return m_title; } set { m_title = value; } } //重载索引函数 public override string this[string sType] { get { string type=sType.ToUpper(); switch(type) { case "商务传真": return m_busiFax; default: return base[sType]; } } set { string type = sType.ToUpper(); switch (type) { case "商务传真": m_busiFax = value; break; default: base[sType] = value; break; } } } //构造函数 public Business(string sName) : base(sName) { } //重载方法 public override void Output() { Console.WriteLine("商务:{0}", m_name+m_title); Console.WriteLine("住宅电话:{0}", m_homePhone); Console.WriteLine("办公电话:{0}", m_busiPhone); Console.WriteLine("手机:{0}", m_mobilePhone); Console.WriteLine("商务传真:{0}", m_busiFax); Console.WriteLine(); } } /// <summary> /// 派生类:同学ClassMate /// </summary> public class ClassMate:Contact { //字段 protected DateTime m_birthday; //属性 public DateTime Birthday { get { return m_birthday; } set { m_birthday = value; } } //构造函数 public ClassMate(string sName) : base(sName) { } //重载方法 public override void Output() { Console.WriteLine("同学:{0}", m_name); Console.WriteLine("住宅电话:{0}", m_homePhone); Console.WriteLine("办公电话:{0}", m_busiPhone); Console.WriteLine("手机:{0}", m_mobilePhone); Console.WriteLine("生日:{0}", m_birthday.ToString()); } }
2.3 密封类和密封方法
抽象类本身无法创建实例,而强制要求通过派生类实现功能。
与之相反的是,还可以定义一种密封类,它不允许从中派生中其它的类。
密封类通常位于类的继承层次的最低层,或者是在其它一些不希望被继承的情况下使用。String类和StringBuilder类就是密封类。
密封类使用关键字sealed定义:
public sealed class Business:Contact { // }
一个类可以同时被定义为密封类和抽象类,意味着该类既不能被继承,也不能被实例化。这只出现在一种情况下,就是该类的所有成员均为静态成员。Console类就是这样一个类。
如果方法在定义中使用了关键字sealed,它就是一个密封方法。与密封类的不同之处在于:
密封类是指不允许有派生类的类,而密封方法是指不允许被重载的方法。
密封方法所在的类不一定是密封类(这一点与抽象方法不同),而如果该类存在派生类,那么在派生类中就必须原封不同的继承这个密封方法。
密封方法本身也要求是一个重载方法(sealed和override必须在方法定义中同时出现)。例如在ClassMate类中将重载方法Output定义为密封的:
public sealed override void Output() { // }
这样在ClassMate类的所有派生类中,就不允许重载该方法的实现。如果要在派生类中定义同名的方法,就必须使用关键字new来隐藏基类的方法:
public class CollegeClassmate:Classmate { //构造函数 public CollegeClassmate(string sName):base(sName) { } //错误的定义:密封方法不允许被重载 //public override void Output() //覆盖方法 //public new void Output() { // } }
3 再谈接口与继承
接口和抽象类之间有很多共同点:
--接口和抽象类都不能被实例化
--接口中的方法和抽象方法都不提供任何实现代码
--接口的任何派生类型(类和接口)都必须继承接口定义的所有方法,抽象类的任何派生类都必须继承该抽象类定义的所有抽象方法
--接口的任何非抽象派生类都必须提供对该接口定义的所有方法的实现代码,而抽象类的任何非抽象派生类也都必须提供对该抽象类所定义的所有抽象方法的实现代码。
接口和抽象类之间的不同点:
--接口是比抽象类的抽象程序更高的一种数据类型,可以将接口中的方法均视为抽象方法
--抽象类中既可以有抽象方法,也可以有非抽象方法
--接口没有字段成员
--接口不提供任何实现方式
--抽象类除了协议描述(通过抽象方法进行)之外,还可以提供高层的实现方式供基派生类继承。
--接口引入了多继承
由于接口引入了多继承,在继承方法成员时就可能引起冲突。如果某个派生类所继承的基类和接口含有同名的方法,那么可以在继承的方法中通过显式的接口声明加以区别。
public interface IOutput { void Output(); } public class Contact { public virtual void Output() {} } public class Business:Contact,IOutput { public override void Output(){} void IOutput.Output(){} }
下面是一个完整的示例:
class InterfaceInheritSample { static void Main(string[] args) { Business c1 = new Business("李明"); c1["办公电话"] = "01088888888"; ClassMate c2 = new ClassMate("张鹏"); c2.Birthday = new DateTime(1977, 2, 19); Contact c = c1; c1.Output();//Business.Output(override) IOutput i = c; i.Output();//Business.Output(IOutput) c = c2; c.Output();//Classmate.Output i = c; i.Output();//Classmate.Output } } /// <summary> /// 接口IOutput /// </summary> public interface IOutput { void Output(); } /// <summary> /// 基类:联系人Contact /// </summary> public class Contact:IOutput,IComparable { //字段 protected string m_name; protected string m_homePhone = "未知"; protected string m_busiPhone = "未知"; protected string m_mobilePhone = "未知"; //属性 public string Name { get { return m_name; } set { m_name = value; } } //虚拟索引函数 public virtual string this[string sType] { get { string type = sType.ToUpper(); switch (type) { case "住宅电话": return m_homePhone; case "办公电话": return m_busiPhone; case "手机": return m_mobilePhone; default: return null; } } set { string type = sType.ToUpper(); switch (type) { case "住宅电话": m_homePhone = value; break; case "办公电话": m_busiPhone = value; break; case "手机": m_mobilePhone = value; break; default: throw new ArgumentOutOfRangeException(); } } } //构造函数 public Contact() { } public Contact(string sName) { m_name = sName; } //虚拟方法 public virtual void Output() { Console.WriteLine("姓名:{0}", m_name); Console.WriteLine("住宅电话:{0}", m_homePhone); Console.WriteLine("办公电话:{0}", m_busiPhone); Console.WriteLine("手机:{0}", m_mobilePhone); Console.WriteLine(); } //方法 public int CompareTo(object obj) { if (obj is Contact) return this.m_name.CompareTo(((Contact)obj).m_name); return -1; } } /// <summary> /// 派生类:商务Business /// </summary> public class Business:Contact,IOutput { //字段 protected string m_busiFax = "未知"; protected string m_title = "先生/女士"; //属性 public string Title { get { return m_title; } set { m_title = value; } } //重载索引函数 public override string this[string sType] { get { string type=sType.ToUpper(); switch(type) { case "商务传真": return m_busiFax; default: return base[sType]; } } set { string type = sType.ToUpper(); switch (type) { case "商务传真": m_busiFax = value; break; default: base[sType] = value; break; } } } //构造函数 public Business(string sName) : base(sName) { } //重载方法 public override void Output() { Console.WriteLine("商务:{0}", m_name+m_title); Console.WriteLine("办公电话:{0}", m_busiPhone); Console.WriteLine("手机:{0}", m_mobilePhone); Console.WriteLine("商务传真:{0}", m_busiFax); Console.WriteLine(); } //接口方法 void IOutput.Output() { Console.WriteLine("商务:{0}", m_name + m_title); Console.WriteLine("住宅电话:{0}", m_homePhone); Console.WriteLine("办公电话:{0}", m_busiPhone); Console.WriteLine("手机:{0}", m_mobilePhone); Console.WriteLine("商务传真:{0}", m_busiFax); Console.WriteLine(); } } /// <summary> /// 派生类:同学ClassMate /// </summary> public class ClassMate:Contact { //字段 protected DateTime m_birthday; //属性 public DateTime Birthday { get { return m_birthday; } set { m_birthday = value; } } //构造函数 public ClassMate(string sName) : base(sName) { } //重载方法 public override void Output() { Console.WriteLine("同学:{0}", m_name); Console.WriteLine("住宅电话:{0}", m_homePhone); Console.WriteLine("办公电话:{0}", m_busiPhone); Console.WriteLine("手机:{0}", m_mobilePhone); Console.WriteLine("生日:{0}", m_birthday.ToString()); Console.WriteLine(); } }
程序输出结果为:
商务:李明先生/女士 办公电话:01088888888 手机:未知 商务传真:未知 商务:李明先生/女士 住宅电话:未知 办公电话:01088888888 手机:未知 商务传真:未知 同学:张鹏 住宅电话:未知 办公电话:未知 手机:未知 生日:1977-2-19 0:00:00 同学:张鹏 住宅电话:未知 办公电话:未知 手机:未知 生日:1977-2-19 0:00:00 请按任意键继续. . .
上面用到的IComparable接口是在程序集System中定义的,继承该接口的类必须实现CompareTo方法,并返回一个整数值来表示比较的结果。
集合相关的接口,可参考.NET命名空间System.Collections