effective_c#中文版:改善c#程序的50种方法.docx

上传人:文*** 文档编号:68353532 上传时间:2022-12-27 格式:DOCX 页数:73 大小:176.20KB
返回 下载 相关 举报
effective_c#中文版:改善c#程序的50种方法.docx_第1页
第1页 / 共73页
effective_c#中文版:改善c#程序的50种方法.docx_第2页
第2页 / 共73页
点击查看更多>>
资源描述

《effective_c#中文版:改善c#程序的50种方法.docx》由会员分享,可在线阅读,更多相关《effective_c#中文版:改善c#程序的50种方法.docx(73页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、为什么程序经可以正常工作了,我们还要改变它们呢?答案就是我们可以让它们变得更好。我们常常会改变所使 用的工具或者语言,因为新的工具或者语言更富生产。如果固守旧有的习惯,我们将得不到期望的结果。对于C# 这种和我们已经熟悉的语言(如C+或Java)有诸多共通之处的新语言,情况更是如此。人们很容易回到旧的习惯 中去。当然,这些旧的习惯绝大多数都很好,C#语言的设计者们也确实希望我们能够利用这些旧习惯下所获取的知 识。但是,为了让C#和公共语言运行库(Common Language Runtime, CLR)能够更好地集成在起,从而为面向组 件的软件开发提供更好的支持,这些设计者们不可避免地需要添加

2、或者改变某些元素。本章将讨论那些在C#中应该 改变的旧习惯,以及对应的新的推荐做法。条款1:使用属性代替可访问的数据成员C#将属性从其他语言中的种特殊约定提升成为种第一等(first-class)的语言特性。如果大家还在类型中定义公 有的数据成员,或者还在手工添加get和set方法,请赶快停下来。属性在使我们可以将数据成员暴露为公有接口 的同时,还为我们提供了在面向对象环境中所期望的封装。在C#中,属性(property)是这样种语言元素:它们 在被访问的时候看起来好像是数据成员,但是它们却是用方法实现的。有时候,些类型成员最好的表示形式就是数据,例如一个客户的名字、个点的x/y坐标,或者上一

3、年的收入。 使用属性我们可以创建一种特殊的接这种接口在行为上像数据访问,但却仍能获得函数的全部好处。客户代 码国对属性的访问就像访问公有变量一样。但实际的实现采用的却是方法,这些方法内部定义了属性访问器的行为。 .NET框架假定我们会使用属性来表达公有数据成员。事实上,.NET框架中的数据绑定类只支持属性,而不支持公有 数据成员。这些数据绑定类会将对象的属性关联到用户界面控件(Web控件或者Windows Forms控件)上。其数据 绑定机制事实上是使用反射来查找个类型中具有特定名称的属性。例如下面的代码: textBoxCity.DataBindings.Add(Text,address,

4、City);便是将textBoxCity控件的Text属性和address对象的City属性绑定在起。(有关数据绑定的细节,参见条款38。) 如果City是个公有数据成员,这样的数据绑定就不能正常工作。.NET框架类库(Framework Class Library)的设计 者们之所以不支持这样的做法,是因为将数据成员直接暴露给外界不符合面向对象的设计原则。.NET框架类库这样 的设计策略从某种意义上讲也是在推动我们遵循面向对象的设计原则。对于C+和Java编程老手,我想特别指出的 是这些数据绑定代码并不会去查找get和set函数。在C#中,我们应该忘掉get一和set这些旧式的约定,面全面采

