您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 销售管理 > 改善C程序的50种方法(第二版)
C#高效编程改善C#程序的50种方法(第2版)读书笔记第一部分:C#的语言元素一、用属性代替可访问的字段1、.NET数据绑定只支持对属性的数据绑定,而不支持公有数据成员;2、在属性的get和set访问器中可使用lock添加多线程的支持。二、用readonly(运行时常量)而不是const(编译时常量)1、const只可用于基元类型、枚举、字符串,而readonly则可以是任何的类型;2、const在编译时将替换成具体的常量,这样如果在引用中同时使用了const和readonly两种值,则对readonly的再次改变将会改变设计的初衷,这是需要重新编译所更改的程序集,以重新引用新的常量值。3、const(属于编译时常量)比readonly(运行时)效率高,但失去了应用的灵活性。三、用is与as操作符而不是强制类型转换1、两者都是在运行时进行类型的转换,as操作符只能使用在引用类型,而is可以使用值和引用类型;2、通常的做法是用is判断类型,然后选择使用as或强类型转换操作符(用operater定义的转换)有选择地进行。四、ConditionalAttribute代替#if#endif条件编译1、ConditionalAttribute只用于方法级,对其他的如类型、属性等的添加都是无效的;而#if#endif则不受此限制;Conditional特性强制我们将条件代码拆分为若干独立的方法,有助于我们代码的高效性。语法:[Conditional(“DEBUG”)]。2、ConditionalAttribute可以添加多个编译条件的或(OR)操作,例如:[Conditional(“DEBUG”),Conditional(“TRACE”)],而#if#endif则可以添加与(AND),若想创建一个依赖多个环境变量的条件例程,我们必须使用#if时,应该避免将可执行代码放入其中,即#if只不是创建新的符号而已;3、ConditioanlAttribute定义可以放在一个单独的方法中,使得程序更为灵活。#if和#endif会在Release和Debug版本中都留下一个方法,虽然在Release中什么也不做,但是方法加载、JIT编译还是会有一些开销。且易引发一些意想不到的Bug。五、为类型提供ToString()方法1、可以通过重写以更友好的方式提供用户需要的信息,让类型的ToString()方法输出有用的信息;【C#3.0中编译器会为所有匿名类型创建一个默认的ToString()方法,将显示对象中的每个属性值。】2、使用IFormatter.ToString()方法提供更灵活的定制,如果添加IFormatProvider和ICustomFormatter接口则更有意义的定制消息输出。六、理解几个等同性判断间的关系1、当我们创建类型时,应该为类型定义“等同性”含义。C#提供4种不同的函数来判断两个对象的是否“相等”,ReferenceEquals(Objectleft,objectright)、Equals(objectleft,objectright)、virtualEquals(objectright)、Operator==(MyClassleft,MyClassright)。2、对于前两个我们永远不需要重新定义,ReferenceEquals()方法判断依据是对象的标识,无论对象是引用还是值类型(意味着两个相同值的值类类比较永远是False);静态Equals()方法实质是调用了Left参数的Equals()方法来判断两个对象是否相等。3、可重写的Equals()方法默认使用对象标识判断,即比较对象是否引用相等,但对于值类型准备准备效率不高,所以建议在创建值类型时,都覆写其ValueType.Equals()方法,而对于引用类型只当需要改变其预定义语义时才重写。例如字符串需要使用值语义而不是引用语义来判断是否相等。4、当覆写Equals()方法时,也要同时实现IEquatableT,也应当重写GetHashCode()方法,同时提供operater==()操作。七、理解GetHashCode()的陷阱1、GetHashCode()仅在一种地方用到,即基于散列的集合定义键的散列值时,包括HashSetT和DictionaryK,V容器等。Object提供的GetHashCode()效率低下,创建大多数类型,最好的的办法是完全避免实现GetHashCode();2、如果创建的类型将被当做散列表中的键来使用,则需要自己实现GetHashCode()。3、重写GetHashCode()必须遵循以下3个原则:(1)如果两个对象相等(由Operator==定义),那么它们必须生成相同的散列码。(2)对于任何一个对象A,应当是一个实例不变式,即A.GetHashCode()必须保持不变。(3)对于所有输入,散列函数都应该在所有整数中按照随机分布生成散列码。八、推荐使用查询语法而不是循环1、查询语法比循环具有更强的可读性。可以将过滤(Where)、排序(Orderby)、和一个投影(Select)组合在一次遍历中实现。2、Linq并行计算扩展,只需要简单的在查询后加.AsParallel()方法即可。2、foreach可以消除编译器对for循环对数组边界的检查;foreach的循环变量是只读的,且存在一个显式的转换,在集合对象的对象类型不正确时抛出异常;3、foreach使用的集合需要有:具备公有的GetEnumberator()方法;显式实现了IEnumberable接口;实现了IEnumerator接口;4、foreach可以带来资源管理的好处,因为如果编译器可以确定IDisposable接口时,可以使用优化的try…finally块;九、避免API中使用转换操作符1、通过构造函数而不是转换操作符来创建对象,C#只有使用New操作符时才会创建新对象。2、在访问被替换的对象时,和使用者打交道的实际上是一些临时对象或者内部的字段,这些临时对象被修改后就会被丢弃。而且进行类型转换的代码是由编译器完成的,所以Bug很难被发现。十、使用可选参数减少方法重载数量1、C#可选参数和命名实参可以大大简化重载代码数量并具有相当的灵活性2、在C#4.0中Ref在COM场景中也变成了可选,几乎所有的参数都会以引用形式传递,即使这些参数不会被调用方法修改。3、具名参数可以避免参数顺序所带来的混乱。例SetName(lastName:”aaa”,firstName:”bbb”);4、对于程序集第一次发布,可以随意使用可选参数和具名参数的,并任意给出你想提供的重载,而在进行后续发布时,必须为额外的参数创建重载,这样才能保证现有程序的正常运行;在任何的后续发布中,都要避免修改参数的名称,因为参数名称已经成为了公有接口的一部分。十一、理解短小方法的优势1、在C#中,手工进行的额外优化反而会拖慢JIT的运行速度,要尽量写出最清晰的代码,将优化工作交给JIT编译器完成。如:将函数逻辑直接写进循环以期降低方法调用。2、JIT编译器以方法为单位进行编译,CLR将按照函数粒度逐一进行JIT编译,没有被调用的方法不会被JIT编译,如果要处理太多IL,将大大减慢程序的启动速度。3、如果将较长的Switch中的Case语句的代码替换成一个一个的方法,则JIT编译器所节省的时间将成倍增加;4、提高程序的启动和运行速度,就必须注意分支语句的优化,因为分支诗句在第一次调用时,其所有分支都会被JIT编译,而我们却只会用到某个分支中的内容。5、短小精悍的方法并选择较少的局部变量可以获得优化的寄存器使用,即选择哪些局部变量放在寄存器中,而不是栈上,越少使用局部变量,方法内的控制分支越少,JIT就会更方便的找到最适合的寄存器中的那一些。6、保证代码的尽可能的清晰可读,也就让JIT更容易的分析并做出优化,方法越简单越好,因为它适合内联,不过虚方法和Try/catch块中的方法不会被内联。第二部分:.NET资源管理十二、推荐使用成员初始化而不是赋值语句1、在声明变量时就进行初始化。而不是在每个构造函数中进行。若没有指定构造函数,C#编译器将为你的类型创建一个默认的构造函数。2、如下三种情况下要避免使用初始化器:(1)想要初始化的对象为0或null时,因为0或null的初始化很底层,会直接使用CPU指令将一整块内存设置为0,此时再执行一次额外的0初始化是多此一举;(2)对同一个对象执行不同初始化方式,使用初始化器会产生垃圾对象,降低代码的执行效率;(3)需要对字段进行异常处理时。十三、正确初始化静态成员变量1、静态初始化器会在一个类的任何方法、变量或者属性访问之前执行,甚至在调用基类的静态构造函数之前执行;2、使用静态构造函数而不是初始化器最常见的理由是处理异常,在使用静态初始化器时,无法捕获异常。十四、尽量减少重复的初始化逻辑1、默认参数让我们在处理构造函数时有了更多的选择,但使用了new()约束的泛型类不支持所有参数都有默认值的构造函数,所以类必须提供显示的无参构造函数。2、sring.Empty不是一个编译期常量,只是sring类的一个属性,所以sring.Empty不能用作参数默认值。3、C#4.0以前不支持默认参数,这时可以使用构造器链,让一个构造函数调用声明在同一个类中的另一个构造函数,而不是创建一个公用的辅助方法。4、构造函数定义中只能使用一个初始化器,要么使用This()委托给另一个构造函数,要么使用base()调用基类的构造函数,二都不可兼得。5、推荐使用This()委托,并使用初始化器来初始化简单资源,使用构造函数来初始化需要复杂逻辑的成员,同时不将调用抽取到一个构造函数中,以便减少重复。十五、利用using和try/finally语句来清理资源1、使用非托管系统资源的类型必须显示调用IDisposable接口的Dispose()来释放,Using()语句可以保证调用到Dispose(),Using()语句将生成一个Try/finally块,包裹住分配的对象。2、仅在编译期类型支持IDisposable接口时,你才能使用Using语句,对于编译期无法确定的对象,则不适用。若需要同时分配多个IDisposable对象,应该使用Try/finally。十六、尽量减少内存垃圾,避免创建非必要对象1、分配和销毁一个堆上的对象都要花费额外的处理器时间,所有的引用类型,即使是局部变量,都是在堆上分配的,引用类型的局部变量在函数退出后马上成为垃圾;2、减少分配对象数量的技巧:经常使用的局部变量提升为成员变量(字段);提供一个类,用于存储某个类型常用实例的单例对象。3、用StringBuilder进行复杂的字符串操作。十七、实现标准的销毁模式1、使用非托管系统资源的类型必须提供一个终结器,GC运行时,会清理掉那些没有提供终结器的托管垃圾对象,而提供了终结器的对象则会停留在内存中,添加到终结队列中,由GC调用另一个线程来执行终结器。2、实现IDisposable接口是一种标准做法,用来通知使用者和运行时系统对该对象包含的资源需要及时释放。在需要IDisoposable接口的类型中,即使我们不需要一个终结器也应该实现一个终结器。为需要多态的类型添加一个受保护的虚方法Dispose(),派生类通过重写这个方法来释放自己的任务;3、使用IDisposable.Dispose()方法需要做四个方面的工作:释放所有的非托管资源;释放所有的托管资源;设置一个状态标记来表示是否已经执行了Dispose();调用GC.SuppressFinalize(this)取消对象的终结操作;十八、区分值类型和引用类型1、值类型不支持多态,其最佳用途是存放在应用程序中用到的数据。2、用值类型表示底层数据存储的类型,用引用类型来封装程序的行为。3、使用接口而不是使用类型可以避免装箱,即将值类型从接口实现,然后通过接口调用成员。十九、保证0为值类型的有效状态1、在创建枚举值时,请确保0是一个有效的选项。2、系统初始化过
本文标题:改善C程序的50种方法(第二版)
链接地址:https://www.777doc.com/doc-2416551 .html