一、C#语言的编程习惯
优先使用隐式类型的局部变量
1、开发者把更多注意力集中在名称上,而不用分心去考虑类型
2、编译器选取的类型可能比开发者指定的合适:如IQueryable和IEnumerable
3、变量是值类型,不建议var,可能产生以下问题
(1)宽化转换,比较安全,比如float到double
(2)窄化转换:会令精度下降,比如long到int
考虑用readonly代替const
1、const:编译器期量,使用它的地方,在编译时会变成a=1,若常量发生变更而不重新编译,会不生效;readonly:运行期常量,赋值更灵活
2、const性能比readonly好
3、const可能存在的问题:若A程序集引用了B程序集的常量,B程序集修改了常量的值,A程序集没有进行重新生成,则A程序的常量值还是B程序集的旧值
优先考虑is或as运算法,尽量少用强制类型转换
用内插字符串$取代string.Format()
1、C# 6.0 提供
2、可读性更好
3、避免序号与数组位置不对应或数量不相等导致的bug
4、不会创建参数化SQL查询,SQL语句需注意
使用nameof属性获取变量名的字符串
用nameof运算符来写代码的好处是:
如果属性名变了,那么用来构造PropertyChangedEventArgs对象的参数也随之变化。
使用委托表示回调
用null条件运算符调用事件处理程序
例如:线程安全调用 event?.Invoke();
尽量避免装箱和取消装箱这两种操作
1、装箱是将值类型转换成引用类型
2、取消装箱是把已经装箱的那个值拷贝出来
3、拆箱是将引用类型转换成值类型,只有装箱过的对象才能拆箱
只有在对应新版基类与现有子类之间的冲突时才应该使用new修饰符
不鼓励把基类所有方法都设置成虚方法
设置虚方法就是告诉使用者派生类可能以其他方式实现这个虚方法
二、.NET的资源管理
理解并善用.NET的资源管理机制
1、垃圾回收器GC
(1)使开发者无需担心内存泄漏、迷途指针、未初始化的指针以及其他内存管理问题
(2)非托管资源需要开发者自己控制,如数据库连接、GDI+对象、COM对象等
2、两种控制非托管资源机制
(1)finalizer:防护机制,可以确保对象总是能够把非托管资源释放掉,但缺陷很多
(2)IDsposable接口,推荐使用
用适当的方式初始化类中的静态成员
1、如果初始化开销比较大或复杂,可以考虑运用Lazy<T>机制
2、如无多态,可在静态构造函数中实现单例
尽量减少重复的初始化逻辑
可以考虑带默认值的构造函数还是多个互相重载的构造函数
不要创建无谓的对象
绝对不要在构造函数里调用虚函数
实现标准的dispose模式
三、合理地运用泛型
这章节里的大多数篇幅比较常,代码量较多,就不多解释,有兴趣的可以去深入了解下。
通过运行期类型检查实现特性的泛型算法
通过IComparable及IComparer定义顺序关系
创建泛型类时,总是应该给实现了IDisposable的类型参数提供支持
考虑支持泛型协变和逆变
用委托要求类型参数必须提供某种方法
如果有泛型方法,就不要再创建针对基类或接口的重载版本
实现泛型接口的同时,还应该实现非泛型接口
如果不需要把类型参数所表示的对象设为实例字段,那么应该优先考虑泛型方法,而不是泛型类
只定义刚好够用的约束条件
只把必备的契约定义在接口中,把其他功能留给扩展方法去实现
考虑通过扩展方法增强已构造类型的功能
四、合理地运用LINQ
这章节里的大多数篇幅比较常,代码量较多,就不多解释,有兴趣的可以去深入了解下。
优先考虑提供迭代器方法,而不要返回集合
优先考虑通过查询语句来编写代码,而不要使用循环语句
我个人是觉得用循环可能好点,比较直观,一眼就知道代码是干嘛了,虽然查询语句也不差。
// 使用循环
var list1 = new List<int>();
foreach (var item in Enumerable.Range(0, 100))
{
list1.Add(item * item);
}
foreach (var item in list1)
{
Console.WriteLine(item);
}
// 使用查询语句
var list2 = (from n in Enumerable.Range(0, 100)
select n * n).ToList();
list2.ForEach(n => Console.WriteLine(n));
把针对序列的API设计得更加易拼接
将迭代逻辑与操作、谓词及函数解耦
等真正用到序列中的元素时再去生成
考虑通过函数参数来放松耦合关系
有点像AOP切面编程
绝对不要重载扩展方法
理解查询表达式与方法调用之间的映射关系
尽量采用惰性求职的方式来查询,而不是及早求值
考虑用lambda表达式来代替方法
不要在Func与Action中抛出异常
掌握尽早执行与延迟执行之间的区别
只有当程序确实要用到某个方法的执行结果时,才会去调用这个方法。
这就是声明式写法和命令式写法的重要区别。
不要把开销较大的资源捕获到闭包中
注意IEnumerable与IQueryable形成的数据源之间的区别
用Single()及First()来明确地验证你对查询结果所做的假设
不要修改绑定变量
五、合理地运用异常
令代码在发生异常时依然能保持稳定是每一位C#程序员所应掌握的关键技能。
考虑在方法约定遭到违背时抛出异常
1、如果方法不能够完成其所宣称的操作,那么应该通过异常来指出这个错误,如果改用错误码来实现,那么这些代码,容易被调用方所忽视。
2、错误码必须由调用方来处理,而异常则可以验证调用栈向上传播,直至到合适的catch子句。
利用using与try/finally来清理资源
如果某个类型用到了非托管的系统资源,如File,那么就需要通过IDsposable接口的Dispose方法来明确的释放。
而想要确保方法总是能够得到调用,最好的办法就是利用using语句或者try/finally语句块。
专门针对应用程序创建异常
优先考虑做出强异常保证
优先用异常筛选器来改写先捕获异常再重新抛出的逻辑
采用异常筛选器会给程序性能带来正面影响。.NET CLR对带有when关键字的try/catch结构做了优化,
使得程序在无须进入该结构时其性能尽量不会受到影响。
合理利用异常筛选器的副作用来实现某些效果
如下,捕捉异常后,保存日志后继续抛出:
// SaveLog返回false
// 不使用筛选器
try
{
try
{
Convert.ToInt32("w");
}
catch (Exception ex)
{
SaveLog(ex);
throw;
}
}
catch { }
// 使用筛选器
try
{
try
{
Convert.ToInt32("w");
}
catch (Exception ex) when (SaveLog(ex))
{
}
}
catch { }