5、 用属性。当然,数据绑定所应用的类一般都要和用户界面打交道。但这并不意味着属性只在UI (用户界面)逻辑中有用武之 地。对于其他类和结构,我们也需要使用属性。随着时间的推移,新的需求或行为往往会影响原来类型的实现,采 用属性比较容易能够应对这些变化。例如,我们可能很快就会发现Customer类型不能有一个空的Name。如果我们 使用个公用属性来实现Name,那么只需要在个地方做更改即可:public class Customerprivate string _name;public string Namegetreturn _name;)if (value = null) 11(value.L

6、ength = 0)throw new ArgumentException( Name cannot be blank, Name);_name = value;)/一如果使用的是公有数据成员,我们就要寻找并修改所有设置Customer的Name的代码,那将花费大量的时间。另外,由于属性是采用方法来实现的,因此为它们添加多线程支持就更加容易直接在get和set方法中提供同步 数据访问控制即可:public string Namegetlock( this)(return _name;)setlock( this)name = value;)既然是采用方法来实现的,那么属性也就具有了方法所具有的

7、全部功能。比如,属性可以实现为虚属性:public class Customer(private string _name;public virtual string Name(getreturn _name;)set_name = value;)/Z忽略其他实现代码。自然,属性也可以实现为抽象属性,或者作为接口定义的一部分:public interface INameValuePairobject Nameget;)object Valueget;set;)最后,我们还可以借助属性的特点来创建const和非const版本的接口:public interface IConstNameValue

8、Pairobject Nameget;)object Valueget;)public interface INameValuePairobject Valueget;set;)/上述接口的应用:public class Stuff : IConstNameValuePair, INameValuePair(private string _name;private object _value;#region IConstNameValuePair Memberspublic object Namegetreturn _name;)object IConstNameValuePair.Value

9、getreturn _value;)#endregion#region INameValuePair Memberspublic object Valuegetreturn _value;set_value = value;)#endregion)属性在C#中已经成为项比较完善的、第一等的语言元素。我们可以针对成员函数做的任何事情,对于属性也同样 适用。毕竟,属性是对访问/修改内部数据的方法的种扩展。我们知道,属性访问器在编译后事实上是两个分离的方法。在C# 2.0中,我们可以为个属性的get访问器和set 访问器指定不同的访问修饰符。这使得我们可以更好地控制属性的可见性。/合法的C# 2.0

10、代码:public class Customer(private string _name;public virtual string Namegetreturn _name;)protected set_name = value;)/Z忽略其他实现代码。)C#的属性语法扩展自简单的数据字段。如果类型接口需要包含些索引数据项,则可以使用种称作索引器 (indexer)的类型成员。索引器在C#中又称含参属性(parameterized property) 这种使用属性来返回一个序列 中的数据项的做法对于很多场合非常有用,下面的代码展示了这用法:public int this int index

11、getreturn _theValues index ;set_theValues index = value;)/Z访问索引器:int val = MyObjectf i ;索引器和一般的属性(即支持单个数据项的属性)在C#中有同样的语言支持,它们都用方法实现,我们可以在其内 部做任何校验或者计算工作。索引器也可以为虚索引器,或者抽象索引器。它们可以声明在接口中,也可以成为只 读索引器或者读一写索引器。以数值作为参数的一维索引器”还可以参与数据绑定。使用非数值的索引器则可以用 来定义m叩或者dictionary等数据结构:public Address this string name get

12、return _theValues name ;)set_theValues name = value;)与C#中的多维数组类似,我们也可以创建多维索引器其每维上的参数类型可以相同,也可以不同。public int this int x, int y getreturn ComputeValue( x, y);)public int this int x, string name getreturn ComputeValue( x, name );)注意所有的索引器都使用this关键字来声明。我们不能为索引器指定其他的名称。因此,在每个类型中,对于同样 的参数列表,我们只能有一个索引器。属性显

13、然是一个好东西,相较于以前的各种访问方式来讲,它的确是一个进步。但是,有些读者可能会有如下的想 法:刚开始先使用数据成员,之后如果需要获得属性的好处时,再考虑将数据成员替换为属性。这种做法听起来似 乎有道理,但实际上是错的。让我们来看下面一段代码:/使用公有数据成员,不推荐这种做法:public class Customerpublic string Name;/Z忽略其他实现代码。)这段代码描述了一个Customer类,其内包含一个成员Name。我们可以使用成员访问符来获取/设置其Name的值:string name = customerOne.Name;customerOne.Name =

