第9章 方法和属性
方法是包含程序逻辑过程的代码段,而属性在类的内部对字段进行封装。在C#编程语言中,方法和属性作为类的重要组成部分,为类定义逻辑处理的过程和数据结构,方法和属性表现一个类最主要的特征,并提供该类具备的绝大部分功能。为了能够在面向对象编程的过程中更高效地实现设计目标,C#编程语言中对于方法和属性作了许多独特的设计。
9.1 方法的参数使用
方法能够带有参数执行,参数可以看成是外部程序与方法的交互数据,在一个方法中,可以包含一个或多个参数,其中多个参数使用“,”分隔符隔开,也可以不带参数执行。在C#编程语言中,参数还具有三个关键字前缀,即ref关键字、out关键字和params关键字。
技术要点
本示例主要说明了如何在程序中使用ref、out和params关键字定义方法的参数,技术要点如下。
● ref关键字定义的参数按引用传递,即在方法中对该参数进行的任何修改,都将反映在该参数中。在传递之前,参数必须完成初始化。
● out关键字定义的参数按引用传递,与ref关键字定义的参数类似,不同的是out关键字定义的参数不是在传递前完成初始化,而是必须在方法内部实现该参数的初始化。
● params关键字定义的参数表示方法的参数数量是可以变化的。为了保证参数数目及数据类型的正确,一般需要在方法内部对参数进行检查。
实现步骤
(1)创建控制台应用程序项目,命名为“ParameterOfMethod”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace ParameterOfMethod { class Program { static void Main(string[] args) { Employee emp; CreateEmployee(out emp); Console.WriteLine("员工数据:"+ GetEmployeeInfo(ref emp)); Console.ReadLine(); } //获取员工姓名的方法,使用ref关键字声明引用参数 static string GetEmployeeInfo(ref Employee refemp) { return "姓名——" + refemp.FirstName + " " + refemp.LastName + " 年龄——" + refemp.Age; } //创建员工类实例的方法,使用out关键字声明输出参数 static void CreateEmployee(out Employee outemp) { outemp = NewEmployee("Pony", "Smith", 41); } //创建员工类实例的方法,使用params关键字声明参数列表 static Employee NewEmployee(params object[] properties) { string strfirstname = ""; string strlastname = ""; int iage = 0; if (properties.Length == 3) { if (properties[0].GetType().FullName == "System.String") { strfirstname = properties[0].ToString(); } if (properties[1].GetType().FullName == "System.String") { strlastname = properties[1].ToString(); } if (properties[2].GetType().FullName == "System.Int32") { iage = (int)properties[2]; } Employee retemp = new Employee(strfirstname, strlastname, iage); return retemp; } else { return null; } } } class Employee//表示员工的类 { public string FirstName; public string LastName; public int Age; public Employee(string firstname,string lastname,int age) { FirstName = firstname; LastName = lastname; Age = age; } } }
(3)按F5键运行程序,运行结果如下所示。
员工数据:姓名——Pony Smith 年龄——41
源程序解读
(1)本示例定义了描述员工的Employee类,在主程序入口Main方法所在的Program类中定义了三个方法,其中GetEmployeeInfo方法用来获取员工类实例的数据,该方法的参数使用了ref关键字,CreateEmployee方法通过调用NewEmployee方法创建员工类的实例,员工类实例通过out关键字定义的参数传递,NewEmployee方法用来创建员工类实例,该方法使用params定义的参数列表,使用该参数列表中的数据创建员工类实例。本示例程序的流程图如图9.1所示。
图9.1 方法的参数示例程序流程图
(2)在NewEmployee方法中,参数使用了object数组,表示传入的参数可以是可变长度、任意数据类型的数组。为了能够正确地创建员工类实例,本示例在方法内部对传入参数进行检查。
9.2 方法的改写
当父类派生子类时,子类继承了父类中定义的方法,如果父类中的方法不适合子类的设计要求,就需要重新在子类中定义该方法,即在子类中改写方法。
技术要点
本示例主要说明了如何在包含继承类中的程序中改写方法,技术要点如下。
● 子类从父类继承时继承了父类的方法,当子类的方法行为与父类不一致,或者子类中需要更为具体的实现,例如抽象方法的实现时,需要对父类中定义的方法进行改写。
● 改写的方法需要使用override关键字声明,并且和父类保持同样的数据类型和参数列表。
● 在改写的方法中,可以使用base关键字,调用父类中的方法。
实现步骤
(1)创建控制台应用程序项目,命名为“OverrideMethod”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace OverrideMethod { class Program { static void Main(string[] args) { Employee emp = new Employee("Pony","Smith",41,"Enhineer"); Console.WriteLine("员工类实例数据:"+emp.GetInfo()); Console.ReadLine(); } } class Person//表示“人”的类 { public string FirstName; public string LadtName; public int Age; //Person类的构造函数,由参数指定类实例的字段值 public Person(string firstname, string lastname, int age) { FirstName = firstname; LadtName = lastname; Age = age; } //获取字段值组成的字符串的方法,该方法为虚函数,可以在子类中重写 public virtual string GetInfo() { return FirstName + " " + LadtName+" ," + Age.ToString(); } } class Employee:Person//表示员工的类 { public string Department; //员工类的构造函数,继承于Person类的构造函数 public Employee(string firstname, string lastname, int age, string department) : base(firstname, lastname, age) { Department = department; } //改写 public override string GetInfo() { return base.GetInfo()+" ,"+Department; } } }
(3)按F5键运行程序,运行结果如下所示。
员工类实例数据:Pony Smith ,41 ,Enhineer
源程序解读
(1)本示例定义了一个表示“人”的Person类和一个表示员工的Employee类,其中Employee类继承于Person类,在继承的同时,Employee类使用override关键字改写了Person类的GetInfo方法。
(2)Employee类在改写方法的方法中,同时利用了Person类中的GetInfo方法,此时使用了base关键字来调用父类中的方法。同样地,Employee类的构造函数,也使用了这种方法来继承父类的构造函数。
9.3 方法的重载
在一个类内部,一个方法可能会存在多种不同处理方式以及不同的处理结果,在这种情况下,在同一个类的内部,虽然方法的名称一致,但是返回的类型或者参数不一致,就存在方法的重载。方法的重载主要的作用是使方法具有更广泛的适用性。能够具备多种参数类型和处理结果。
技术要点
本示例主要说明了如何在程序中通过重载扩展方法的功能,技术要点如下。
● 在一个类内部定义的多个重名方法,无论返回类型是否一致,都被视为该方法的重载。
● 重载方法之间必须具有不同类型或者不同数量的参数类型。
实现步骤
(1)创建控制台应用程序项目,命名为“OverloadsMethod”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace OverloadsMethod { class Program { static void Main(string[] args) { Console.WriteLine("第一次比较结果:{0}", Compare("A", "a")); //根据参数的数目和数据类型,自动调用相应重载方法 Console.WriteLine("第二次比较结果:{0}", Compare("A", "a", true)); Console.ReadLine(); } //对比两个字符串的方法,不忽略大小写直接对比是否相等 static bool Compare(string strA, string strB) { return strA == strB; } //对比两个字符串的重载方法,ignoreCase参数表示是否忽略大小写 static bool Compare(string strA, string strB, bool ignoreCase) { if (ignoreCase)//对ignoreCase进行判断 { return strA.ToLower() == strB.ToLower(); } else { return strA == strB; } } } }
(3)按F5键运行程序,运行结果如下所示。
第一次比较结果:False 第二次比较结果:True
源程序解读
(1)本示例定义了比较字符串是否相等的Compare方法,并通过不同的参数类型实现了该方法的重载。
(2)在主程序入口Main方法中,使用不同的参数数量和数据类型调用Compare方法,程序会自动选择合适的Compare方法来处理。
9.4 类的属性定义
面向对象的一个重要特征是封装性。所谓封装,就是外部程序不需要了解类内部的实现过程或者结构,只需要了解类具有的功能和属性就能够使用。类的属性是实现封装性的一个数据结构,在C#编程语言中,属性由声明语句和get、set两个访问器组成,get访问器中定义属性的读取方式,set访问器中定义属性的赋值方式。
技术要点
本示例主要说明了如何在程序中定义类的属性,技术要点如下。
● get访问器必须返回一个值,或者抛出异常,总是以return或throw语句作为结束。未定义get访问器的属性不能从外部程序读取。
● set访问器中具有一个名称为value的隐藏变量,即外部程序赋给的属性值。未定义set访问器的属性不能由外部程序赋值。
实现步骤
(1)创建控制台应用程序项目,命名为“DefineProperty”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace DefineProperty { class Program { static void Main(string[] args) { Employee emp = new Employee(); emp.FirstName = "Pony"; emp.LastName = "Smoith"; emp.Age = 43; emp.Department = "Engineer"; 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; } } } }
(3)按F5键运行程序,运行结果如下所示。
姓名:Pony Smoith 年龄:43 部门:Engineer
源程序解读
(1)本示例定义了表示员工的Employee类,在该类中声明了员工的属性,并在属性中定义各自的get、set访问器。在主程序入口Main方法中分别为这些属性赋值,并访问这些属性,将属性值显示出来。
(2)本示例中的每个属性都有一个私有的成员字段相对应,这些私有字段的作用是保存属性值,并在外部程序读取属性时将该值返回到外部程序。
9.5 使用抽象属性
对于抽象类中定义的抽象属性,在继承该类的子类中实现。子类中的get访问器和set访问器必须与抽象父类一致。
技术要点
本示例主要说明了如何在抽象类中定义抽象属性,并在派生的子类中实现该抽象属性,技术要点如下。
● 抽象类中的抽象属性不能实现get访问器或set访问器,只能在派生的子类中实现。
● 派生的子类在实现抽象类的抽象属性时,需要使用override关键字,数据类型和访问器的定义必须与抽象类中的定义一致。
实现步骤
(1)创建控制台应用程序项目,命名为“AbstractProperty”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace AbstractProperty { class Program { static void Main(string[] args) { SimpleInterest simple = new SimpleInterest(); CompoundInterest comound = new CompoundInterest(); simple.Principal = 2000; simple.InterestRates = 0.05; simple.Period = 10; comound.Principal = 2000; comound.InterestRates = 0.05; comound.Period = 10; Console.WriteLine("以单利法计算的利息总和是:{0:c}", simple.Sum); Console.WriteLine("以复利法计算的利息总和是:{0:c}", comound.Sum); Console.ReadLine(); } } abstract class Interest//表示利息的类 { private double principal; public double Principal//表示本金的属性 { get { return principal; } set { principal = value; } } private double interestrates; public double InterestRates//表示利率的属性 { get { return interestrates; } set { interestrates = value; } } private int period; public int Period//表示时期的属性 { get { return period; } set { period = value; } } //表示利息总和的抽象属性,该属性只声明了get访问器,是只读属性 public abstract double Sum { get; } } class SimpleInterest : Interest//表示单利的类 { public override double Sum//抽象属性的实现 { get { return Principal * InterestRates * Period; } } } class CompoundInterest : Interest//表示复利的类 { public override double Sum//抽象属性的实现 { get { return Principal * (Math.Pow(1+InterestRates,Period)-1); } } } }
(3)按F5键运行程序,运行结果如下所示。
以单利法计算的利息总和是:¥1,000.00 以复利法计算的利息总和是:¥1,257.79
源程序解读
(1)本示例定义了表示利息的Interest抽象类,在该类中定义了Sum抽象属性,表示最终利息的金额,因为该属性是表示各种属性的计算结果,所以定义为只读属性,即在该属性的定义中不包含set访问器。由Interest抽象类派生的SimpleInterest类表示使用单利法计算利息。CompoundInterest类表示使用复利法计算利息。在这两个类内部实现了Sum抽象属性,分别使用单利法和复利法计算出最终的利息金额。在主程序入口Main方法中,分别创建两个派生类的示例,并为实例的属性赋值,最后通过各自Sum属性get访问器中的代码,计算出利息并显示出来。本示例程序的流程图如图9.2所示。
图9.2 使用抽象属性的示例程序流程图
(2)在派生子类中,对于抽象属性的继承,必须使用override关键字,说明是对抽象属性的改写。SimpleInterest类和CompoundInterest类在实现抽象属性Sum的代码时,都需要按照Interest中Sum属性的定义来定义get访问器,而不允许定义set访问器。
9.6 使用静态属性
在类中定义静态的属性,对类的静态字段进行了封装。类的静态属性通过类的名称调用,而不是通过类实例的名称调用。静态属性封装的成员字段或方法,必须也是静态的。
技术要点
本示例主要说明了如何在程序中实现类的静态属性定义,技术要点如下。
● 外部程序通过类的名称调用静态属性。
● 静态属性中的get访问器和set访问器中调用的成员必须是静态的。
实现步骤
(1)创建控制台应用程序项目,命名为“StaticProperty”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace StaticProperty { class Program { static void Main(string[] args) { Counter MyCounter = new Counter(); MyCounter.BaseNumber = 100; for (int i = 0; i < 5; i++) { Console.WriteLine("当前计数器的值是:" + Counter.Current); } Console.ReadLine(); } } class Counter//表示计数器的类 { private int basenumber; public int BaseNumber//表示基数的属性 { get { return basenumber; } set { current = value; //给基数赋值时同时赋值给当前值 basenumber = value; } } private static int current; public static int Current//表示当前值的属性 { get { return ++current;//递增 } } } }
(3)按F5键运行程序,运行结果如下所示。
当前计数器的值是:101 当前计数器的值是:102 当前计数器的值是:103 当前计数器的值是:104 当前计数器的值是:105
源程序解读
(1)本示例定义了表示计数器的Counter类,在该类中定义了表示计数的基数值的BaseNumber非静态属性和表示计数器当前值的静态属性Current,在BaseNumber属性的set访问器中,同时给Current属性进行初始化赋值,当外部程序访问Current属性时,Current属性将递增返回,实现计数功能。
(2)本示例中的Current属性是只读的,对Current属性的初始化,只能通过对BaseNumber属性的赋值来完成。在主程序入口Main方法中,程序创建了一个Counter类的实例,并为该实例的BaseNumber属性赋值,此时Current的初始化完成,外部程序每读取一次Current属性,该属性都将返回一个递增的值。
9.7 属性的继承
在父类派生子类的时候,子类继承了父类中定义的属性,如果存在子类中的属性与父类的属性不一致的情况时,就有必要在子类中改写该属性。
技术要点
本示例主要说明了如何在子类继承父类属性时改写属性,技术要点如下。
● 子类在改写父类的属性时,将会“隐藏”父类的属性,此时必须在属性声明语句中使用new关键字。
● 子类改写属性时不必遵循父类中改属性的get访问器和set访问器设定,例如,父类中定义允许读写的属性,在子类中可以修改为只读属性。
实现步骤
(1)创建控制台应用程序项目,命名为“InheritProperty”。
(2)打开并编辑Program.cs文件,代码如下所示。
using System; using System.Collections.Generic; using System.Text; namespace InheritProperty { class Program { static void Main(string[] args) { Employee employee = new Employee(); employee.Name = "John"; Retired retired = new Retired(); retired.Name = "John"; Console.WriteLine(employee.Name); Console.WriteLine(retired.Name); Console.ReadLine(); } } class Employee//表示员工的类 { private string name; public string Name//表示员工姓名的属性 { get { return name; } set { name = value; } } } class Retired : Employee//表示退休员工的类,继承于员工类 { private string name; //表示退休员工姓名的属性,使用new关键字隐藏了Employee类的Name属性 public new string Name { get { return name; } set { name = value + "——Retired"; //在姓名后面添加字符串 } } } }
(3)按F5键运行程序,运行结果如下所示。
John John——Retired
源程序解读
(1)本示例中定义了表示员工的Employee类和表示退休员工的Retired类,其中Retired类继承于Employee类,如果程序未改写Name属性,Employee类的Name属性和Retired类的Name属性行为一致。本示例程序在Retired类中,改写了Employee类的Name属性,即在Name属性后添加指定的字符串。此时Employee类的Name属性和Retired类的Name属性行为不一致。
(2)在声明Retired类的Name属性时,使用了new关键字,此时从Employee类继承的、同一名称的Name属性将被隐藏。