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

第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属性将被隐藏。