14、 This Company, Inc.;这段代码非常简洁和直观。有人据此就认为以后如果有需要,再将Customer类的数据成员Name替换为属性就可 以了,而使用Customer类型的代码无需做任何改变。这种说法从某种程度上来讲是对的。属性在被访问的时候和数据成员看起来没有什么差别。这正是C#引入新的属性语法的一个目标。但属性毕竟不是数 据,访问属性和访问数据产生的是不同的MSIL。前面那个Customer类型的Name字段在编译后将产生如下MSIL代 码:.field public string Name而访问该字段的部分编译后的MSIL代码如下:ldloc.0Idfld string Na

15、meSpace.Customer:Namestloc.l向该字段存储数据的部分编译后的MSIL代码如下:ldloc.0Idstr This Company, lnc.Hstfld string NameSpace.Customer:Name大家不必担忧,我们不会整天围绕着il代码转。为了让大家清楚1在这里展示一下IL代码还是很重要的。我们再来看下面的Customer类型实现,这次我们采用了属性的方案: public class Customer private string _name;public string Name get (return _name; set_name = value

16、;)忽略其他实现代码。)当我们在C#中访问Name属性时,使用的语法和前面访问字段的语法一 模样。string name = customerOne.Name;customerOne.Name = This Company, Inc.;但是,C#编译器对于两段相同的C#代码产生的却是完全不同的MSIL代码。我们来看新版Customer类型的Name属 性编译后的MSIL:.property instance string Name().get instance string NameSpace.Customer:get_Name().set instance void NameSpace.Cu

17、stomer:set_Name(string) /Z 属性 Customer:Name 结束。.method public hidebysig specialname instance string get_Name() cil managed/Z代码长度H (Oxb).maxstack 1.locals init(O string CS$OOOOOOO3$OOOOOOOO)IL_0000: ldarg.0IL_0001: Idfldstring NameSpace.Customer:_nameIL_0006: stloc.OIL_0007: br.sIL_0009IL_0009: ldloc

18、.0IL_OOOa: ret/ 方法 Customer:get_Name 结束。.method public hidebysig specialname instance voidset_Name(string value) cil managed/Z代码长度8 (0x8).maxstack 2IL_0000: ldarg.0IL_0001: Idarg.lIL_0002: stfld string NameSpace.Customer:_nameIL_0007: ret/ 方法 Customer:set_Name 结束。在将属性定义从C#代码转换为MSIL的过程中,有两点需要我们注意:首先,

19、.property指示符定义了属性的类型, 以及实现属性get访问器和set访问器的两个函数。这两个函数被标记为hidebysig和specialname。对我们来说,这 两个标记意味着它们所修饰的函数不能直接在C#源代码中被调用,也不被认为是类型正式定义的一部分。要访问它 们,我们只能通过属性。当然,大家对于属性定义产生不同的MSIL应该早有预期。更重要的是,对属性所做的get和set访问的客户代码编 译出来的MSIL也不同:/getIdloc.Ocallvirt instance string NameSpace.Customer:get_Name()stloc.l/ setIdloc.O

20、Idstr This Company, Inc.callvirt instance void NameSpace.Customer:set_Name(string)大家看到了,同样是访问客户(Customer)名称(Name)的C#源代码,由于所使用的Name成员不同属性或 者数据成员,编译后产生出的MSIL指令也不同。尽管访问属性和访问数据成员使用的是同样的C#源代码,但是C# 编译器却将它们转换为不同的代码。换句话说,虽然属性和数据成员在源代码层次上是兼容的,但是在二进制层次上却不兼容。这意味着如果将一个类 型的公有数据成员改为公有属性,那么我们必须重新编译所有使用该公有数据成员的C#代码

