Visual C# 2008开发技术实例详解
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第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类的实例,并通过数据类型转换,将类实例转换为不同类型的接口实例。通过接口中声明的属性,调用类中实现该接口属性的代码。