C#高效编程改进C#代码的50个行之有效的办法
C#高效编程改进C#代码的50个行之有效的办法 C#高效编程改进C#代码的50个行之有效的办法第1章C#语言习惯为什么程序已经可以正常工作了,还要继续修改呢?答案就是我们还能让程序变得更好。如果你总是墨守成规,那么将永远体会不到新技术带来的优势。对于C祥这种和我们已经熟悉的语言(如C++或Jav等)有很多类似之处的新语言,情况更是如此。C#也是一种用大括号组织代码块的语言,因此人们很容易重拾他们熟悉的习惯。但这会阻碍你学到C#的种种精妙之处。自2001年第一个正式版本以来,C#一直在不断改进。其第一个版本与C++和Java尚有许多共同之处,但如今已渐行渐远。若你是从其他语言转到C#。那么应掌握必要的C语言习惯,让其更好地配合你的工作。本章将讨论那些在CH中应该改变的旧习惯,以及与其对应的推荐的新做法。条目1使用属性而不是可访问的数据成员属性一直是C#语言中的一等公民。自1.0版本以来,CH对属性进行了一系列的增强,让其表达能力不断提高。你甚至可以为 setter和getr定不同的访问权限。隐式属性也极大降低了声明属性时的工作量,不会比声明数据成员麻烦多少。若你仍然在类型中声明公有成员,或是仍在手工编写set或get之类的方法,那么快停下来吧。属性允许将数据成员作为公共接口的一部分暴露出去,同时仍旧提供面向对象环境下所需要的封装。属性这个语言元素可以让你像访问数据成员一样使用,但其底层依旧使用方法实现。类型的某些成员确实非常适合作为数据,例如某个客户的名称,某个点的x、y坐标或上2第1章CH语言习惯一年度的收入等。而属性则让你可以创建出类似于数据访问,但实际上却是方法调用的接口,自然也可以享受到方法调用的所有好处。客户代码访问属性时,就像是在访问公有的字段。不过其底层使用方法实现,其中可以自由定义属性访问器的行为。NET Framework假设你会对公有数据成员使用属性。实际上, NET Framework中的数据绑定类仅支持属性,而不支持公有数据成员。对于所有的数据绑定类库均是如此,包括WPF、Windows forms和 Silverlight。数据绑定会将某个对象的一个属性和某个用户界面控件相互关联起来。数据绑定机制将使用反射来找到类型中的特定属性:text30xC丑ty. DataBindings,Add〔"Text",address,"City )这段代吗将 textBox=Y控件的ext属性绑定到了 address对象的ciy属性上。公有的数据成员并不推荐使用,因此 Framework Class Library设计器也不支持其实现绑定。这样的设计也保证了你必须选择合适的面向对象技术。确实,数据绑定只是用在用户界面逻辑中会使用到的类中。但这并不意味着属性仅应该用在UI逻辑中,其他类和结构中也应使用属性。在日后产生新的需求或行为时,属性更易于修改。例如,你会很快有这样的想法,客户对象不应该有空白的名称。若你使用了公有属性来封装Name,那么只要修改一处即可。public class Customerprivate string name ipublic string Nameget i return name: Hsetif (string. IsNullOrEmpty(value))throw new ArgumentException(Name cannot be blankName):name= value// More Elided条目1使用属性而不是可访问的数据成员3若是使用了公有的数据成员,那么就需要查找每一处设置客户名称的代码并逐一修复。1这将花费大量的时间因为属性是使用方法来实现的,所以添加多线程支持也非常简单。很容易即可在属性的get和set访问器中作出如下的修改,从而支持对数据的同步访问:public class Customerprivate object syncHandle= new object ()private string name ipublic string Namegetock (syncHandle)return name jsetif (string, IsNullOrEmpty (value))throw new ArgumentExcepti。n〔" Name cannot be blankName")lock ( syncHandle)namevalue/ More Elided属性可以拥有方法的所有语言特性。例如,属性可以为虚的( virtual)public class Customerpublic virtual string Namegetiseti注意,上述例子中使用了C#30中的隐式属性语法。使用属性来封装私有字段是一个常用的模式。通常而言,我们并不需要验证属性的 getter或 setter逻辑。因为语言本身提供了简4第1章C#语言习惯化的隐式属性语法,力求尽量降低开发人员的输入工作,即将一个简单的字段暴露成属性编译器将为你创建一个私有的成员字段,并自动生成最简单的ge和set访问器的逻辑。你还可以将属性声明为抽象的( abstract),以类似隐式属性语法的形式将其定义在接口中。下面的例子就将属性定义在了一个泛型接口中。需要注意的是,虽然其语法和隐式属性完全相同,但是编译器却不会白动地生成任何实现。接口只是定义了一个契约,强制所有实现了该接口的类型都必须满足。public interface INareValuePairstring NancgetT Valuegetiseti属性是种全功能的、第等的语言元素,能够以方法调用的形式访问或修改内部数据。成员函数中可以实现的功能均可在属性中实现属性的访问器将作为两个独立的方法编译到你的类型中。在C#中,你可以为ge和set访问器制定不同的访问权限这样即可更精妙地控制作为属性暴露出来的数据成员的可见性:public class Customerpublic virtual string Namegetiprotected set/ remaining implementation omitted上述属性语法的表达含义远远超出了简单数据字段的范畴。若类型需要包含并暴露出可索引的项目,那么可以使用索引器(即支持参数的属性)。若想返回序列中的项,创建一个属性会是个不错的做法:条目1使用属性而不是可访问的数据成员5public int thislint index]get i return thevalues[index ] Jseti thevalues[index]= value:]// Accessing an indexerint val= someobject [i]索引器和单一条目属性有着同样的语言支持:它们都是作为方法实现的,因此可以在索引器内部实现任意的验证或计算逻辑。索引器也可为虚的或抽象的,可声明在接口中,可以为只读或读写。一维且使用数字作为参数的索引器也可参与数据绑定。使用非整数参数的索引器可用来定义图和字典public Address this[string name]get t return adressvalues [name]: Hset f adressvalues [name]= value: IC#中支持多维数组,类似地,我们也可以创建多维索引器,每一个维度上可以使用同样或不同的类型:public int this[int x, int y]get i return Computevalue(x, y): hpublic int thislint x, string name]get i return Computevalue(x, name); I需要注意的是,所有的索引器都使用this关键字声明。C#不支持为索引器命名。因此类型中每个不同的索引器都必须有不同的参数列表,以免混淆。几乎属性上的所有特性都能应用到索引器上。索引器也可为虚的或抽象的,可以对 setter和gete给出不同的访问限制,不过却不能像属性那样创建隐式索引器。属性的功能很强大,是个不错的改进。但你是不是还在想能不能先用数据成员来实现,而在稍后需要其他各种功能的时候再改成属性呢?这看似是个不错的策略,不过实际上却行不通。考虑如下这个类的定义6第1章C井语言习惯// using public data members, bad practicepublic class Customerpublic string Name// remaining implementation omitted这个类描述一个客户( Customer),包含了一个名称(Name)。你可以使用熟悉的成员表示方式获取或设置该名称string name customerone Name icustomerOne. Name ="This Company, Inc.看似简单直观,你也会认为若是日后将Name改成属性,那么代码也可以无需修改保持正常。但这个答案并不是完全正确的。属性仅仅是访问时类似于数据成员,这是语法所实现的目的。不过属性并不是数据,属性的访问和数据的访问将会生成不同的MSIL( MicrosoftIntermediate Language,微软中间语言)指令。虽然属性和数据成员在源代码层次上是兼容的,不过在二进制层面上却大相径庭。这也就意味着,若将某个公有的数据成员改成了与之等同的共有属性,那么就必须重新编译所有用到该公有数据成员的代码。C#把二进制程序集作为一等公民看待。该语言本身的一个日标就是支持发布某个单一程序集时,不需要更新整个的应用程序。而这个将数据成员改为属性的简单操作却破坏掉了二进制兼容型,也就会让更新单一程序集变得非常困难若是查看属性生成的,那么你或许会想比较一下属性和数据成员的性能。属性当然不会比数据成员访问快,不过也不会比其慢多少。J编译器将内联一些方法调用,包括属性访问器。当∏T编译器内联了属性访问器时,数据成员和属性的访间效率即可持平。即使某个属性访问器没有被内联,其性能差距也实在是微乎其微,仅仅一次函数调用之别而已。只有在某些极端情况下,二者的差距才会有所影响。在调用方来看,属性虽然是方法,但它和数据却有着类似的概念。这会使你的调用者对属性有着一些潜意识的认识。例如,调用者会把属性访问当成是数据的访问。不管怎样,二者看上去很像。属性访问器应该满足这些潜意识的预期。ge访问器不应该有可被观察到的副作用。set访问器会修改状态,用户应该可以看到调用后带来的改变调用者也会对属性访问器的性能有着一定的预期。属性的访问就像是访问一个数据字条目2用运行时常量( readonly)而不是编译期常量( const)7段,因此不会与访问数据有太过明显的性能差别。属性访问器不应该执行长时间的计算,或1进行跨应用程序的调用(例如执行数据库查询等),或是其他任何与调用者期待不符的耗时操作。无论何时需要在类型的公有或保护接口中暴露数据,都应该使用属性。你也应该使用索引器来暴露序列或字典。所有的数据成员都应该是私有的,没有任何例外。这样你就立即得到了数据绑定的支持,也便于日后对方法实现的各种修改。对于将任何变量封装到一个属性所需的额外输入工作其实不会占用太多时间,而日后若是需要使用属性来更正设计,则会花去大量的时间。现在多投入一点点,换来的是今后维护时的更加游刃有余。条目2用运行时常量( readonly)而不是编译期常量( constC#有两种类型的常量:编译期常量和运行时常量。二者有着截然不同的行为,使用不当将会带来性能上或正确性上的问题。这两个问题最好都不要发生,不过若难以同时避免的话,那么一个略微慢一些但能保证正确的程序则要好过一个快速但不能正常工作的程序。考虑到这些,你应该尽量使用运行时常量,而不是编译期常量。虽然编译期常量略微快一些,但是却没有运行时常量那么灵活。应仅仅在那些性能异常敏感,且常量的值在各个版本之间绝对不会变化时,再使用编译期常量运行时常量使用 readonly关键字声明,编译期常量则使用 const关键字声明:/ Compile time constant:public const int Millennium= 2000:′ Runtime constant:public static readonly int ThisYear =2004上述代码在类或 struct的范围内演示了两种常量。编译期常量也可声明在方法中,而只读的运行时常量却不能声明在方法中。编译期常量与运行时常量行为的不司之处在于对它们的访问方式不同。编译期常量的值是在目标代码中进行替换的。以下构造:8第1章C#语言习惯if (my DateTime. Year = Millennium)将与如下代码编译成同样的I:if (myDateTime. Year = 2000)运行时常量将在运行时求值。引用运行时常量生成的将引用到 readon1y的变量,而不是变量的值。这个差別会带来几个限制,会影响到选用哪种类型的常量。编译期常量仅能用于基本类型(内建的整数和浮点类型)、枚举或字符串。只有这些类型才允许我们在初始化器中指定有意义的常量值。在代码编译后得到的Ⅱ代码中,只有这些常量可直接被替换为它们的字面值。例如,下面的代码就不会通过编译。即使要初始化的常量类型属于值类型,也无法在C#中使用new操作符来初始化编译期常量:Does not compile, use readonly instead:private const DateTime classcreation newDateTime(2000,1,1,0,0,0编译期常量( const)仅能用于数字和字符串。运行时常量( readonly)也是一种常量,因为在构造函数执行后不能被再次修改。二者的区别在于,只读的值将在运行时给出,这自然会带来更好的灵活性。例如,运行时常量可以为任意类型。运行时常量必须在构造函数或初始化器中初始化。你可以让某个 readon1y值为一个 Datatime结构,而不能指定某个 const为 DataTime。你可以用 readonly值保存实例常量,为类的每个实例存放不同的值。而编译期常量就是静态的常量。二者最重要的区别在于, readonly值是在运行时解析的。引用一个 readonly常量时生成的引用的是 readon1y变量,而不是其值。这一点将会对日后的维护产生深远的影响编译期常量将生成同样的讧,就像宜接在代码中给出数字一样,即使是跨程序集也是如此,即使另一个程序集中引用了某个程序集中的某个常量,相应常量也会被直接替换成这个值。编译期常量与运行时常量的求值方式将会影响运行时的兼容性。假设我们在一个名为Infrastructure的程序集中分别定义了一个 const字段和一个 readonly字段:public class Usefulvalues
用户评论