21、。本书第4章创建二 进制组件”讨论了二进制组件的相关细节,但是在此之前大家要清楚,将一个数据成员改为属性会破坏二进制兼容 性。如果这样的程序集已经被部署,那么升级它们的工作将变得非常麻烦。看了属性产生的代码之后,有读者可能想知道使用属性和使用数据成员在性能上有什么差别。虽然使用属性不会 比使用数据成员的代码效率更快,但是它也不见得就会比使用数据成员的代码慢,因为JIT编译器会对某些方法调 用(包括属性访问器)进行内联处理。如果JIT编译器对属性访问器进行了内联处理,那么属性和数据成员的效率 将没有任何差别。即使属性访问器没有被内联,实际的效率差别相对于函数调用的成本来讲也是可以忽略不计的。 只

22、有在很少的些情况下,这种差别值得我们注意。综上所述,只要打算将数据暴露在类型的公有接口或者受保护接口中,我们都应该使用属性来实现。对于具有序列 或者字典特征的类型,则应该采用索引器。所有的数据成员都应一律声明为私有。使用属性的好处显而易见:我们 可以得到更好的数据绑定支持,我们可以更容易地在将来对其访问方法的实现做任何改变。将变量封装在属性中只 不过增加一两分钟代码录入时间。如果刚开始使用数据成员,后来又发现需要使用属性,这时再来修改的成本将是 几个小时。今天的一点投入,会为明天节省许多时间。条款2:运行时常量(readonly)优于编译时常量(const)C#语言有两种不同的常量机制:种为编

23、译时(compile-time)常量,种为运行时(runtime)常量。两种常量 有着非常迥异的行为,使用不正确会导致程序的性能下降或者出现错误。这两种代价,哪个都没有人愿意承担, 但是如果必须承担个,那么“慢、但是能够正确运行的”程序总比“快、但是可能出错的”程序要好。因此,我 们说运行时常量优于编译时常量。编译时常量比运行时常量稍微快一点,但却缺乏灵活性。只有在性能非常关键, 并且其值永远不会改变的情况下,我们应该使用编译时常量。在C#中,我们使用readonly关键字来声明运行时常量,用const关键字来声明编译时常量。/Z编译时常量:public const int -Millenni

24、um = 2000;/Z运行时常量:public static readonly int _ThisYear = 2004;编译时常量与运行时常量行为的不同处在于它们的访问方式。编译时常量在编译后的结果代码中会被替换为该常量 的值,例如下面的代码:if ( myDateTime. Year = Millennium )其编译后的IL和下面的代码编译后的IL 一样:if ( myDateTime. Year = 2000 )条款2:运行时常量(readonly)优于编译时常量(const) 16运行时常量的值则在运行时被计算。对于使用运行时常量的代码,其编译后的1L将维持对readonly变量(

25、而非它 的值)的引用。这种差别会为我们使用两种常量类型带来些限制。编译时常量只可以用于基元类型(包括内建的整数类型和浮点 类型)、枚举类型或字符串类型。因为只有这些类型允许我们在初始化器中指定有意义的常量值図。在使用这 些常量的代码编译后得到的IL代码中,常量将直接被替换为它们的字面值(literal)。例如,下面的代码就不会 通过编译。事实上,C#不允许我们使用new操作符来初始化一个编译时常量,即使被初始化的常量类型为个值类 型。/Z下面的代码不会通过编译,但是换成readonly就可以:private const DateTime _classCreation = newDateTime

26、( 2000, 1, 1, 0, 0, 0 );编译时常量仅限于数值和字符串。只读(read- only)字段之所以也被称作一种常量,是因为它们的构造器一旦被 执行,我们将不能对它们的值做任何修改。与编译时常量不同的地方在于,只读字段的赋值操作发生在运行时, 因此它们具有更多的灵活性。比如,只读字段的类型就没有任何限制。对于只读字段,我们只能在构造器或者初始 化器中为它们赋值。在上面的代码中,我们以声明readonly的DateTime结构变量,但是却不能声明const的 DateTime结构变量。我们可以声明readonly的实例常量,从而为个类型的每个实例存储不同的值。但是const修饰的

