C#高效编程50改进代码的地方

所需积分/C币:9 2017-10-07 16:10:26 46.64MB PDF

C# 改进代码 C# 改进代码 C# 改进代码 C# 改进代码 C# 改进代码 C# 改进代码
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 Customer private string name i public string Name get i return name: H set if (string. IsNullOrEmpty(value)) throw new ArgumentException( Name cannot be blank Name): name= value // More Elided 条目1使用属性而不是可访问的数据成员3 若是使用了公有的数据成员,那么就需要查找每一处设置客户名称的代码并逐一修复。1 这将花费大量的时间 因为属性是使用方法来实现的,所以添加多线程支持也非常简单。很容易即可在属性的 get和set访问器中作出如下的修改,从而支持对数据的同步访问: public class Customer private object syncHandle= new object () private string name i public string Name get ock (syncHandle) return name j set if (string, IsNullOrEmpty (value)) throw new ArgumentExcepti。n〔 " Name cannot be blank Name") lock ( syncHandle) name value / More Elided 属性可以拥有方法的所有语言特性。例如,属性可以为虚的( virtual) public class Customer public virtual string Name geti seti 注意,上述例子中使用了C#30中的隐式属性语法。使用属性来封装私有字段是一个常 用的模式。通常而言,我们并不需要验证属性的 getter或 setter逻辑。因为语言本身提供了简 4第1章C#语言习惯 化的隐式属性语法,力求尽量降低开发人员的输入工作,即将一个简单的字段暴露成属性 编译器将为你创建一个私有的成员字段,并自动生成最简单的ge和set访问器的逻辑。 你还可以将属性声明为抽象的( abstract),以类似隐式属性语法的形式将其定义在接口 中。下面的例子就将属性定义在了一个泛型接口中。需要注意的是,虽然其语法和隐式属性 完全相同,但是编译器却不会白动地生成任何实现。接口只是定义了一个契约,强制所有实 现了该接口的类型都必须满足。 public interface INareValuePair<T> string Nanc get T Value geti seti 属性是种全功能的、第等的语言元素,能够以方法调用的形式访问或修改内部数据。 成员函数中可以实现的功能均可在属性中实现 属性的访问器将作为两个独立的方法编译到你的类型中。在C#中,你可以为ge和set 访问器制定不同的访问权限这样即可更精妙地控制作为属性暴露出来的数据成员的可见性: public class Customer public virtual string Name geti protected set / remaining implementation omitted 上述属性语法的表达含义远远超出了简单数据字段的范畴。若类型需要包含并暴露出可 索引的项目,那么可以使用索引器(即支持参数的属性)。若想返回序列中的项,创建一个 属性会是个不错的做法: 条目1使用属性而不是可访问的数据成员5 public int thislint index] get i return thevalues[index ] J seti thevalues[index]= value:] // Accessing an indexer int val= someobject [i] 索引器和单一条目属性有着同样的语言支持:它们都是作为方法实现的,因此可以在索 引器内部实现任意的验证或计算逻辑。索引器也可为虚的或抽象的,可声明在接口中,可以 为只读或读写。一维且使用数字作为参数的索引器也可参与数据绑定。使用非整数参数的索 引器可用来定义图和字典 public Address this[string name] get t return adressvalues [name]: H set f adressvalues [name]= value: I C#中支持多维数组,类似地,我们也可以创建多维索引器,每一个维度上可以使用同样 或不同的类型: public int this[int x, int y] get i return Computevalue(x, y): h public int thislint x, string name] get i return Computevalue(x, name); I 需要注意的是,所有的索引器都使用this关键字声明。C#不支持为索引器命名。因此 类型中每个不同的索引器都必须有不同的参数列表,以免混淆。几乎属性上的所有特性都能 应用到索引器上。索引器也可为虚的或抽象的,可以对 setter和gete给出不同的访问限制, 不过却不能像属性那样创建隐式索引器。 属性的功能很强大,是个不错的改进。但你是不是还在想能不能先用数据成员来实现, 而在稍后需要其他各种功能的时候再改成属性呢?这看似是个不错的策略,不过实际上却行 不通。考虑如下这个类的定义 6第1章C井语言习惯 // using public data members, bad practice public class Customer public string Name // remaining implementation omitted 这个类描述一个客户( Customer),包含了一个名称(Name)。你可以使用熟悉的成员 表示方式获取或设置该名称 string name customerone Name i customerOne. Name ="This Company, Inc. 看似简单直观,你也会认为若是日后将Name改成属性,那么代码也可以无需修改保持正 常。但这个答案并不是完全正确的。属性仅仅是访问时类似于数据成员,这是语法所实现的 目的。不过属性并不是数据,属性的访问和数据的访问将会生成不同的MSIL( Microsoft Intermediate Language,微软中间语言)指令。 虽然属性和数据成员在源代码层次上是兼容的,不过在二进制层面上却大相径庭。这也 就意味着,若将某个公有的数据成员改成了与之等同的共有属性,那么就必须重新编译所有 用到该公有数据成员的代码。C#把二进制程序集作为一等公民看待。该语言本身的一个日标 就是支持发布某个单一程序集时,不需要更新整个的应用程序。而这个将数据成员改为属性 的简单操作却破坏掉了二进制兼容型,也就会让更新单一程序集变得非常困难 若是查看属性生成的,那么你或许会想比较一下属性和数据成员的性能。属性当然不 会比数据成员访问快,不过也不会比其慢多少。J编译器将内联一些方法调用,包括属性 访问器。当∏T编译器内联了属性访问器时,数据成员和属性的访间效率即可持平。即使某 个属性访问器没有被内联,其性能差距也实在是微乎其微,仅仅一次函数调用之别而已。只 有在某些极端情况下,二者的差距才会有所影响。 在调用方来看,属性虽然是方法,但它和数据却有着类似的概念。这会使你的调用者对 属性有着一些潜意识的认识。例如,调用者会把属性访问当成是数据的访问。不管怎样,二 者看上去很像。属性访问器应该满足这些潜意识的预期。ge访问器不应该有可被观察到的 副作用。set访问器会修改状态,用户应该可以看到调用后带来的改变 调用者也会对属性访问器的性能有着一定的预期。属性的访问就像是访问一个数据字 条目2用运行时常量( readonly)而不是编译期常量( const)7 段,因此不会与访问数据有太过明显的性能差别。属性访问器不应该执行长时间的计算,或1 进行跨应用程序的调用(例如执行数据库查询等),或是其他任何与调用者期待不符的耗时 操作。 无论何时需要在类型的公有或保护接口中暴露数据,都应该使用属性。你也应该使用索 引器来暴露序列或字典。所有的数据成员都应该是私有的,没有任何例外。这样你就立即得 到了数据绑定的支持,也便于日后对方法实现的各种修改。对于将任何变量封装到一个属性 所需的额外输入工作其实不会占用太多时间,而日后若是需要使用属性来更正设计,则会花 去大量的时间。现在多投入一点点,换来的是今后维护时的更加游刃有余。 条目2用运行时常量( readonly)而不是编译期常量( const C#有两种类型的常量:编译期常量和运行时常量。二者有着截然不同的行为,使用不当 将会带来性能上或正确性上的问题。这两个问题最好都不要发生,不过若难以同时避免的话, 那么一个略微慢一些但能保证正确的程序则要好过一个快速但不能正常工作的程序。考虑到 这些,你应该尽量使用运行时常量,而不是编译期常量。虽然编译期常量略微快一些,但是 却没有运行时常量那么灵活。应仅仅在那些性能异常敏感,且常量的值在各个版本之间绝对 不会变化时,再使用编译期常量 运行时常量使用 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 new DateTime(2000,1,1,0,0,0 编译期常量( const)仅能用于数字和字符串。运行时常量( readonly)也是一种常量, 因为在构造函数执行后不能被再次修改。二者的区别在于,只读的值将在运行时给出,这自 然会带来更好的灵活性。例如,运行时常量可以为任意类型。运行时常量必须在构造函数或 初始化器中初始化。你可以让某个 readon1y值为一个 Datatime结构,而不能指定某个 const 为 DataTime。 你可以用 readonly值保存实例常量,为类的每个实例存放不同的值。而编译期常量就 是静态的常量。 二者最重要的区别在于, readonly值是在运行时解析的。引用一个 readonly常量时生 成的引用的是 readon1y变量,而不是其值。这一点将会对日后的维护产生深远的影响 编译期常量将生成同样的讧,就像宜接在代码中给出数字一样,即使是跨程序集也是如此, 即使另一个程序集中引用了某个程序集中的某个常量,相应常量也会被直接替换成这个值。 编译期常量与运行时常量的求值方式将会影响运行时的兼容性。假设我们在一个名为 Infrastructure的程序集中分别定义了一个 const字段和一个 readonly字段: public class Usefulvalues 条目2用运行时常量( readonly)而不是编译期常量(cons)9 public static readonly int StartValue =5; public const int Endvalue = 10; 在另一个程序集中,我们使用了这两个值: for (int i= Usefulvalues. Startvalue: i Use fulValues. Endvalue; i++) Console. WriteLine(" value is to"1) 执行该程序,输出如下 value is 5 Value is 6 Value is 9 过了一段时间,我们吏新了 Infrastructure程序集,做出了如下慘改: public class Usefulvalues public static readonly int StartValue 105: public const int Endvalue =120 随后,在分发 Infrastructure程序集时,并没有重新编译整个应用程序的所有程序集。我们期 待的是如下的输出: value is 105 Value is 106 Value is 119 但实际上,你却看不到任何输出。因为现在那个循环语句将使用105作为它的起始值, 使用10作为它的结束条件。C#编译器在第一次编译 Application程序集时,将其中的 Endvalue 替换成了它对应的常量值10。而对于 Startva1ue来说,因为它被声明为 readonly,所以对 其求值会发生在运行时。因此, Application程序集在没有被重新编译的情况下,使用了的 Startvalue值。若想修改所有使用 readonly常量的客户代码的行为,只要简单地更新一个 Infrastructure程序集就够了。相反,更改一个公有的编译期常量的值应该被看做是对类型接 口的修改,你必须重新编译所有引用该常量的代码。而更改只读常量的值却仅仅算作是对类 型实现的修改,它与其客户代码在二进制层次上是兼容的。 不过,有时你确实需要让某个值在编译时确定。例如,考虑用系列常量来标出对象的

...展开详情
试读 127P C#高效编程50改进代码的地方
img
yucc883178

关注 私信 TA的资源

上传资源赚积分,得勋章
    最新推荐
    C#高效编程50改进代码的地方 9积分/C币 立即下载
    1/127
    C#高效编程50改进代码的地方第1页
    C#高效编程50改进代码的地方第2页
    C#高效编程50改进代码的地方第3页
    C#高效编程50改进代码的地方第4页
    C#高效编程50改进代码的地方第5页
    C#高效编程50改进代码的地方第6页
    C#高效编程50改进代码的地方第7页
    C#高效编程50改进代码的地方第8页
    C#高效编程50改进代码的地方第9页
    C#高效编程50改进代码的地方第10页
    C#高效编程50改进代码的地方第11页
    C#高效编程50改进代码的地方第12页
    C#高效编程50改进代码的地方第13页
    C#高效编程50改进代码的地方第14页
    C#高效编程50改进代码的地方第15页
    C#高效编程50改进代码的地方第16页
    C#高效编程50改进代码的地方第17页
    C#高效编程50改进代码的地方第18页
    C#高效编程50改进代码的地方第19页
    C#高效编程50改进代码的地方第20页

    试读已结束,剩余107页未读...

    9积分/C币 立即下载 >