第8章 类与接口
对于所有的面向对象编程语言,类和接口都是最重要的数据结构,绝大部分的编程实现都需要使用这两种数据结构。类和接口是C#编程语言最常用、最核心的数据结构。在C#编程语言中,一切数据类型都是基于Object类创建的。接口定义类的数据和方法规范,类似于一种契约,继承接口的类必须实现接口中所描述的属性、方法、事件和索引器。
8.1 传递类与传递结构体的区别
在C#编程语言中,类属于引用类型的数据类型,结构体属于值类型的数据类型,这两种数据类型的本质区别主要是各自指向的内存位置不同。传递类的时候,主要表现为是否同时改变了源对象。
技术要点
本示例主要说明了传递类与传递结构体的区别,技术要点如下。
● 类在传递的时候,传递的内容是位于托管内存中的位置,结构体在传递的时候,传递的内容是位于程序堆栈区的内容。当类的传递对象修改时,将同时修改源对象,而结构体的传递对象修改时,不会对源对象产生影响。
● 在一个类中,可以定义默认的、不带参数的构造函数,而在结构体中不能定义默认的、不带参数的构造函数。两者都可以定义带有参数的构造函数,通过这些参数给各自的字段赋值或初始化。
实现步骤
(1)创建控制台应用程序项目,命名为“ClassAndStruct”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace ClassAndStruct { class Program { static void Main(string[] args) { //使用带参数的构造函数创建员工类的实例 classEmployee clsEmpA = new classEmployee("Pony","Smith",43); classEmployee clsEmpB = clsEmpA; //修改引用数据 clsEmpB.Age = 33; //使用带参数的构造函数创建员工结构体的实例 structEmployee strctEmpA = new structEmployee("Pony", "Smith", 43); structEmployee strctEmpB = strctEmpA; //修改 strctEmpB.Age = 33; Console.WriteLine("类的数据:姓名-{0} {1} 年龄-{2}", clsEmpA.FirstName,clsEmpA.LastName,clsEmpA.Age); Console.WriteLine("结构体的数据:姓名-{0} {1} 年龄-{2}", strctEmpA.FirstName, strctEmpA.LastName, strctEmpA.Age); Console.ReadLine(); } } class classEmployee//表示员工的类 { private string firstname; public string FirstName { get { return firstname; } set { firstname = value; } } private string lastname; public string LastName { get { return lastname; } set { lastname = value; } } private int age; public int Age { get { return age; } set { age = value; } } //类的默认构造函数,可以在类中重新定义 public classEmployee() { firstname = ""; lastname = ""; age = 0; } //类的带参数的构造函数,在构造类实例的同时给字段赋值 public classEmployee(string strFirstNamem, string strLastName, int iAge) { firstname = strFirstNamem; lastname = strLastName; age = iAge; } } struct structEmployee//表示员工的结构体 { private string firstname; public string FirstName { get { return firstname; } set { firstname = value; } } private string lastname; public string LastName { get { return lastname; } set { lastname = value; } } private int age; public int Age { get { return age; } set { age = value; } } //在结构体中不能定义默认的不带参数的构造函数,只能定义结构体的带参数的构造函数 public structEmployee(string strFirstNamem, string strLastName, int iAge) { firstname = strFirstNamem; lastname = strLastName; age = iAge; } }
}
(3)按F5键运行程序,运行结果如下所示。
类的数据:姓名-Pony Smith 年龄-33 结构体的数据:姓名-Pony Smith 年龄-43
源程序解读
(1)本示例为了说明类和结构体在传递时的差别,在程序中分别定义了表示员工的类classEmployee类和表示员工的结构体structEmployee,并定义了各自的字段和构造函数。在主程序入口Main方法中,声明类的实例clsEmpA和clsEmpB,并使用构造函数创建clsEmpA类实例,然后将clsEmpA类实例传递给clsEmpB类实例,修改clsEmpB类实例的字段值,最后打印clsEmpA类实例中的字段,查看字段的值是否随clsEmpB类实例字段的修改而变化。同时,声明结构体的实例strctEmpA和strctEmpB,并使用构造函数创建strctEmpA结构体实例,然后将strctEmpA结构体实例传递给strctEmpB结构体实例,修改strctEmpB结构体实例的字段值,最后打印strctEmpA结构体实例中的字段,查看字段的值是否随strctEmpB结构体实例字段的修改而变化。程序的流程图如图8.1所示。
图8.1 传递类与结构体的区别示例程序流程图
(2)类与结构体还有一个比较明显的区别,就是类能够定义默认的、不带参数的构造函数,并能在该构造函数中初始化字段。而结构体不允许定义默认的、不带参数的构造函数。
8.2 多个接口的继承
接口是面向对象编程的一个重要概念。在C#编程语言中,接口可以继承接口,并且一个接口可以从多个接口中继承。
技术要点
本示例主要说明了多个接口继承的程序实现方法,技术要点如下。
● 接口类似于一个抽象基类,主要的作用是规范和组织类的行为。
● 不能直接实例化接口,任何继承接口的非抽象类型,都必须实现接口中定义的所有成员。
● 接口中的所有成员都是公共的,不能在接口成员前添加访问限定符。
● 接口只提供方法的声明,不提供方法的实现。
● 接口可以继承接口,并且能够继承多个接口。
实现步骤
(1)创建控制台应用程序项目,命名为“MultiInterface”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace MultiInterface { class Program { static void Main(string[] args) { Order ord = new Order();//创建单据类实例 ord.Entry();//单据入仓操作 ord.Export();//单据出仓操作 Console.ReadLine(); } } interface WarehouseEntry//单据入仓接口 { void EN_Entry(object EntryOrder);//入仓方法 } interface WarehouseExport//单据出仓接口 { void EX_Export(object EntryOrder);//出仓方法 } //单据的接口,该接口中继承单据入仓接口和单据出仓接口 interface IntOrder : WarehouseEntry, WarehouseExport { void Entry();//单据入仓方法 void Export();//单据出仓方法 } class Order :IntOrder//表示单据的类,继承intOrder接口 { #region WarehouseEntry 成员 public void EN_Entry(object EntryOrder) { Console.WriteLine("完成单据入仓操作"); } #endregion #region WarehouseExport 成员 public void EX_Export(object EntryOrder) { Console.WriteLine("完成单据出仓操作"); } #endregion #region IntOrder 成员 public void Entry() { EN_Entry(null); } public void Export() { EX_Export(null); } #endregion } }
(3)按F5键运行程序,运行结果如下所示。
完成单据入仓操作 完成单据出仓操作
源程序解读
(1)本示例声明了WarehouseEntry接口,表示单据的入仓操作接口,还声明了WarehouseExport接口,表示单据的出仓操作接口。为了实现接口的多继承,还声明了IntOrder接口,同时继承WarehouseEntry接口和WarehouseExport接口。然后定义表示单据的Order类,该类继承IntOrder接口,并实现了接口定义中所规范的方法。最后在主程序入口Main方法中,创建Order类的实例,并且调用实现接口的方法。本示例程序的流程图如图8.2所示。
图8.2 多个接口继承的示例程序流程图
(2)在本示例程序中,IntOrder接口除了继承WarehouseEntry接口和WarehouseExport接口,还声明了Entry方法和Export方法,这两个方法在Order类中也必须实现。
(3)接口的实现必须是公共的、非静态的,并且返回的数据类型必须与接口声明一致。
8.3 复制构造函数
某些编程语言的类提供了复制构造函数,即从当前类实例构造一个新的类实例。在C#编程语言中,没有提供复制构造函数,如果要实现复制构造的功能,就必须自行编写这样的构造函数。
技术要点
本示例主要说明了如何实现复制构造函数,技术要点如下。
● 复制构造函数仍然属于构造函数,不同的是以类本身作为参数的数据类型。
● 调用构造函数时,参数必须是已经创建的类实例。
实现步骤
(1)创建控制台应用程序项目,命名为“CopyConstruct”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace CopyConstruct { class Program { static void Main(string[] args) { Person personA = new Person();//使用默认构造函数创建示例 personA.FirstName = "Pony"; personA.LastName = "Smith"; personA.Age = 42; Person personB = new Person(personA);//使用复制构造函数创建示例 Console.WriteLine("personB的数据:{0} {1}-{2}", personB.FirstName, personB.LastName, personB.Age); Console.ReadLine(); } } class Person//表示“人”的类 { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set;} public Person()//默认构造函数 { } //复制构造函数,以类实例作为参数的构造函数,将参数实例的字段赋给当前实例字段 public Person(Person source) { this.FirstName = source.FirstName; this.LastName = source.LastName; this.Age = source.Age; } } }
(3)按F5键运行程序,运行结果如下所示。
personB的数据:Pony Smith-42
源程序解读
(1)本示例的Person类中定义了两个构造函数。一个是默认的、不带参数的构造函数,另一个是以Person类实例作为参数的复制构造函数。在主程序入口Main方法中,首先使用默认的构造函数创建Person类的实例personA,然后给personA中的字段赋值,再将personA类实例作为Person类复制构造函数的参数,创建Person类的另一个实例personB,最后将personB的数据打印出来。本示例程序的流程图如图8.3所示。
图8.3 复制构造函数示例程序流程图
(2)使用构造函数的时候,将在托管内存中为新的类实例分配位置,而不是指向原先的类实例的托管内存位置。在本示例程序中,personB类实例和personA类实例不存在引用关系,其中一个实例字段的修改不会对另一个实例产生影响。
8.4 类的定义
在面向对象的编程语言中,类是最常见的数据结构,在C#编程语言中也是如此。使用class关键字定义的类,不仅体现出传统面向对象编程的所有特征:继承、多态和封装,而且还具备继承接口、静态成员、文件拆分等强大的功能。C#编程语言与C++编程语言的类有一个显著的差别,就是C#的类只能实现单继承,而C++的类能够实现多继承。
技术要点
本示例主要说明了如何在程序中定义一个类,技术要点如下。
● 任何类都可以看成是包含了指定数据结构和行为的数据结构,使用class关键字定义一个类,在类中可以声明字段、属性、方法和事件,其中字段和属性定义了类的数据结构,方法和事件定义了类的行为。
● 根据实际需要指定类成员的访问属性,使该成员对外部程序可见或不可见,从而实现类的封装。
● 类的构造函数是一个特殊的方法,在该方法之前使用new关键字创建类的实例。
实现步骤
(1)创建控制台应用程序项目,命名为“DefineClass”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace DefineClass { class Program { static void Main(string[] args) { //创建员工类的实例 Employee emp = new Employee("Pony","Smith",21,"Engineer"); Console.WriteLine("修改前数据"); Console.WriteLine("姓名:{0}", emp.FirstName + " " + emp.LastName); Console.WriteLine("年龄:{0}", emp.Age); Console.WriteLine("部门:{0}", emp.Department); emp.AddAge(1);//调用员工类的基类方法 emp.ChangeDepartment("Sale");//调用员工类的方法 Console.WriteLine("修改后数据"); Console.WriteLine("姓名:{0}", emp.FirstName + " " + emp.LastName); Console.WriteLine("年龄:{0}", emp.Age); Console.WriteLine("部门:{0}", emp.Department); Console.ReadLine(); } } class Employee //表示员工的类 { private string firstname; public string FirstName//表示名字的前半部分属性 { get { return firstname; } set { firstname = value; } } private string lastname; public string LastName//表示名字的后半部分属性 { get { return lastname; } set { lastname = value; } } private int age; public int Age//表示年龄的属性 { get { return age; } set { age = value; } } private string department; public string Department//表示部门的属性 { get { return department; } set { department = value; } } //构造函数,使用参数给类的字段赋值 public Employee(string strfirstname, string strlastname, int iage, string strdepartment) { firstname = strfirstname; lastname = strlastname; age = iage; department = strdepartment; } public void AddAge(int Addition)//增加年龄的方法 { age += Addition; } public void ChangeDepartment(string newdepartment)//修改部门的方法 { department = newdepartment; } } }
(3)按F5键运行程序,运行结果如下所示。
修改前数据 姓名:Pony Smith 年龄:21 部门:Engineer 修改后数据 姓名:Pony Smith 年龄:22 部门:Sale
源程序解读
(1)本示例程序定义了一个表示员工的Employee类。在该类中包含了表示员工姓名前半部分的FirstName属性,表示员工姓名后半部分的LastName属性,表示员工年龄的Age属性和表示员工部门的Department属性。同时包含了增加员工年龄的AddAge方法和修改员工部门的ChangeDepartment方法。Employee类的构造函数有四个参数,在使用该构造函数创建员工类实例时,将这些参数的值赋给指定的属性。当Employee类定义完成后,在主程序入口Main方法中,使用带参数的构造函数,创建Employee类的实例emp,并将该实例的属性值显示在屏幕上,接着调用类实例中所包含的AddAge方法和ChangeDepartment方法,再次打印emp类实例的属性,查看调用方法后emp类实例属性的变化,本示例程序流程图如图8.4所示。
图8.4 类的定义示例程序流程图
(2)在本示例中,采用了规范的方式来定义类的属性,即首先声明一个private的私有成员变量,用来存储变量的值,再声明一个属性,指向该私有成员变量。这是类属性的标准定义方式,如果在属性中只定义get部分的代码段,就将该属性指定为只读属性。
8.5 类的继承
在C#编程语言中,除了密封类以外,绝大部分的类都可以继承,在面向对象编程体系中,继承是一个重要的概念,对于类而言,所谓继承,就是子类包含父类的数据结构和行为方式,包括字段、属性、方法和事件。尽管在子类本身的定义中没有包含这些定义,仍然可以使用这些父类成员。
技术要点
本示例主要说明了如何在程序中使用类的继承,技术要点如下。
● 继承父类的子类包含了父类的特征,父类的字段、属性、方法和事件,在子类中不需要声明就能够直接使用。
● 在子类中能够继承父类的构造函数,使用base关键字,即可使用父类的构造函数。
实现步骤
(1)创建控制台应用程序项目,命名为“InheritClass”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace InheritClass { class Program { static void Main(string[] args) { //创建员工类的实例 Employee emp = new Employee("Pony", "Smith", 21, "Engineer"); Console.WriteLine("修改前数据"); Console.WriteLine("姓名:{0}", emp.FirstName + " " + emp.LastName); Console.WriteLine("年龄:{0}", emp.Age); Console.WriteLine("部门:{0}", emp.Department); emp.AddAge(1);//调用员工类的基类方法 emp.ChangeDepartment("Sale");//调用员工类的方法 Console.WriteLine("修改后数据"); Console.WriteLine("姓名:{0}", emp.FirstName + " " + emp.LastName); Console.WriteLine("年龄:{0}", emp.Age); Console.WriteLine("部门:{0}", emp.Department); Console.ReadLine(); } } class Person//表示“人”的类 { private string firstname; public string FirstName//表示名字的前半部分属性 { get { return firstname; } set { firstname = value; } } private string lastname; public string LastName//表示名字的后半部分属性 { get { return lastname; } set { lastname = value; } } private int age; public int Age//表示年龄的属性 { get { return age; } set { age = value; } } //构造函数,使用参数给类的字段赋值 public Person(string strfirstname, string strlastname, int iage) { firstname = strfirstname; lastname = strlastname; age = iage; } public void AddAge(int Addition)//添加年龄的方法 { age += Addition; } } class Employee : Person//表示员工的类,从Person类继承 { private string department; public string Department//表示部门的属性 { get { return department; } set { department = value; } } //构造函数,使用参数给类的字段赋值 public Employee(string strfirstname, string strlastname, int iage, string strdepartment) : base(strfirstname, strlastname, iage)//继承基类的构造函数 { department = strdepartment; } public void ChangeDepartment(string newdepartment)//修改部门的方法 { department = newdepartment; } } }
(3)按F5键运行程序,运行结果如下所示。
修改前数据 姓名:Pony Smith 年龄:21 部门:Engineer 修改后数据 姓名:Pony Smith 年龄:22 部门:Sale
源程序解读
(1)本示例定义了表示“人”的Person类和表示员工的Employee类,其中Employee类继承于Person类。同时,Employee类的构造函数也继承于Person类,在主程序入口Main方法中,创建一个Employee类的实例,并将该实例的属性输出到屏幕上,接着分别调用在Employee类中声明的修改部门ChangeDepartment方法和在Person类中声明的增加年龄的AddAge方法。再次将执行过方法的类属性输出到屏幕上。本示例程序的流程图如图8.5所示。
图8.5 类的继承示例程序流程图
(2)本示例中Employee的构造函数继承了Person类的构造函数,该继承通过在构造函数的声明语句中添加base后缀语句完成。
8.6 使用抽象类
在C#编程语言中,抽象类用来表示从面向对象设计中抽象出来的概念,例如,桌子和椅子抽象为家具时,可以将家具作为抽象类,而表示桌子的类和表示椅子的类都从家具类继承。桌子和椅子都能够被制作,但是制作的工艺不同。在家具抽象类中,可以定义一个抽象的“制作”方法,但具体的实现需要分别在桌子类和椅子类中定义。虽然抽象类与接口的作用相类似,但是也有显著的区别,抽象类更多地应用于需要扩展的场合,接口则常用于既定规范和需要多继承的场合。
技术要点
本示例主要说明了如何在程序中使用抽象类,技术要点如下。
● 抽象类中定义的抽象方法,必须在该类的非抽象子类中提供方法的实现。抽象类能够同时提供非抽象的方法,而接口不能定义方法的实现,这是抽象函数与接口的一个主要区别。
● 在子类中实现抽象方法的时候,必须使用override关键字声明该方法是重载父类中的方法。
实现步骤
(1)创建控制台应用程序项目,命名为“AbstractClass”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace AbstractClass { class Program { static void Main(string[] args) { double area = 0; Triangle trg = new Triangle(0, 0, 0, 4, 3, 0);//使用点坐标创建三角形实例 area = trg.GetArea();//使用计算面积的方法获取三角形面积 Console.WriteLine("三角形的面积是:{0}",area); Point[] Points ={ new Point(0, 0), new Point(0, 8), new Point(6, 0) }; trg = new Triangle(Points); area = trg.GetArea();//使用计算面积的方法获取三角形面积 Console.WriteLine("三角形的面积是:{0}", area); Console.ReadLine(); } } abstract class Shape//表示形状的抽象类 { public abstract double GetArea();//获取面积的抽象方法 } class Triangle : Shape//表示三角形的类,继承形状抽象类 { private Point[] points; internal Point[] Points//三角形的点集属性 { get { return points; } set { points = value; } } public Triangle()//默认构造函数 { points = new Point[3]; } //使用点坐标创建三角形类实例的构造函数 public Triangle(double X1, double Y1, double X2, double Y2, double X3, double Y3) { points = new Point[3]; points[0].X = X1; points[0].Y = Y1; points[1].X = X2; points[1].Y = Y2; points[2].X = X3; points[2].Y = Y3; } //使用点集创建三角形类实例的构造函数 public Triangle(Point[] pointsarray) { points = new Point[3]; if (pointsarray.Length >= 3) { pointsarray.CopyTo(points,0); } } //实现计算三角形面积的抽象方法 public override double GetArea() { if (points.Length >= 3) { double X1 = points[0].X; double Y1 = points[0].Y; double X2 = points[1].X; double Y2 = points[1].Y; double X3 = points[2].X; double Y3 = points[2].Y; double a = Math.Sqrt(Math.Pow(X1 - X2,2) + Math.Pow(Y1 - Y2,2)); double b = Math.Sqrt(Math.Pow(X2 - X3, 2) + Math.Pow(Y2 - Y3, 2)); double c = Math.Sqrt(Math.Pow(X3 - X1, 2) + Math.Pow(Y3 - Y1, 2)); double p = (a + b + c) / 2; return Math.Sqrt(p * (p - a) * (p - b) * (p - c)); } else { return 0; } } } struct Point//表示点的结构 { public double X; public double Y; public Point(double x, double y)//结构的构造函数 { X = x; Y = y; } } }
(3)按F5键运行程序,运行结果如下所示。
三角形的面积是:6 三角形的面积是:24
源程序解读
(1)本示例定义表示任何形状的Shape抽象类,并定义了表示三角形的Triangle非抽象类,其中Triangle继承于Shape类,并在Triangle类中实现了Shape类的GetArea抽象方法。本示例程序的流程图如图8.6所示。
图8.6 使用抽象类示例程序流程图
(2)在定义抽象类的时候,除了能够定义抽象方法外,也可以同时定义非抽象方法。(3)在非抽象子类中实现抽象父类的抽象方法时,如本示例程序所示,必须使用override关键字作为方法的前缀声明。
8.7 使用静态构造函数
静态构造函数是一种特殊的构造函数,通常用来实现某些默认属性的赋值和初始化。在创建类实例或者引用任何静态成员之前,静态构造函数自动执行。
技术要点
本示例主要说明了如何在程序中使用静态构造函数,技术要点如下。
● 静态函数在创建类实例或者应用静态成员之前自动执行,而不需要在程序中编写调用静态构造函数的代码。
● 即使没有创建类的实例,只需要引用类中的任何静态成员,例如,调用类中定义的静态方法,静态构造函数也将自动执行。
实现步骤
(1)创建控制台应用程序项目,命名为“StaticConstruct”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace StaticConstruct { class Program { static void Main(string[] args) { Account myaccount = new Account("交通",2000);//创建账目类实例 Console.WriteLine("账目数据 {0}:{1}元 日期:{2}", myaccount.AccountName,myaccount.AccountMoney,myaccount.AccountDate); Console.ReadLine(); } } class Account//表示账目的类 { private string accountname; public string AccountName//表示账目名称的属性 { get { return accountname; } set { accountname = value; } } private static double accountmoney; public double AccountMoney//表示账目金额的属性 { get { return accountmoney; } set { accountmoney = value; } } private static DateTime accountdate; public DateTime AccountDate//表示账目发生日期的属性 { get { return accountdate; } set { accountdate = value; } } static Account()//静态构造函数,不允许带参数 { accountdate = DateTime.Now; accountmoney = 0; } public Account(string name,double money) { accountname = name; AccountMoney = money; } } }
(3)按F5键运行程序,运行结果如下所示。
账目数据 交通:2000元 日期:2007-8-29 4:48:22
源程序解读
(1)本示例程序在表示账目的Account类中实现了静态的构造函数,在主程序入口Main方法中,创建了Account类的实例myaccount,并将myaccount的属性输出到屏幕上。
(2)本示例程序的输出结果表明,在创建Accountl类的实例时,静态构造函数初始化了该实例的AccountDate属性和AccountMoney属性。在使用非静态构造函数创建Accountl类实例时,又修改了AccountMoney属性,最后AccountDate属性的值未修改,获得的是当前日期和时间,这个值是在静态函数执行时初始化的。而AccountMoney属性在非静态构造函数执行时又被赋值,最终变为非静态构造函数修改后的值。
8.8 使用私有构造函数
C#编程语言允许在类中定义私有的构造函数,私有构造函数的作用是在静态类中阻止外部程序创建类的实例。使用私有构造函数,可以在C#中便捷地实现Singleton模式。
技术要点
本示例主要说明了如何在程序中使用私有构造函数,技术要点如下。
● 因为类具有默认的构造函数,所以在一些需要阻止外部程序创建类实例的场合,需要显式声明私有构造函数。声明了私有构造函数的类,不能在类的外部创建类实例。
● 使用sealed关键字声明的类,称为密封类,被声明的类不能够派生子类。
实现步骤
(1)创建控制台应用程序项目,命名为“PrivateConstruct”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace PrivateConstruct { class Program { static void Main(string[] args) { Copyright copyright = Copyright.CurrentCopyRight; copyright.Company_Name = "***软件公司"; copyright.Begin_Date = DateTime.Parse("2002-01-01"); copyright.End_Date = DateTime.Now; Console.WriteLine("Copyright(c.):{0} {1}-{2}", copyright.Company_Name, copyright.Begin_Date.Year, copyright.End_Date.Year); Console.ReadLine(); } } sealed class Copyright//表示版权信息的类 { public string Company_Name; public DateTime Begin_Date; public DateTime End_Date; private Copyright()//私有构造函数 { } //提供静态的Copyright类实例供外部程序调用 public static Copyright CurrentCopyRight = new Copyright(); } }
(3)按F5键运行程序,运行结果如下所示。
Copyright(c.):***软件公司 2002-2007
源程序解读
(1)在本示例定义了一个表示版权信息的Copyright类,并在该类中定义了私有构造函数,阻止外部程序创建该类的实例。同时,提供一个静态实例供外部程序调用。
(2)Copyright类的声明语句包含sealed关键字,目的是为了更好地实现Singleton模式,即该类不能派生子类。排除在派生子类中创建类实例的可能。
8.9 使用析构函数
析构函数是类中一种特殊的方法,在销毁类实例的时候,程序将自动调用析构函数,释放当前实例占用的托管内存。
技术要点
本示例主要说明了如何在程序中使用析构函数,技术要点如下。
● 在父类中定义的析构函数,同时被子类继承。
● 析构函数在实例对象从内存中移除时才会被执行。
● 析构函数的执行顺序,是由继承级别自底向上析构的。
实现步骤
(1)创建控制台应用程序项目,命名为“Destructor”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace Destructor { class Program { static void Main(string[] args) { ShowDestructor();//创建类的实例 GC.Collect();//手动垃圾回收,使类实例运行析构函数 Console.ReadLine(); } static void ShowDestructor() { Third third = new Third();//创建类的实例 } } class First { ~First()//First类的析构函数 { Console.WriteLine("First类的析构函数运行。"); } } class Second:First//Second类继承于First类 { ~Second()//Second类的析构函数 { Console.WriteLine("Second类的析构函数运行。"); } } class Third : Second//Third类继承于First类 { ~Third()//Third类的析构函数 { Console.WriteLine("Third类的析构函数运行。"); } } }
(3)按F5键运行程序,运行结果如下所示。
Third类的析构函数运行。 Second类的析构函数运行。 First类的析构函数运行。
源程序解读
(1)本示例在三个类中分别定义了各自的析构函数,其中Second类继承于First类,Third类继承于First类,子类在继承的同时继承了父类的析构函数。
(2)在本示例中定义了ShowDestructor方法,使用该方法创建Third类的实例,在主程序入口Main方法中,通过手动垃圾回收,释放Third类实例占用的托管内存,由程序自动调用析构函数。
8.10 显式实现接口方法
在类继承多接口的时候,会遇到这种情况,即实现接口的方法时,两个或两个以上不同的接口可能会存在相同名称的方法成员,此时有两个选择,一种是使用隐式实现接口方法,此时两个或两个以上接口共用一个实现方法,另一种是显式实现接口,分别为两个或两个以上接口提供不同的实现方法。
技术要点
本示例主要说明了在程序中如何显式实现接口方法,并演示隐式实现和显式实现的区别,技术要点如下。
● 隐式实现接口方法时,两个或两个以上声明了该方法的接口使用相同的实现代码。
● 显式实现接口方法时,在类中分别为不同的接口方法编写不同的实现代码。
● 一般情况下建议使用显式实现接口方法,每个接口方法的实现代码不同,使程序更易懂,而且能够避免多个接口方法同名带来的混淆。
实现步骤
(1)创建控制台应用程序项目,命名为“ExplicitInterface”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace ExplicitInterface { class Program { static void Main(string[] args) { clsA clsa = new clsA();//创建clsA类的实例 intfA intfa = (intfA)clsa; intfa.MyMethod(); intfB intfb = (intfB)clsa; intfb.MyMethod(); clsB clsb = new clsB();//创建claB类的实例 intfa = (intfA)clsb; intfa.MyMethod(); intfb = (intfB)clsb; intfb.MyMethod(); Console.ReadLine(); } } interface intfA//接口A { void MyMethod(); } interface intfB//接口B { void MyMethod();//与接口A的方法同名 } class clsA:intfA,intfB { public void MyMethod()//隐式实现intfA和intfB接口 { Console.WriteLine("两个接口共用一个实现方法的示例。"); } } class clsB : intfA, intfB { #region intfA 成员 void intfA.MyMethod()//显式实现intfA接口 { Console.WriteLine("实现接口intfA的MyMethod方法。"); } #endregion #region intfB 成员 void intfB.MyMethod()//显式实现intfB接口 { Console.WriteLine("实现接口intfB的MyMethod方法。"); } #endregion } }
(3)按F5键运行程序,运行结果如下所示。
两个接口共用一个实现方法的示例。 两个接口共用一个实现方法的示例。 实现接口intfA的MyMethod方法。 实现接口intfB的MyMethod方法。
源程序解读
(1)本示例定义了intfA和intfB两个接口,并定义了clsA和clsB两个类,这两个类均同时继承和实现intfA和intfB接口。不同的是,clsA类隐式实现intfA和intfB接口,clsB类显式实现intfA和intfB接口。
(2)显式实现接口的方法,需要在方法前添加接口名称的前缀。
(3)在主程序入口Main方法中,分别创建clsA和clsB的实例,并调用实现接口的方法。在调用接口的时候,注意将类实例转换为指定的接口类型。
8.11 显式实现接口属性
接口的属性与接口的方法类似,也同样存在着类在多继承接口时属性同名的情况。同样地,多接口的重名属性也分两种方式解决,一种是隐式实现接口的属性,此时两个或两个以上不同的接口使用同一个属性,另一种是显式实现接口的属性,此时在类中分别为每个接口编写实现代码。
技术要点
本示例主要说明了如何在程序中显式实现接口属性,技术要点如下。
● 隐式实现接口属性时,两个或两个以上声明了该属性的接口,使用相同的实现代码。
● 显式实现接口方法时,在类中分别为不同的接口属性编写不同的实现代码。
● 一般情况下建议使用显式实现接口属性,每个接口属性的实现代码不同,使程序更易懂,而且能够避免多个接口属性同名带来的混淆。
● 在类中的接口属性,是以编写与属性名一致的方法来实现的。在调用时,需要将类的实例转换为接口类型。通过接口中声明的属性来调用类的实现代码。
实现步骤
(1)创建控制台应用程序项目,命名为“ExplicitInterfaceProperty”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace ExplicitInterfaceProperty { class Program { static void Main(string[] args) { Box mybox = new Box(200,100);// 声明类实例“myBox” // 声明英制单位接口的实例 IEnglishDimensions eDimensions = (IEnglishDimensions)mybox; // 声明公制单位接口的实例 IMetricDimensions mDimensions = (IMetricDimensions)mybox; Console.WriteLine("英制单位尺寸(in): {0}*{1}", eDimensions.Length(), eDimensions.Width()); Console.WriteLine("公制单位尺寸(cm): {0}*{1}", mDimensions.Length(), mDimensions.Width()); Console.ReadLine(); } } // 声明英制单位接口: interface IEnglishDimensions { double Length(); double Width(); } // 声明公制单位接口: interface IMetricDimensions { double Length(); double Width(); } class Box : IEnglishDimensions, IMetricDimensions//继承两个接口的类Box { private double Length; private double Width; #region IEnglishDimensions 成员 double IEnglishDimensions.Length()//显示实现IEnglishDimensions的Length属性 { return Length*2.54; } double IEnglishDimensions.Width()//显示实现IEnglishDimensions的Width属性 { return Width * 2.54; } #endregion #region IMetricDimensions 成员 double IMetricDimensions.Length()//显示实现IMetricDimensions的Length属性 { return Length; } double IMetricDimensions.Width()//显示实现IMetricDimensions的Width属性 { return Width; } #endregion public Box(double dLength, double dWidth)//Box类的构造函数 { Length = dLength; Width = dWidth; } } }
(3)按F5键运行程序,运行结果如下所示。
英制单位尺寸(in): 508*254 公制单位尺寸(cm): 200*100
源程序解读
(1)本示例定义了表示英制单位的IEnglishDimensions接口和表示公制单位的IMetricDimensions接口。Box类继承这两个接口,并在类中显式实现这两个接口的Length属性和Width属性。在主程序入口Main方法中,创建Box类的实例,同时声明两个接口类型的变量,将Box类实例分别转换为不同的接口,使用接口声明的方法,调用Box类中不同的属性实现代码。本示例程序的流程图如图8.7所示。
图8.7 显式实现接口属性的示例程序流程图
(2)显式实现接口的属性,需要在与属性名称一致的方法前添加接口名称的前缀。
(3)在主程序入口Main方法中,创建Box类的实例,并通过数据类型转换,将类实例转换为不同类型的接口实例。通过接口中声明的属性,调用类中实现该接口属性的代码。