下面是小编整理的第五章 类 (rainbow 翻译).net,本文共4篇,希望能帮助到大家!本文原稿由网友“shenhai”提供。
篇1:第五章 类 (rainbow 翻译).net
第五章 类(1) 前一章讨论了数据类型和它们的用法,现在我们转移到C#中至关重要的结构――类。没有了类,就连简单的C#程序 都不能编译。这一章假定你知道了一个类的基本组成部分:方法、属性、构造函数和析构函数。 C#在其中增加了索引和事 件。 在这一章中,
第五章 类(1)
前一章讨论了数据类型和它们的用法。现在我们转移到C#中至关重要的结构――类。没有了类,就连简单的C#程序
都不能编译。这一章假定你知道了一个类的基本组成部分:方法、属性、构造函数和析构函数。 C#在其中增加了索引和事
件。
在这一章中,你学到下列有关类的话题。
。 使用构造函数和析构函数
。给类写方法
。给一个类增加属性存取标志
。实现索引
。创建事件并通过代表元为事件关联客户
。应用类、成员和存取修饰符。
5.1 构造函数和析构函数
在你可以访问一个类的方法、属性或任何其它东西之前, 第一条执行的语句是包含有相应类的构造函数。甚至
你自己不写一个构造函数,也会有一个缺省的构造函数提供给你。
class TestClass
{
public TestClass: base() {} // 由编译器提供
}
一个构造函数总是和它的类名相同,但是,它没有声明返回类型。总之,构造函数总是public的,你可以用它们来
初始化变量。
public TestClass()
{
// 在这给变量
// 初始化代码等等。
}
如果类仅包含静态成员(能以类型调用,而不是以实例调用的成员),你可以创建一个private的构造函数。
private TestClass() {}
尽管存取修饰符在这一章的后面将要大篇幅地讨论,但是private意味着从类的外面不可能访问该构造函数。所
以,它不能被调用,且没有对象可以自该类定义被实例化。
并不仅限于无参数构造函数――你可以传递初始参数来初始化成员。
public TestClass(string strName, int nAge) { ... }
作为一个C/C++程序员,你可能习惯于给初始化写一个附加的方法,因为在构造函数中没有返回值。当然,尽管在
C#中也没有返回值,但你可以引发一个自制的异常,以从构造函数获得返回值。更多有关异常处理的知识在第七章 “异常
处理”中有讨论。
但是,当你保留引用给宝贵的资源,应该想到写一个方法来解决:一个可以被显式地调用来释放这些资源。问题是
当你可以在析构函数(以类名的前面加“~”的方式命名)中做同样的事情时,为何还要写一个附加的方法.
public ~TestClass()
{
// 清除
}
你应该写一个附加方法的原因是垃圾收集器,它在变量超出范围后并不会立即被调用,而仅当间歇期间或内存条件满
足时才被触发。当你锁住资源的时间长于你所计划的时间时,它就会发生。因此,提供一个显式的释放方式是一个好主
意,它同样能从析构函数中调用。
public void Release()
{
// 释放所有宝贵的资源
}
public ~TestClass()
调用析构函数中的释放方法并不是必要的――总之,垃圾收集会留意释放对象。但没有忘记清除是一种良好的习惯。
5.2 方法
既然对象能正确地初始化和结束,所剩下来的就是往类中增加功能。在大多数情况下,功能的主要部分在方法中能得
到实现。你早已见过静态方法的使用,但是,这些是类型(类)的部分,不是实例(对象)。
为了让你迅速入门,我把这些方法的烦琐问题安排为三节:
。方法参数
。改写方法
。方法屏蔽
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: }
因为我传递值而不是引用给一个变量,所以当调用方法时(见第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; // 一定要初始化
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: }
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: }
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: }
15:
16: return dArea;
17: }
18: }
该检测简单地利用了毕达哥拉斯公式,对于直角三角形,检测结果必须为0。如果结果不为0,类就调用它基类的
ComputeArea来实现。
dArea = base.ComputeArea(a,b,c);
例子的要点为:通过显式地利用基类的资格检查,你就能轻而易举地调用基类实现改写方法。
当你需要实现其在基类中的功能,而不愿意在改写方法中重复它时,这就非常有帮助。
5.2.3 方法屏蔽
重定义方法的一个不同手段就是要屏蔽基类的方法。当从别人提供的类派生类时,这个功能特别有价值。看清单
5.6,假设BaseClass由其他人所写,而你从它派生出 DerivedClass 。
清单 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()实现了一个额外的功能。但是,如果基类的开发者认为把
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 implementation, 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: }
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();
基类方法的实现就被调用了。这种行为不同于改写方法,后者保证大部分派生方法获得调用。
(
原文转自:www.ltesting.net
篇2:第五章 类 (rainbow 翻译).net
第五章 类 (2) 5.3 类属性 有两种途径揭示类的命名属性――通过域成员或者通过属性,前者是作为具有公共访问性的成员变量而被实现的;后 者并不直接回应存储位置,只是通过 存取标志(accessors)被访问。 当你想读出或写入属性的值时,存取标志限定了被实现的
第五章 类 (2)
5.3 类属性
有两种途径揭示类的命名属性――通过域成员或者通过属性。前者是作为具有公共访问性的成员变量而被实现的;后
者并不直接回应存储位置,只是通过 存取标志(aclearcase/“ target=”_blank“ >ccessors)被访问。
当你想读出或写入属性的值时,存取标志限定了被实现的语句。用于读出属性的值的存取标志记为关键字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();
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地址列表(以
www.microsoft.com为例 )。这个列表通过索引可以访问,你可以看一下清单5.10 的具体实现。
清单 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(”www.microsoft.com“);
34:
35: int nCount = myDNSResolver.Count;
36: Console.WriteLine(”Found IP‘s for hostname“, nCount);
37: for (int i=0; i < nCount; i++)
38: Console.WriteLine(myDNSResolver[i]);
39: }
40: }
为了解析主机名,我用到了DNS类,它是System .Net 名字空间的一部分。但是,由于这个名字空间并不包含在核心
库中,所以必须在编译命令行中引用该库:
csc /r:System.Net.dll /out:resolver.exe dnsresolve.cs
解析代码是向前解析的。在该 Resolve方法中,代码调用DNS类的静态方法GetHostByName,它返回一个IPHostEntry
对象。结果,该对象包含有我要找的数组――AddressList数组。在退出Resolve 方法之前,在局部的对象实例成员
m_arrIPs中,存储了一个AddressList array的拷贝(类型IPAddress 的对象存储在其中)。
具有现在生成的数组 ,通过使用在类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();
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类声明TextOut事件域成员,
你可以观察到代
表元作为一种新的类型声明,当声明事件时可以使用代表元。
该类仅有一个方法,它允许我们触发事件。请注意,你必须进行事件域成员不为null的检测,因为可能会出现没有客
户对事件感兴趣这种情况。
TestApp类包含了Main 方法,也包含了另外两个方法,它们都具备事件所必需的信号。其中一个方法是静态的,而另
一个是实例方法。
EventSource 被实例化,而静态方法CatchEvent被预关联上了 TextOut事件:
evsrc.TextOut += new EventHandler(CatchEvent);
从现在起,当事件被触发时,该方法被调用。如果你对事件不再感兴趣,简单地取消关联:
evsrc.TextOut -= new EventHandler(CatchEvent);
注意,你不能随意取消关联的处理函数――在类代码中仅创建了这些处理函数。为了证明事件处理函数也和实例方法
一起工作,余下的代码建立了TestApp 的实例,并钩住事件处理方法。
事件在哪方面对你特别有用?你将经常在ASP+中或使用到WFC (Windows Foundation Classes)时,涉及到事件和代表
元。
5.5 应用修饰符
在这一章的学习过程中,你已经见过了象public、virtual等修饰符。欲以一种易于理解的方法概括它们,我把它们划
分为三节:
。类修饰符
。成员修饰符
。存取修饰符
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关键字。
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;
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,它是私有的――甚至派生类也不能访问它。
花些时间为每个类成员甚至每个类计划一种保护层次,通常是个好主意。当需要引入修改时,全面的计划最终会帮
助你,因为没有程序员会愿意使用“没有文档”的类功能。
5.6 小结
这章显示了类的各种要素,它是运行实例(对象)的模板。在一个对象的生命期,首先被执行的代码是个构造函数。
构造函数用来初始化变量,这些变量后来在方法中用于计算结果。
方法允许你传递值、引用给变量,或者只传送一个输出值。方法可以被改写以实现新的功能,或者你可以屏蔽基类成
员,如果它实现了一个具有和派生类成员相同名字的方法。
命名属性可以被当作域成员(成员变量)或属性存取标志实现。后者是get和set存取标志,忽略一个或另外一个,你
可以创建仅写或仅读属性。存取标志非常适合于确认赋给属性的值。
C#类的另外一个功能是索引,它使象数组语法一样访问类中值成为可能。还有,如果当类中的某些事情发生时,你想
客户得到通知,要让它们与事件关联。
当垃圾收集器调用析构函数时,对象的生命就结束了。由于你不能准确地预测这种情况什么时候会发生,所以应该创
建一个方法以释放这些宝贵的资源,当你停止使用它们时。
(
原文转自:www.ltesting.net
篇3:第七章 异常处理(rainbow 翻译).net
第七章 异常处理
通用语言运行时(CLR)具有的一个很大的优势为,异常处理是跨语言被标准化的,
第七章 异常处理(rainbow 翻译).net
,
一个在C#中所引发的异常可以在
Visual Basic客户中得到处理。不再有 HRESULTs 或者 ISupportErrorInfo 接口。
尽管跨语言异常处理的覆盖面很广,但这一章完全集中讨论C#异常处理。你稍为改变编译器的溢
原文转自:www.ltesting.net
篇4:第六章 控制语句(rainbow 翻译).net
第六章 控制语句 有一种语句,你在每种编程语言控制流程语句中都可以找到,在这一章中,我介绍了C#的控制语句,它们分为两个主 要部分: 。选择语句 。循环语句 如果你是C或C++ 程序员 ,很多信息会让你感到似曾相似;但是,你必须知道它们还存在着一些差别
第六章 控制语句
有一种语句,你在每种编程语言控制流程语句中都可以找到。在这一章中,我介绍了C#的控制语句,它们分为两个主
要部分:
。选择语句
。循环语句
如果你是C或C++程序员,很多信息会让你感到似曾相似;但是,你必须知道它们还存在着一些差别。
6.1 选择语句
当运用选择语句时,你定义了一个控制语句,它的值控制了哪个语句被执行。在C#中用到两个选择语句:
。if 语句
。switch 语句
6.1.1 if 语句
最先且最常用到的语句是 if 语句。内含语句是否被执行取决于布尔表达式:
if (布尔表达式) 内含语句
当然,也可以有else 分枝,当布尔表达式的值为假时,该分枝就被执行:
if (布尔表达式) 内含语句 else 内含语句
在执行某些语句之前就检查一个非零长字符串的例子:
if (0 != strTest.Length)
这是一个布尔表达式。(!=表示不等于。) 但是,如果你来自C或者C++,可能会习惯于编写象这样的代码:
if (strTest.Length)
这在C#中不再工作,因为 if 语句仅允许布尔( bool) 数据类型的结果,而字符串的Length属性对象返回一个整
形(integer)。编译器将出现以下错误信息:
error CS0029: Cannot implicitly convert type ‘int‘ to ‘bool‘ (不能隐式地转换类型 ‘int‘ 为 ‘bool‘。)
上边是你必须改变的习惯,而下边将不会再在 if 语句中出现赋值错误:
if (nMyValue = 5) ...
正确的代码应为
if (nMyValue == 5) ...
因为相等比较由==实行,就象在C和C++中一样。看以下有用的对比操作符(但并不是所有的数据类型都有效):
== ――如果两个值相同,返回真。
!= ――如果两个值不同,返回假。
<, <=, >, >= ―― 如果满足了关系(小于、小于或等于、大于、大于或等于),返回真。
每个操作符是通过重载操作符被执行的,而且这种执行对数据类型有规定。如果你比较两个不同的类型,对于编译
器,必须存在着一个隐式的转换,以便自动地创建必要的代码。但是,你可以执行一个显式的类型转换。
清单 6.1 中的代码演示了 if 语句的一些不同的使用场合,同时也演示了如何使用字符串数据类型。这个程序的
主要思想是,确定传递给应用程序的第一个参数是否以大写字母、小写字母或者数字开始。
清单 6.1 确定字符的形态
1: using System;
2:
3: class NestedIfApp
4: {
5: public static int Main(string[] args)
6: {
7: if (args.Length != 1)
8: {
9: Console.WriteLine(”Usage: one argument“);
10: return 1; // error level
11: }
12:
13: char chLetter = args[0][0];
14:
15: if (chLetter >= ‘A‘)
16: if (chLetter <= ‘Z‘)
17: {
18: Console.WriteLine(” is uppercase“,chLetter);
19: return 0;
20: }
21:
22: chLetter = Char.FromString(args[0]);
23: if (chLetter >= ‘a‘ && chLetter <= ‘z‘)
24: Console.WriteLine(” is lowercase“,chLetter);
25:
26: if (Char.IsDigit((chLetter = args[0][0])))
27: Console.WriteLine(” is a digit“,chLetter);
28:
29: return 0;
30: }
31: }
始于第7行的第一个 if 语段检测参数数组是否只有一个字符串。如果不满足条件,程序就在屏幕上显示用法信息,并
终止运行。
可以采取多种方法从一个字符串中提取出单个字符――既可象第13行那样利用字符索引,也可以使用Char类的静态
FromString 方法,它返回字符串的第一个字符。
第16~20行的 if 语句块使用一个嵌套 的if 语句块检查大写字母。用逻辑“与”操作符(&&)可以胜任小写字母的
检测,而最后通过使用Char类的静态函数IsDigit,就可以完成对数字的检测。
除了“&&”操作符之外,还有另一个条件逻辑操作符,它就是代表“或”的“¦¦”。两个逻辑操作
符都 是“短路”式的。对于“&&”操作符,意味着如果条件“与”表达式的第一个结果返回一个假值,余下的条件“与”
表达式就不会再被求值了。相对应,“¦¦”操作符当第一个真条件满足时,它就“短路”了。
我想让大家理解的是,要减少计算时间,你应该把最有可能使求值“短路”的表达式放在前面。同样你应该清楚,计
算 if 语句中的某些值会存在着替在的危险。
if (1 == 1 ¦¦ (5 == (strLength=str.Length)))
当然,这是一个极其夸张的例子,但它说明了这样的观点:第一条语句求值为真,那么第二条语句就不会被执行,它
使变量strLength维持原值。给大家一个忠告:决不要在具有条件逻辑操作符的 if 语句中赋值。
6.1.2 switch 语句
和 if 语句相比,switch语句有一个控制表达式,而且内含语句按它们所关联的控制表达式的常量运行。
switch (控制表达式)
{
case 常量表达式:
内含语句
default:
内含语句
}
控制表达式所允许的数据类型 为: sbyte, byte, short, ushort, uint, long, ulong, char, string, 或者枚举类
型。只要使其它不同数据类型能隐式转换成上述的任何类型,用它作为控制表达式也很不错。
switch 语句接以下顺序执行:
1、控制表达式求值
2、如果 case 标签后的常量表达式符合控制语句所求出的值,内含语句被执行。
3、如果没有常量表达式符合控制语句,在default 标签内的内含语句被执行。
4、如果没有一个符合case 标签,且没有default 标签,控制转向switch 语段的结束端。
在继续更详细地探讨switch语句之前,请看清单 6.2 ,它演示用 switch语句来显示一个月的天数(忽略跨年度)
清单 6.2 使用switch语句显示一个月的天数
1: using System;
2:
3: class FallThrough
4: {
5: public static void Main(string[] args)
6: {
7: if (args.Length != 1) return;
8:
9: int nMonth = Int32.Parse(args[0]);
10: if (nMonth < 1 ¦¦ nMonth >12) return;
11: int nDays = 0;
12:
13: switch (nMonth)
14: {
15: case 2: nDays = 28; break;
16: case 4:
17: case 6:
18: case 9:
19: case 11: nDays = 30; break;
20: default: nDays = 31;
21: }
22: Console.WriteLine(” days in this month“,nDays);
23: }
24: }
switch 语段包含于第13~21行。对于C程序员,这看起来非常相似,因为它不使用break语句。因此,存在着一个更具
生命力的重要差别。你必须加上一个break语句(或一个不同的跳转语句),因为编译器会提醒,不允许直达下一部分。
何谓直达?在C(和C++)中,忽略break并且按以下编写代码是完全合法的:
nVar = 1
switch (nVar)
{
case 1:
DoSomething();
case 2:
DoMore();
}
在这个例子中,在执行了第一个case语句的代码后,将直接执行到其它case标签的代码,直到一个break语句退出
switch语段为止。尽管有时这是一个强大的功能,但它更经常地产生难于发现的缺陷。
可如果你想执行其它case标签的代码,那怎么办? 有一种办法,它显示于清单6.3中。
清单 6.3 在swtich语句中使用 goto 标签 和 goto default
1: using System;
2:
3: class SwitchApp
4: {
5: public static void Main()
6: {
7: Random bjRandom = new Random();
8: double dRndNumber = objRandom.NextDouble();
9: int nRndNumber = (int)(dRndNumber * 10.0);
10:
11: switch (nRndNumber)
12: {
13: case 1:
14: //什么也不做
15: break;
16: case 2:
17: goto case 3;
18: case 3:
19: Console.WriteLine(”Handler for 2 and 3“);
20: break;
21: case 4:
22: goto default;
23: // everything beyond a goto will be warned as
24: // unreachable code
25: default:
26: Console.WriteLine(”Random number “, nRndNumber);
27: }
28: }
29: }
在这个例子中,通过Random类产生用于控制表达式的值(第7~9行)。switch语段包含两个对switch语句有效的跳转
语句。
goto case 标签:跳转到所说明的标签
goto default: 跳转到 default 标签
有了这两个跳转语句,你可以创建同C一样的功能,但是,直达不再是自动的。你必须明确地请求它。
不再使用直达功能的更深的含义为:你可任意排列标签,如把default标签放在其它所有标签的前面。为了说明它,我
创建了一个例子,故意不结束循环:
switch (nSomething)
{
default:
case 5:
goto default;
}
我已经保留了其中一个swich 语句功能的讨论直至结束――事实上你可以使用字符串作为常量表达式,
这对于VB程序
员,可能听起来不象是什么大的新闻,但来自C或C++的程序员将会喜欢这个新功能。
现在,一个 switch 语句可以如以下所示检查字符串常量了。
string strTest = ”Chris“;
switch (strTest)
{
case ”Chris“:
Console.WriteLine(”Hello Chris!“);
break;
}
6.2 循环语句
当你想重复执行某些语句或语段时,依据当前不同的任务,C#提供4个不同的循环语句选择给你使用:
。 for 语句
。foreach 语句
。 while 语句
。do 语句
6.2.1 for 语句
当你预先知道一个内含语句应要执行多少次时,for 语句特别有用。当条件为真时,常规语法允许重复地执行内含语
句(和循环表达式):
for (初始化;条件;循环) 内含语句
请注意,初始化、条件和循环都是可选的。如果忽略了条件,你就可以产生一个死循环,要用到跳转语句(break 或
goto)才能退出。
for (;;)
{
break; // 由于某些原因
}
另外一个重点是,你可以同时加入多条由逗号隔开的语句到for循环的所有三个参数。例如,你可以初始化两个变量、
拥有三个条件语句,并重复4个变量。
作为C或C++程序员,你必须了解仅有的一个变化:条件语句必须为布尔表达式,就象 if 语句一样。
清单6.4 包含使用 for 语句的一个例子。它显示了如何计算一个阶乘,比使用递归函数调用还要快。
清单 6.4 在for 循环里计算一个阶乘
1: using System;
2:
3: class Factorial
4: {
5: public static void Main(string[] args)
6: {
7: long nFactorial = 1;
8: long nComputeTo = Int64.Parse(args[0]);
9:
10: long nCurDig = 1;
11: for (nCurDig=1;nCurDig <= nComputeTo; nCurDig++)
12: nFactorial *= nCurDig;
13:
14: Console.WriteLine(”! is “,nComputeTo, nFactorial);
15: }
16: }
尽管该例子过于拖沓,但它作为如何使用for 语句的一个开端。首先,我本应在初始化内部声明变量nCurDig:
for (long nCurDig=1;nCurDig <= nComputeTo; nCurDig++) nFactorial *= nCurDig;
另一种忽略初始化的选择如下行,因为第10行在for 语句的外部初始化了变量。(记住C#需要初始化变量):
for (;nCurDig <= nComputeTo; nCurDig++) nFactorial *= nCurDig;
另一种改变是把++操作符移到内含语句中:
for ( ;nCurDig <= nComputeTo; ) nFactorial *= nCurDig++;
如果我也想摆脱条件语句,全部要做的是增加一条if 语句,用break 语句中止循环:
for (;;)
{
if (nCurDig >nComputeTo) break;
nFactorial *= nCurDig++;
}
除了用于退出for语句的break语句外,你还可以用continue 跳过当前循环,并继续下一次循环。
for (;nCurDig <= nComputeTo;)
{
if (5 == nCurDig) continue; // 这行跳过了余下的代码
nFactorial *= nCurDig++;
}
6.2.2 foreach 语句
已经在Visual Basic 语言中存在了很久的一个功能是,通过使用For Each 语句收集枚举。C#通过foreach 语句,也
有一个用来收集枚举的命令:
foreach(表达式中的类型标识符) 内含语句
循环变量由类型和标识符声明,且表达式与收集相对应。循环变量代表循环正在为之运行的收集元素。
你应该知道不能赋一个新值给循环变量,也不能把它当作ref 或out 参数。这样引用在内含语句中被执行的代码。
你如何说出某些类支持foreach 语句? 简而言之,类必须支持具有 GetEnumerator()名字的方法,而且由其所返回的
结构、类或者接口必须具有public 方法MoveNext() 和public 属性Current。如果你想知道更多,请阅读语言参考手册,
它有很多关于这个话题的详细内容。
对于清单 6.5 中的例子,我恰好偶然选了一个类,实现了所有这些需要。我用它来列举被定义过的所有的环境变量。
清单 6.5 读所有的环境变量
1: using System;
2: using System.Collections;
3:
4: class EnvironmentDumpApp
5: {
6: public static void Main()
7: {
8: IDictionary envvars = Environment.GetEnvironmentVariables();
9: Console.WriteLine(”There are environment variables declared“, envvars.Keys.Count);
10: foreach (String strKey in envvars.Keys)
11: {
12: Console.WriteLine(” = “,strKey, envvars[strKey].ToString());
13: }
14: }
15: }
对GetEnvironmentVariables的调用返回一个IDictionary类型接口,它是由.NET框架中的许多类实现了的字典接口。
通过 IDictionary 接口,可以访问两个收集:Keys 和 Values。在这个例子里,我在foreach语句中使用Keys,接着查
找基于当前key值的值(第12行)。
当使用foreach时,只要注意一个问题:当确定循环变量的类型时,应该格外小心。选择错误的类型并没有受到编译
器的检测,但它会在运行时受检测,且会引发一个异常。
6.2.3 while 语句
当你想执行一个内含语句0次或更多次时,while语句正是你所盼望的:
while (条件) 内含语句
条件语句――它也是一个布尔表达式 ――控制内含语句被执行的次数。你可以使用 break 和continue语句来控制
while语句中的执行语句,它的运行方式同在for语句中的完全相同。
为了举例while的用法,清单 6.6 说明如何使用一个 StreamReader类输出C#源文件到屏幕。
清单 6.6 显示一个文件的内容
1: using System;
2: using System.IO;
3:
4: class WhileDemoApp
5: {
6: public static void Main()
7: {
8: StreamReader sr = File.OpenText (”whilesample.cs“);
9: String strLine = null;
10:
11: while (null != (strLine = sr.ReadLine()))
12: {
13: Console.WriteLine(strLine);
14: }
15:
16: sr.Close();
17: }
18: }
代码打开文件 whilesample.cs, 接着当ReadLine 方法返回一个不等于null的值时,就在屏幕上显示所读取的值。注
意,我在while条件语句中用到一个赋值。如果有更多的用&&和¦¦连接起来的条件语句,我不能保证它们是
否会被执行,因为存在着“短路”的可能。
6.2.4 do 语句
C#最后可利用的循环语句是do语句。它与while语句十分相似,仅当经过最初的循环之后,条件才被验证。
do
while (条件);
do语句保证内含语句至少被执行过一次,而且只要条件求值等于真,它们继续被执行。通过使用break语句,你可以迫
使运行退出 do 语块。如果你想跳过这一次循环,使用continue语句。
一个如何使用do语句的例子显示在清单 6.7中。它向用户请求一个或多个数字,并且当执行程序退出do循环后计算平
均值。
清单 6.7 在do 循环中计算平均值
1: using System;
2:
3: class ComputeAverageApp
4: {
5: public static void Main()
6: {
7: ComputeAverageApp theApp = new ComputeAverageApp();
8: theApp.Run();
9: }
10:
11: public void Run()
12: {
13: double dValue = 0;
14: double dSum = 0;
15: int nNoOfValues = 0;
16: char chContinue = ‘y‘;
17: string strInput;
18:
19: do
20: {
21: Console.Write(”Enter a value: “);
22: strInput = Console.ReadLine();
23: dValue = Double.Parse(strInput);
24: dSum += dValue;
25: nNoOfValues++;
26: Console.Write(”Read another value?“);
27:
28: strInput = Console.ReadLine();
29: chContinue = Char.FromString(strInput);
30: }
31: while (‘y‘ == chContinue);
32:
33: Console.WriteLine(”The average is \",dSum / nNoOfValues);
34: }
35: }
在这个例子里,我在静态 Main函数中实例化 ComputeAverageApp类型的一个对象。它同样接着调用实例的Run方法,
该方法包含了计算平均值所有必要的功能。
do 循环跨越第19~31行。条件是这样设定的:分别回答各个问题 “y”,以决定是否要增加另一个值。输入任何其它
字符会引起程序退出 do语块,且平均值被计算。
正如你可以从提到的例子看出,do语句和while语句差别不太大――仅有的差别就是条件在什么时候被求值。
6.3 小结
这章解释了如何使用C#中用到的各种选择和循环语句。 if 语句在应用程序中可能是最为常用的语句。当在布尔表达
式中使用计算时,编译器会为你留意。但是,你一定要确保条件语句的短路不会阻止必要代码的运行。
switch 语句――尽管同样与C语言的相应部分相似――但也被改善了。直达不再被支持,而且你可以使用字符串标
签,对于C程序员,这是一种新的用法。
在这一章的最后部分,我说明如何使用for、foreach、while和do语句。语句完成各种需要,包括执行固定次数的循
环、列举收集元素和执行基于某些条件的任意次数的语句。
(
原文转自:www.ltesting.net
初中英语:第一条彩虹隧道 The First Rainbow Tunnel
- 针对 .NET 框架的安全编码指南.net2023-08-26
- 用Delphi制作中国式报表.net2022-12-11
- 凡客诚品.NET笔试题2023-09-24
- 励志类的文章2025-05-31
- 见闻类作文2025-07-07
- 护理类读书笔记2025-09-19
- 城市规划类个人简历2025-05-27
- 生产类口号2025-06-04
- 灯谜:人名类灯谜2025-06-22
- 律师类的英文简历2025-08-25