27、编译时常量默 认就被定义为静态常量。我们知道,运行时常量和编译时常量最堂要的区别就在于运行时常量值的辨析发生在运行时,而编译时常量值的 辨析发生编译时。换言之,使用运行时常量编译后的IL代码引用的是readonly变量,而非它的值;而使用编译 时常量编译后的1L代码将直接引用它的值就像我们直接在代码中使用常量值一样。即使我们使用的是数值常 量并跨程序集引用,情况也是样:如果在程序集A中引用程序集B中的常量,那么编译后程序集A中出现的那 个常量将被它的值所替换。这种差别对于代码的维护性而言有着相当的影响。编译时常量与运行时常量被辨析的方式影响着运行时的兼容性。假设我们在个名为Infrastruc

28、ture的程序集中 分别定义了一个const字段和一个readonly字段:public class UsefulValues (public static readonly int StartValue = 5;public const int EndValue = 10;)在另外一个程序集Application中,我们又引用着这些值:for ( int i = UsefulValues. StartValue;i UsefulValues. EndValue;i+ )Console. WriteLine( value is 0, i );如果我们运行上面的代码,将得到以下输出:Value

29、is 5Value is 6Value is 9假设随着时间的推移,我们又发布了一个新版的Infrastructure程序集:public class UsefulValuespublic static readonly int StartValue = 105;public const int EndValue = 120;我们将新版的Infrastructure程序集分发出去,但并没有重新编译Application程序集。我们期望得到如下的输 出:Value is 105Value is 106Value is 119但实际上,我们却没有得到任何输出。因为现在那个循环语句将使用105作为它

30、的起始值,使用10作为它的结束条 件。其根本原因在于C#编译器在第一次编译Application程序集时,将其中的EndValue替换成了它对应的常量值 10o而对于StartValue来说,由于它被声明为readonly,所以它的辨析发生在运行时。因此,Application程序 集在没有被重新编译的情况下,仍然可以使用新的StartValue值。为了改变所有使用readonly常量的客户代码 的行为,简单地安装一个新版的Infrastructure程序集就足够了。“更改个运行时常量的值”应该被视作对类 型接口的更改,其后果是我们必须重新编译所有引用该常量的代码。“更改个公有的运行时常量的

31、值”应该被 视作对类型实现的更改,它与其客户代码在二进制层次上是兼容的。大家看看上述代码中的循环编译后的MSIL,就 会对这里所谈的更加清楚了:IL0000:Idsfldint32 Chapterl. UsefulValues:StartValueIL._0005:stloc.0IL_0006:br. sIL_001cIL._0008:Idstrvalue is 0IL-_000d:Idloc. 0IL,_000e:boxmscorlibSystem. Int32IL_0013:callvoid mscorlibSystem. Console:WriteLine(string, object)

32、IL_0018: ldloc.0IL_0019: Ide. i4. 1IL_001a: addIL.OOlb: stloc.OIL_001c: ldloc.0IL_001d: Ide. i4. s 10IL_001f: bit.s IL_0008大家可以在这段MSIL代码的顶端看到StartValue的确是被动态加载的,而在其末尾可以看到结束条件被硬编码 (hard-code)为 10。不过,有时候有些值确实可以在编译时确定,这时候 就应该使用编译时常量。例如,考虑在对象的序列化形式(有 关对象序列化,可参见条款25)中使用组常量来区分不同版本的对象。其中,标记特殊版本号的持久化数据应 该采用

