第一部分: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(Object left,object right)、Equals(object left,object right)、virtual Equals(object right)、
Operator==(MyClass left,MyClass right)。equals()方法
2、对于前两个我们永远不需要重新定义,ReferenceEquals()方法判断依据是对象的标识,无论对象是引用还是值类型(意味着两个相同值的值类类比较永远是False);静态Equals()方法实质是调用了Left参数的Equals()方法来判断两个对象是否相等。
3、可重写的Equals()方法默认使用对象标识判断,即比较对象是否引用相等,但对于值类型准备准备效率不高,所以建议在创建值类型时,都覆写其ValueType.Equals()方法,而对于引用类型只当需要改变其预定义语义时才重写。例如字符串需要使用值语义而不是引用语义来判断是否相等。
4、当覆写Equals()方法时,也要同时实现IEquatable<T>,也应当重写GetHashCode()方法,同时提供operater==()操作。
七、理解GetHashCode()的陷阱
1、GetHashCode()仅在一种地方用到,即基于散列的集合定义键的散列值时,包括HashSet<T>和Dictionary<K,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、如下三种情况下要避免使用初始化器:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论