使用C#类的方法
使用C#类的方法
	第五章 类
	前一章讨论了数据类型和它们的用法。现在我们转移到C#中至关重要的结构——类。没
	有了
	类,就连简单的C#程序都不能编译。这一章假定你知道了一个类的基本组成部分:方法
	、属性、构
	造函数和析构函数。 C#在其中增加了索引和事件。
	在这一章中,你学到下列有关类的话题。
	。 使用构造函数和析构函数
	。给类写方法
	。给一个类增加属性存取标志
	。实现索引
	。创建事件并通过代表元为事件关联客户
	。应用类、成员和存取修饰符。
	5.1 构造函数和析构函数
	在你可以访问一个类的方法、属性或任何其它东西之前,第一条执行的语句是包含有相
	
	应类的构造函数。甚至你自己不写一个构造函数,也会有一个缺省的构造函数提供给你
	。
	class TestClass
	{
	public TestClass():base(){}//由编译器提供
	}
	一个构造函数总是和它的类名相同,但是,它没有声明返回类型。总之,构造函数总是
	
	public 的,你可以用它们来初始化变量。
	public TestClass()
	{
	//在这给变量
	//初始化代码等等。
	}
	如果类仅包含静态成员(能以类型调用,而不是以实例调用的成员),你可以创建一个
	private 的构造函数。
	private TestClass(){}
	尽管存取修饰符在这一章的后面将要大篇幅地讨论,但是private 意味着从类的外面不
	可能?第28 页 共104 页
	访问该构造函数。所以,它不能被调用,且没有对象可以自该类定义被实例化。
	并不仅限于无参数构造函数——你可以传递初始参数来初始化成员。
	public TestClass(string strName,int nAge){...}
	作为一个C/C++程序员,你可能习惯于给初始化写一个附加的方法,因为在构造函数中没
	有
	返回值。当然,尽管在C#中也没有返回值,但你可以引发一个自制的异常,以从构造函
	数获得返回
	值。更多有关异常处理的知识在第七章 "异常处理"中有讨论。
	但是,当你保留引用给宝贵的资源,应该想到写一个方法来解决:一个可以被显式地调
	用来
	释放这些资源。问题是当你可以在析构函数(以类名的前面加"~"的方式命名)中做同样的
	事情时,为
	何还要写一个附加的方法.
	public ~TestClass()
	{
	//清除
	}
	你应该写一个附加方法的原因是垃圾收集器,它在变量超出范围后并不会立即被调用,
	而仅当
	间歇期间或内存条件满足时才被触发。当你锁住资源的时间长于你所计划的时间时,它
	就会发生。
	因此,提供一个显式的释放方式是一个好主意,它同样能从析构函数中调用。
	public void Release()
	{
	//释放所有宝贵的资源
	}
	public ~TestClass()
	{
	Release();
	}
	调用析构函数中的释放方法并不是必要的——总之,垃圾收集会留意释放对象。但没有
	忘记清
	除是一种良好的习惯。
	5.2 方法
	既然对象能正确地初始化和结束,所剩下来的就是往类中增加功能。在大多数情况下,
	功能的
	主要部分在方法中能得到实现。你早已见过静态方法的使用,但是,这些是类型(类)的
	部分,不是?展现C#
	共104 页 第29 页
	实例(对象)。
	为了让你迅速入门,我把这些方法的烦琐问题安排为三节:
	。方法参数
	。改写方法
	。方法屏蔽
	5.2.1 方法参数
	因方法要处理更改数值,你多多少少要传递值给方法,并从方法获得返回值。以下三个
	部分涉及
	到由传递值和为调用者获取返回结果所引起的问题。
	。输入参数
	。引用参数
	。输出参数
	5.2.1.1 输入参数
	你早已在例子中见过的一个参数就是输入参数。你用一个输入参数通过值传递一个变量
	给一个方
	法——方法的变量被调用者传递进来的值的一个拷贝初始化。清单5.1 示范输入参数的
	使用。
	清单 5.1 通过值传递参数
	1:using System;
	2:
	3:public class SquareSample
	4:{
	5:public int CalcSquare(int nSideLength)
	6:{
	7:return nSideLength*nSideLength;
	8:}
	9:}
	10:
	11:class SquareApp
	12:{
	13:public static void Main()
	14:{
	15:SquareSample sq =new SquareSample();
	16:Console.WriteLine(sq.CalcSquare(25).ToString());
	17:}
	18:}?第30 页 共104 页
	因为我传递值而不是引用给一个变量,所以当调用方法时(见第16 行),可以使用一个常
	量表达式
	(25)。整型结果被传回给调用者作为返回值,它没有存到中间变量就被立即显示到屏幕
	上 。
	输入参数按C/C++程序员早已习惯的工作方式工作。如果你来自VB,请注意没有能被编译
	器处理
	的隐式ByVal 或ByRef ——如果没有设定,参数总是用值传递。
	这点似乎与我前面所陈述的有冲突:对于一些变量类型,用值传递实际上意味着用引用
	传递。
	迷惑吗?一点背景知识也不需要:COM 中的东西就是接口,每一个类可以拥有一个或多个
	接口。一个
	接口只不过是一组函数指针,它不包含数据。重复该数组会浪费很多内存资源;所以,
	仅开始地址
	被拷贝给方法,它作为调用者,仍然指向接口的相同指针。那就是为什么对象用值传递
	一个引用。
	5.2.1.2 引用参数
	尽管可以利用输入参数和返回值建立很多方法,但你一想到要传递值并原地修改它(也就
	是在相
	同的内存位置),就没有那么好运了。这里用引用参数就很方便。
	void myMethod(ref int nInOut)
	因为你传递了一个变量给该方法(不仅仅是它的值),变量必须被初始化。否则,编译器
	会报警。
	清单 5.2 显示如何用一个引用参数建立一个方法。
	清单 5.2 通过引用传递参数
	1://class SquareSample
	2:using System;
	3:
	4:public class SquareSample
	5:{
	6:public void CalcSquare(ref int nOne4All)
	7:{
	8:nOne4All *=nOne4All;
	9:}
	10:}
	11:
	12:class SquareApp
	13:{
	14:public static void Main()
	15:{
	16:SquareSample sq =new SquareSample();
	17:
	18:int nSquaredRef =20;//一定要初始化?展现C#
	共104 页 第31 页
	19:sq.CalcSquare(ref nSquaredRef);
	20:Console.WriteLine(nSquaredRef.ToString());
	21:}
	22:}
	正如所看到的,所有你要做的就是给定义和调用都加上ref 限定符。因为变量通过引用
	传递,你
	可以用它来计算出结果并传回该结果。但是,在现实的应用程序中,我强烈建议要用两
	个变量,一
	个输入参数和一个引用参数。
	5.2.1.3 输出参数
	传递参数的第三种选择就是把它设作一个输出参数。正如该名字所暗示,一个输出参数
	仅用于从
	方法传递回一个结果。它和引用参数的另一个区别在于:调用者不必先初始化变量才调
	用方法。这
	显示在清单5.3 中。
	清单 5.3 定义一个输出参数
	1:using System;
	2:
	3:public class SquareSample
	4:{
	5:public void CalcSquare(int nSideLength,out int nSquared)
	6:{
	7:nSquared =nSideLength *nSideLength;
	8:}
	9:}
	10:
	11:class SquareApp
	12:{
	13:public static void Main()
	14:{
	15:SquareSample sq =new SquareSample();
	16:
	17:int nSquared;//不必初始化
	18:sq.CalcSquare(15,out nSquared);
	19:Console.WriteLine(nSquared.ToString());
	20:}
	21:}?第32 页 共104 页
	5.2.2 改写方法
	面向对象设计的重要原则就是多态性。不要理会高深的理论,多态性意味着:当基类程
	序员已
	设计好用于改写的方法时,在派生类中,你就可以重定义(改写)基类的方法。基类程序
	员可以用
	virtual 关键字设计方法:
	virtual void CanBOverridden()
	当从基类派生时,所有你要做的就是在新方法中加入override 关键字:
	override void CanBOverridden()
	当改写一个基类的方法时,你必须明白,不能改变方法的访问属性——在这章的后面,
	你会学
	到更多关于访问修饰符的知识。
	除了改写基类方法的事实外,还有另一个甚至更重要的改写特性。当把派生类强制转换
	成基类
	类型并接着调用虚拟方法时,被调用的是派生类的方法而不是基类的方法。
	((BaseClass)DerivedClassInstance).CanBOverridden();
	为了演示虚拟方法的概念,清单 5.4 显示如何创建一个三角形基类,它拥有一个可以被
	改
	写的成员方法(ComputeArea)。
	清单 5.4 改写一个基类的方法
	1:using System;
	2:
	3:class Triangle
	4:{
	5:public virtual double ComputeArea(int a,int b,int c)
	6:{
	7://Heronian formula
	8:double s =(a +b +c)/2.0;
	9:double dArea =Math.Sqrt(s*(s-a)*(s-b)*(s-c));
	10:return dArea;
	11:}
	12:}
	13:
	14:class RightAngledTriangle:Triangle
	15:{
	16:public override double ComputeArea(int a,int b,int c)
	17:{
	18:double dArea =a*b/2.0;
	19:return dArea;
	20:}
	21:}?展现C#
	共104 页 第33 页
	22:
	23:class TriangleTestApp
	24:{
	25:public static void Main()
	26:{
	27:Triangle tri =new Triangle();
	28:Console.WriteLine(tri.ComputeArea(2,5,6));
	29:
	30:RightAngledTriangle rat =new RightAngledTriangle();
	31:Console.WriteLine(rat.ComputeArea(3,4,5));
	32:}
	33:}
	基类Triangle 定义了方法ComputeArea 。它采用三个参数,返回一个double 结果,且
	具有公共访
	问性。从Triangle 类派生出的是RightAngledTriangle,它改写了ComputeArea 方法,
	并实现了自己
	的面积计算公式。两个类都被实例化,且在命名为TriangleTestApp 的应用类的Main()
	方法中得到
	验证。
	我漏了解释第14 行:
	class RightAngledTriangle :Triangle
	在类语句中冒号(:)表示RightAngledTriangle 从类 Triangle 派生。那就是你所必
	须要做的
	,以让C#知道你想把 Triangle 当作RightAngledTriangle 的基类。
	当仔细观察直角三角形的ComputeArea 方法时,你会发现第3 个参数并没有用于计算。
	但是,利
	用该参数就可以验证是否是“直角”。如清单5.5 所示。
	清单 5.5 调用基类实现
	1:class RightAngledTriangle:Triangle
	2:{
	3:public override double ComputeArea(int a,int b,int c)
	4:{
	5:const double dEpsilon =0.0001;
	6:double dArea =0;
	7:if (Math.Abs((a*a +b*b -c*c))>dEpsilon)
	8:{
	9:dArea =base.ComputeArea(a,b,c);
	10:}
	11:else
	12:{
	13:dArea =a*b/2.0;
	14:}?第34 页 共104 页
	15:
	16:return dArea;
	17:}
	18:}
	该检测简单地利用了毕达哥拉斯公式,对于直角三角形,检测结果必须为0 。如果结果
	不为0,类
	就调用它基类的 ComputeArea 来实现。
	dArea =base.ComputeArea(a,b,c);
	例子的要点为:通过显式地利用基类的资格检查,你就能轻而易举地调用基类实现改写
	方法。
	当你需要实现其在基类中的功能,而不愿意在改写方法中重复它时,这就非常有帮助。
	
	5.2.3 方法屏蔽
	重定义方法的一个不同手段就是要屏蔽基类的方法。当从别人提供的类派生类时,这个
	功能特
	别有价值。看清单 5.6,假设BaseClass 由其他人所写,而你从它派生出 DerivedClas
	s 。
	清单 5.6 Derived Class 实现一个没有包含于 Base Class 中的方法
	1:using System;
	2:
	3:class BaseClass
	4:{
	5:}
	6:
	7:class DerivedClass:BaseClass
	8:{
	9:public void TestMethod()
	10:{
	11:Console.WriteLine("DerivedClass::TestMethod");
	12:}
	13:}
	14:
	15:class TestApp
	16:{
	17:public static void Main()
	18:{
	19:DerivedClass test =new DerivedClass();
	20:test.TestMethod();
	21:}
	22:}
	在这个例子中,DerivedClass 通过TestMethod()实现了一个额外的功能。但是,如果基
	类的?展现C#
	共104 页 第35 页
	开发者认为把TestMethod()放在基类中是个好主意,并使用相同的名字实现它时,会出
	现什么问题
	呢?(见清单5.7)
	清单 5.7 Base Class 实现和 Derived Class 相同的方法
	1:class BaseClass
	2:{
	3:public void TestMethod()
	4:{
	5:Console.WriteLine("BaseClass::TestMethod");
	6:}
	7:}
	8:
	9:class DerivedClass:BaseClass
	10:{
	11:public void TestMethod()
	12:{
	13:Console.WriteLine("DerivedClass::TestMethod");
	14:}
	15:}
	在优秀的编程语言中,你现在会遇到一个真正的大麻烦。但是,C#会给你提出警告:
	hiding2.cs(13,14):warning CS0114:'DerivedClass.TestMethod()'hides inherited
	member
	'BaseClass.TestMethod()'.To make the current method override that implementa
	tion,add
	the override keyword.Otherwise add the new keyword.
	(hiding2.cs(13,14):警告 CS0114:'DerivedClass.TestMethod()'屏蔽了所继承的成员
	
	'BaseClass.TestMethod()'。要想使当前方法改写原来的实现,加上 override 关键字
	。否则加上新
	的关键字。)
	具有了修饰符new,你就可以告诉编译器,不必重写派生类或改变使用到派生类的代码,
	你的方法就
	能屏蔽新加入的基类方法。清单5.8 显示如何在例子中运用new 修饰符。
	清单 5.8 屏蔽基类方法
	1:class BaseClass
	2:{
	3:public void TestMethod()
	4:{
	5:Console.WriteLine("BaseClass::TestMethod");
	6:}?第36 页 共104 页
	7:}
	8:
	9:class DerivedClass:BaseClass
	10:{
	11:new public void TestMethod()
	12:{
	13:Console.WriteLine("DerivedClass::TestMethod");
	14:}
	15:}
	使用了附加的new 修饰符,编译器就知道你重定义了基类的方法,它应该屏蔽基类方法
	。但是,如果
	你按以下方式编写:
	DerivedClass test =new DerivedClass();
	((BaseClass)test).TestMethod();
	基类方法的实现就被调用了。这种行为不同于改写方法,后者保证大部分派生方法获得
	调用。
	5.3 类属性
	有两种途径揭示类的命名属性——通过域成员或者通过属性。前者是作为具有公共访问
	性的成员变量而被
	实现的;后者并不直接回应存储位置,只是通过 存取标志(accessors)被访问。
	当你想读出或写入属性的值时,存取标志限定了被实现的语句。用于读出属性的值的存
	取标志记为关键字
	get ,而要修改属性的值的读写符标志记为set 。在你对该理论一知半解以前,请看一
	下清单5.9 中的例子,
	属性SquareFeet 被标上了get 和set 的存取标志。
	清单 5.9 实现属性存取标志
	1:using System;
	2:
	3:public class House
	4:{
	5:private int m_nSqFeet;
	6:
	7:public int SquareFeet
	8:{
	9:get {return m_nSqFeet;}
	10:set {m_nSqFeet =value;}
	11:}
	12:}
	13:
	14:class TestApp
	15:{
	16:public static void Main()
	17:{
	18:House myHouse =new House();?展现C#
	共104 页 第37 页
	19:myHouse.SquareFeet =250;
	20:Console.WriteLine(myHouse.SquareFeet);
	21:}
	22:}
	House 类有一个命名为SquareFeet 的属性,它可以被读和写。实际的值存储在一个可以
	从类内部访问的变
	量中——如果你想当作一个域成员重写它,你所要做的就是忽略存取标志而把变量重新
	定义为:
	public int SquareFeet;
	对于一个如此简单的变量,这样不错。但是,如果你想要隐藏类内部存储结构的细节时
	,就应该采用存取
	标志。在这种情况下,set 存取标志给值参数中的属性传递新值。(可以改名,见第10
	行。)
	除了能够隐藏实现细节外,你还可自由地限定各种操作:
	get 和set:允许对属性进行读写访问。
	get only:只允许读属性的值。
	set only:只允许写属性的值。
	除此之外,你可以获得实现在set 标志中有效代码的机会。例如,由于种种原因(或根
	本没有原因),你
	就能够拒绝一个新值。最好是没有人告诉你它是一个动态属性——当你第一次请求它后
	,它会保存下来,
	故要尽可能地推迟资源分配。
	5.4 索引
	你想过象访问数组那样使用索引访问类吗 ?使用C#的索引功能,对它的期待便可了结。
	
	语法基本上象这样:
	属性 修饰符 声明 {声明内容}
	具体的例子为
	public string this[ int nIndex]
	{
	get {...}
	set {...}
	}
	索引返回或按给出的index 设置字符串。它没有属性,但使用了public 修饰符。声明部
	分由类型string
	和this 组成用于表示类的索引。get 和set 的执行规则和属性的规则相同。(你不能取
	消其中一个。)只
	存在一个差别,那就是:你几乎可以任意定义大括弧中的参数。限制为,必须至少规定
	一个参数,允许ref
	和out 修饰符。
	this 关键字确保一个解释。索引没有用户定义的名字,this 表示默认接口的索引。如
	果类实现了多个接
	口,你可以增加更多个由InterfaceName.this 说明的索引。
	为了演示一个索引的使用,我创建了一个小型的类,它能够解析一个主机名为IP 地址—
	—或一个IP 地址
	列表(以http://www.microsoft.com 为例 )。这个列表通过索引可以访问,你可以看一
	下清单
	5.10 的具体实现。?第38 页 共104 页
	清单 5.10 通过一个索引获取一个IP 地址
	1:using System;
	2:using System.Net;
	3:
	4:class ResolveDNS
	5:{
	6:IPAddress[ ] m_arrIPs;
	7:
	8:public void Resolve(string strHost)
	9:{
	10:IPHostEntry iphe =DNS.GetHostByName(strHost);
	11:m_arrIPs =iphe.AddressList;
	12:}
	13:
	14:public IPAddress this[ int nIndex]
	15:{
	16:get
	17:{
	18:return m_arrIPs[ nIndex] ;
	19:}
	20:}
	21:
	22:public int Count
	23:{
	24:get {return m_arrIPs.Length;}
	25:}
	26:}
	27:
	28:class DNSResolverApp
	29:{
	30:public static void Main()
	31:{
	32:ResolveDNS myDNSResolver =new ResolveDNS();
	33:myDNSResolver.Resolve("http://www.microsoft.com");
	34:
	35:int nCount =myDNSResolver.Count;
	36:Console.WriteLine("Found {0}IP's for hostname",nCount);
	37:for (int i=0;i <nCount;i++)
	38:Console.WriteLine(myDNSResolver[ i] );
	39:}
	40:}
	为了解析主机名,我用到了DNS 类,它是System .Net 名字空间的一部分。但是,由于
	这个名字空间并不
	包含在核心库中,所以必须在编译命令行中引用该库:?展现C#
	共104 页 第39 页
	csc /r:System.Net.dll /out:resolver.exe dnsresolve.cs
	解析代码是向前解析的。在该 Resolve 方法中,代码调用DNS 类的静态方法GetHostBy
	Name,它返回一个
	IPHostEntry 对象。结果,该对象包含有我要找的数组——AddressList 数组。在退出
	Resolve 方法之前,
	在局部的对象实例成员m_arrIPs 中,存储了一个AddressList array 的拷贝(类型IPAd
	dress 的对象存储
	在其中)。
	具有现在生成的数组 ,通过使用在类ResolveDNS 中求得的索引,应用程序代码就可以
	在第37 至38 行列
	举出IP 地址。(在第6 章 "控制语句",有更多有关语句的信息。)因为没有办法更改IP
	地址,所以仅给
	索引使用了get 存取标志。为了简单其见,我忽略了数组的边界溢出检查。
	5.4 事件
	当你写一个类时,有时有必要让类的客户知道一些已经发生的事件。如果你是一个具有
	多年编程经验的程
	序员,似乎有很多的解决办法,包括用于回调的函数指针和用于ActiveX 控件的事件接
	收(event sinks)。
	现在你将要学到另外一种把客户代码关联到类通知的办法——使用事件。
	事件既可以被声明为类域成员(成员变量),也可以被声明为属性。两者的共性为,事
	件的类型必定是代
	表元,而函数指针原形和C#的代表元具有相同的含义。
	每一个事件都可以被0 或更多的客户占用,且客户可以随时关联或取消事件。你可以以
	静态或者以实例方
	法定义代表元,而后者很受C++程序员的欢迎。
	既然我已经提到了事件的所有功能及相应的代表元,请看清单5.11 中的例子。它生动地
	体现了该理论。
	清单5.11 在类中实现事件处理
	1:using System;
	2:
	3://向前声明
	4:public delegate void EventHandler(string strText);
	5:
	6:class EventSource
	7:{
	8:public event EventHandler TextOut;
	9:
	10:public void TriggerEvent()
	11:{
	12:if (null !=TextOut)TextOut("Event triggered");
	13:}
	14:}
	15:
	16:class TestApp
	17:{
	18:public static void Main()
	19:{
	20:EventSource evsrc =new EventSource();
	21:
	22:evsrc.TextOut +=new EventHandler(CatchEvent);
	23:evsrc.TriggerEvent();?第40 页 共104 页
	24:
	25:evsrc.TextOut -=new EventHandler(CatchEvent);
	26:evsrc.TriggerEvent();
	27:
	28:TestApp theApp =new TestApp();
	29:evsrc.TextOut +=new EventHandler(theApp.InstanceCatch);
	30:evsrc.TriggerEvent();
	31:}
	32:
	33:public static void CatchEvent(string strText)
	34:{
	35:Console.WriteLine(strText);
	36:}
	37:
	38:public void InstanceCatch(string strText)
	39:{
	40:Console.WriteLine("Instance "+strText);
	41:}
	42:}
	第4 行声明了代表元(事件方法原形),它用来给第8 行中的EventSource 类声明Text
	Out 事件域成员。
	你可以观察到代表元作为一种新的类型声明,当声明事件时可以使用代表元。
	该类仅有一个方法,它允许我们触发事件。请注意,你必须进行事件域成员不为null 的
	检测,因为可能会
	出现没有客户对事件感兴趣这种情况。
	TestApp 类包含了Main 方法,也包含了另外两个方法,它们都具备事件所必需的信号。
	其中一个方法是静
	态的,而另一个是实例方法。
	EventSource 被实例化,而静态方法CatchEvent 被预关联上了 TextOut 事件:
	evsrc.TextOut +=new EventHandler(CatchEvent);
	从现在起,当事件被触发时,该方法被调用。如果你对事件不再感兴趣,简单地取消关
	联:
	evsrc.TextOut -=new EventHandler(CatchEvent);
	注意,你不能随意取消关联的处理函数——在类代码中仅创建了这些处理函数。为了证
	明事件处理函数也
	和实例方法一起工作,余下的代码建立了TestApp 的实例,并钩住事件处理方法。
	事件在哪方面对你特别有用?你将经常在ASP+中或使用到WFC (Windows Foundation Cl
	asses)时,涉及到
	事件和代表元。
	5.5 应用修饰符
	在这一章的学习过程中,你已经见过了象public 、virtual 等修饰符。欲以一种易于理
	解的方法概括它们,
	我把它们划分为三节:
	。类修饰符
	。成员修饰符
	。存取修饰符?展现C#
	共104 页 第41 页
	5.5.1 类修饰符
	到目前为止,我还没有涉及到类修饰符,而只涉及到了应用于类的存取修饰符。但是,
	有两个修饰符你可
	以用于类:
	abstract ——关于抽象类的重要一点就是它不能被实例化。只有不是抽象的派生类才能
	被实例化。派生类
	必须实现抽象基类的所有抽象成员。你不能给抽象类使用sealed 修饰符。
	sealed ——密封 类不能被继承。使用该修饰符防止意外的继承,在.NET 框架中的类用
	到这个修饰符。
	要见到两个修饰符的运用,看看清单5.12 ,它创建了一个基于一个抽象类的密封类(肯
	定是一个十分极
	端的例子)。
	清单 5.12 抽象类和密封类
	1:using System;
	2:
	3:abstract class AbstractClass
	4:{
	5:abstract public void MyMethod();
	6:}
	7:
	8:sealed class DerivedClass:AbstractClass
	9:{
	10:public override void MyMethod()
	11:{
	12:Console.WriteLine("sealed class");
	13:}
	14:}
	15:
	16:public class TestApp
	17:{
	18:public static void Main()
	19:{
	20:DerivedClass dc =new DerivedClass();
	21:dc.MyMethod();
	22:}
	23:}
	5.5.2 成员修饰符
	与有用的成员修饰符的数量相比,类修饰符的数量很少。我已经提到了一些,这本书即
	将出现的例子描述
	了其它的成员修饰符。
	以下是有用的成员修饰符:
	abstract ——说明一个方法或存取标志不能含有一个实现。它们都是隐式虚拟,且在继
	承类中,你必须提
	供 override 关键字。?第42 页 共104 页
	const ——这个修饰符应用于域成员或局部变量。在编译时常量表达式被求值,所以,
	它不能包含变量的引
	用。
	event ——定义一个域成员或属性作为类型事件。用于捆绑客户代码到类的事件。
	extern ——告诉编译器方法实际上由外部实现。第10 章 “ 和非受管代码互相操作”
	将全面地涉及到外部
	代码。
	override ——用于改写任何基类中被定义为virtual 的方法和存取标志。要改写的名字
	和基类的方法必须
	一致。
	readonly ——一个使用 readonly 修饰符的域成员只能在它的声明或者在包含它的类的
	构造函数中被更改。
	static ——被声明为static 的成员属于类,而不属于类的实例。你可以用static 于域
	成员、方法、属性、
	操作符甚至构造函数。
	virtual ——说明方法或存取标志可以被继承类改写。
	5.5.3 存取修饰符
	存取修饰符定义了某些代码对类成员(如方法和属性)的存取等级。你必须给每个成员
	加上所希望的存取
	修饰符,否则,默认的存取类型是隐含的。
	你可以应用4 个 存取修饰符之一:
	public ——任何地方都可以访问该成员,这是具有最少限制的存取修饰符。
	protected ——在类及所有的派生类中可以访问该成员,不允许外部访问。
	private ——仅仅在同一个类的内部才能访问该成员。甚至派生类都不能访问它。
	internal ——允许相同组件(应用程序或库)的所有代码访问。在.NET 组件级别,你
	可以把它视为public,
	而在外部则为private 。
	为了演示存取修饰符的用法,我稍微修改了Triangle 例子,使它包含了新增的域成员和
	一个新的派生类(见
	清单 5.13)。
	清单 5.13 在类中使用存取修饰符
	1:using System;
	2:
	3:internal class Triangle
	4:{
	5:protected int m_a,m_b,m_c;
	6:public Triangle(int a,int b,int c)
	7:{
	8:m_a =a;
	9:m_b =b;
	10:m_c =c;
	11:}
	12:
	13:public virtual double Area()
	14:{
	15://Heronian formula
	16:double s =(m_a +m_b +m_c)/2.0;?展现C#
	共104 页 第43 页
	17:double dArea =Math.Sqrt(s*(s-m_a)*(s-m_b)*(s-m_c));
	18:return dArea;
	19:}
	20:}
	21:
	22:internal class Prism:Triangle
	23:{
	24:private int m_h;
	25:public Prism(int a,int b,int c,int h):base(a,b,c)
	26:{
	27:m_h =h;
	28:}
	29:
	30:public override double Area()
	31:{
	32:double dArea =base.Area()*2.0;
	33:dArea +=m_a*m_h +m_b*m_h +m_c*m_h;
	34:return dArea;
	35:}
	36:}
	37:
	38:class PrismApp
	39:{
	40:public static void Main()
	41:{
	42:Prism prism =new Prism(2,5,6,1);
	43:Console.WriteLine(prism.Area());
	44:}
	45:}
	Triangle 类和 Prism 类现在被标为 internal 。这意味着它们只能在当前组件中被访
	问。
	请记住“.NET 组件”这个术语指的是包装(packaging,),而不是你可能在COM+中用到
	的组件。
	Triangle 类有三个 protected 成员,它们在构造函数中被初始化,并用于面积计算的
	方法中。由于这些
	成员是protected 成员,所以我可以在派生类Prism 中访问它们,在那里执行不同的面
	积计算。
	Prism 自己新增了一个成员m_h,它是私有的——甚至派生类也不能访问它。
	花些时间为每个类成员甚至每个类计划一种保护层次,通常是个好主意。当需要引入修
	改时,全面的计划
	最终会帮助你,因为没有程序员会愿意使用“没有文档”的类功能。?第44 页 共104 页
	
	5.6 小结
	这章显示了类的各种要素,它是运行实例(对象)的模板。在一个对象的生命期,首先
	被执行的代码是个
	构造函数。构造函数用来初始化变量,这些变量后来在方法中用于计算结果。
	方法允许你传递值、引用给变量,或者只传送一个输出值。方法可以被改写以实现新的
	功能,或者你可以
	屏蔽基类成员,如果它实现了一个具有和派生类成员相同名字的方法。
	命名属性可以被当作域成员(成员变量)或属性存取标志实现。后者是get 和set 存取
	标志,忽略一个或
	另外一个,你可以创建仅写或仅读属性。存取标志非常适合于确认赋给属性的值。
	C#类的另外一个功能是索引,它使象数组语法一样访问类中值成为可能。还有,如果当
	类中的某些事情发
	生时,你想客户得到通知,要让它们与事件关联。
	当垃圾收集器调用析构函数时,对象的生命就结束了。由于你不能准确地预测这种情况
	什么时候会发生,
	所以应该创建一个方法以释放这些宝贵的资源,当你停止使用它们时。