33、编译时常量,因为它们的值永远不会改变。但是标记当前版本号的数据应该采用运行时常量,因为它的值会 随着每个不同的版本而改动。private const int VERSION = 0x0100;private const int VERSION_1_1 = 0x0101;private const int VERSI0N12 = 0x0102;/主发行版本:private const int VERSI0N_2_0 = 0x0200;/Z标记当前版本:private static readonly int CURRENTVERSION =VERSI0N_2_0;我们使用运行时版本回来将当前的版本

34、号存储在每个序列化文件中:/Z从持久层数据源读取对象,将存储的版本号与编译时常量相比对:protected MyType( Serializationinfo info, StrearningContext cntxt )(int storedVersion = info. Getlnt32( VERSION);switch ( storedVersion ) (case VERSION_2 0:readVersion2( info, cntxt );break;case VERSION l l:readVersionlDotl( info, cntxt );break;忽略其他细节。) )

35、/Z写入当前版本号: SecurityPermissionAttribute( SecurityAction. Demand,SerializationFormatter =true )void ISerializable. GetObjectData( Serializationinfo inf, StreamingContext ext )(/使用运行时常量来标记当前版本号:inf. AddValue( VERSION”, CURRENT_VERSION );/Z写入其他元素)条款3:操作符is或as优于强制转型19使用const较之于使用readonly的唯一好处就是性能:使用已知常量值

36、的代码效率要比访问readonly值的代码效 率稍好一点。但是这其中的效率提升是非常小的,大家应该和其所失去的灵活性进行番权衡比较。在打算放弃 灵活性之前,一定要对两者的性能差别做个评测。综上所述,只有当某些情况要求变量的值必须在编译时可用,应该考虑使用const,例如:特性(attribute)类 的参数,枚举定义,以及某些不随组件版本变化而改变的值。否则,对于其他任何情况,都应该优先选择readonly 常量,从而获得其所具有的灵活性。条款3:操作符is或as优于强制转型C#是一门强类型语言。一般情况下,我们最好避免将一个类型强制转换为其他类型。但是,有时候运行时类型检 查是无法避免的。相

37、信大家都写过很多以System. Object类型为参数的函数,因为.NET框架预先为我们定义了这 些函数的签名。在这些函数内部,我们经常要把那些参数向下转型为其他类型,或者是类,或者是接口。对于这种 转型,我们通常有两种选择:使用as操作符,或者使用传统C风格的强制转型。另外还有一种比较保险的做法: 先使用is来做个转换测试,然后再使用as操作符或者强制转型。正确的选择应该是尽可能地使用as操作符,因为它比强制转型要安全,而且在运行时层面也有比较好的效率。需 要注意的是,as和is操作符都不执行任何用户自定义的转换。只有当运行时类型与目标转换类型匹配时,它们 会转换成功。它们永远不会在转换过

38、程中构造新的对象。我们来看一个例子。假如需要将一个任意的对象转换为个MyType的实例。我们可能会像下面这样来做: object = Factory. GetObject();/Z第一个版本:MyType t = as MyType;if ( t != null )/Z处理t, t现在的类型为MyType else/Z报告转型失败。)或者,也可以像下面这样来做:object = Factory. GetObject();/第二个版本:try (MyType t;t = ( MyType ) o;if ( t != null )/Z处理t, t现在的类型为MyType。 else/Z报告空引用失

39、败。) catch(/Z报告转型失败。)相信大家都同意第一个版本的转型代码更简单,也更容易阅读。其中没有添加额外的try/catch语句,因此也就 避免了其带来的负担。注意,第二个版本中除了要捕捉异常外,还要对null的情况进行检査,因为如果本来就 是null,那么强制转型可以将它转换成任何引用类型。但如果是as操作符,且被转换对象为null,那么执行结果 将返回null。因此,如果使用强制转型,我们既要检查其是否为null,还要捕捉异常。如果使用as操作符,我 们只需要检查返回的引用是否为null就可以了。cast和as操作符之间最大的区别就在于如何处理用户自定义的转换。操作符as和is都只

40、检查被转换对象的运行 时类型,并不执行其他的操作。如果被转换对象的运行时类型既不是所转换的目标类型,也不是其派生类型,那 么转型将告失败。但是,强制转型则会使用转换操作符来执行转型操作,这包括任何内建的数值转换。例如,将 个long类型强制转换为个short类型将会导致部分信息丢失。条款3:操作符is或as优于强制转型21在我们使用用户自定义的转换时,也会有同样的问题,来看下面的代码:public class SecondType(private MyType value;忽略其他细节。/Z转换操作符。/ 将 SecondType 转换为 MyType,参见条款 29。4public stat

41、ic implicit operatorMyType( SecondType t )return t. _value;)假设面第一行代码中的Factory. GetOb ject ()返回的是个SecondType对象:object = Factory. GetObject();/Z 为个 SecondType:MyType t = o as MyType; /Z 转型失败, 的类型不是 MyType。if ( t != null ) (/Z处理t, t现在的类型为MyTypeo else (/Z报告转型失败。/第二个版本:try (MyType tl;tl = ( MyType ) o; /

42、Z转型失败,。的类型不是MyType。if ( tl != null ) (/Z处理tl, tl现在的类型为MyType。 else(/Z报告空引用失败。) catch(/Z报告转型失败。)两个版本的转型操作都失败了。大家应该还记得我前面说过强制转型会执行用户自定义的转换,有读者据此认为 强制转型的那个版本会成功。这么想本身没有错误,只是编译器在产生代码时依据的是对象的编译时类型。编 译器对于的运行时类型无所知编译器只知道的类型是System. Object因此编译器只会检查是否存在将 System. Object转换为MyType的用户自定义转换。它会到System. Object类型和My

43、Type类型的定义中去做这样的 检查。由于没有找到任何用户自定义转换,编译器将产生代码来检查。的运行时类型,并将其和MyType进行比对。 由于的运行时类型为SecondType,因此转型将告失败。编译器不会检 査在的运行时类型SecondType和MyType 之间是否存在用户自定义的转换。当然,如果将上述代码做如下修改,转换就会成功执行:object = Factory. GetObject();/第三个版本:SecondType st = as SecondType;try (MyType t;t = ( MyType ) st;if ( t != null )I/Z处理t, t现在的类

44、型为MyType。 else(/Z报告空引用失败。 catch (条款3:操作符is或as优于强制转型24/Z报告转型失败。在正式的开发中,我们绝不能写如此丑陋的代码,但它却向我们揭示了问题的所在。虽然大家永远都不可能像上面 那样写代码,但可以使用个以System. Object类型为参数的函数,让该函数在内部执行正确的转换。object = Factory. GetObject();DoStuffWithObject( );private void DoStuffWithObject( object o2 )(try (MyType t;t = ( MyType ) o2; /Z转型失败,的

45、类型不是MyTypeif ( t != null )(/Z处理t, t现在的类型为MyType0 else/Z报告空引用失败。) catch(/Z报告转型失败。)记住,用户自定义的转换操作符只作用于对象的编译时类型,而非运行时类型上。至于02的运行时类型和MyType 之间是否存在转换,并不重要。事实上,编译器对此并不了解,也不关心。对于下面的语句,如果st的声明类型 不同,会有不同的行为:t = ( MyType ) st;但对于下面的语句,不管st的声明类型是什么,都会产生同样的结果应。因此,我们说as操作符要优于强制转 型它的转型结果相对比较一致。但如果as操作符两边的类型没有继承关系,即使存在用户自定义转换操作符,也会产生编译时错误。例如,下面 的语句:t = st as MyType;我们已经知道在转型的时候应该尽可能地使用as操作符。下面我们来谈谈些不能使用as操作符的情况。首先,as操作符不能应用于值类型。例如,下面的代码编译的时候就会报错:object = Factory. GetValue();int i = as int; /不能通过编译。这是因为int是一个值类型,所以

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 教案示例

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