【进阶修炼】——改善C#程序质量(4)

46, 显示释放资源,需要实现IDisposable接口。

最好按照微软建议的Dispose模式实现。实现了IDisposable接口后,在Using代码块中,垃圾会得到自动清理。

47, 即使提供了显示的释放方法,也应该在终结器中提供隐式实现。

因为我们不能保证用户会主动去调用这个释放方法,但我们要保证在垃圾回收时,这些资源能得到清理。

48, Dispose方法应该允许被多次调用。

我们可以创建一个变量disposed来标明对象是否被释放了。

49, 在Dispose模式中应提供一个受保护的虚方法。

因为这个类可能被其子类重写,这时我们需要调用父类的Dispose方法。

50, 在Dispose模式中区别对待托管资源和非托管资源。

Dispose(true)是释放所有的托管和非托管资源,是用户主动调用的。Dispose(false),是垃圾回收器调用的,只能释放非托管资源,因为垃圾回收器的调用顺序是没有保障的,如果这时去释放托管资源,该托管资源可能已经被释放了,从而引发异常。

51, 拥有本机资源或包含可释放字段的类型应该实现Dispose模式。

52, 及时释放资源。

垃圾回收是根据一定的策略来执行的,不再使用的资源要及时释放,不要等到垃圾回收。

53, 必要时应将对象的引用赋值为null。

但要注意一点,对局部变量或函数的参数赋值为null没有太大的意义,因为这些变量会在函数结束时交给垃圾回收器处理,对象变量不再持有对对象的引用。

54, 为无用字段标注不可序列化。

原因可以是以下几种:节约了空间;反序列化后字段没有意义了,如Windows的句柄;业务上的原因不允许被序列化,如密码;字段本身对应的类型不可序列化,这会抛出SerializationException异常。在类型上使用Serializable特性可以将整个类型标识为可序列化,如果部分字段不需要序列化,可以在该字段上应用NonSerialized特性。属性事实上是方法,所以是不能序列化的,自动属性也是如此。另外,要标识事件为不可序列化,需要用field: NonSerialized语法。

55, 利用定制特性减少反序列化的字段。

OnDeSerializedAttribute,应用于方法时会在对象被反序列化后调用,我们可以在这里对没有序列化的字段进行必要的重建。相似的特性还有OnDeSerializingAttribute,OnSerializedAttribute,OnSerializingAttribute。

56, 继承ISerializable接口实现更灵活的序列化过程。

上面提到的几个特性如果不能完全满足我们的要求,可以考虑实现这个接口。有了这个接口,上面应用的特性会被忽略掉。与这个接口相关的两个方法是:ISerializable.GetObjectData,用于序列化对象。protected 类名(SerializationInfo info,StreamingContext context),添加这个受保护的构造函数用于反序列化。注意,这个方法需要我们手动添加,编译器检查不到,否则不能反序列化,你可以认为这是一种约定。

57, 实现ISerializable的子类应负责父类的序列化。

如果父类实现了ISerializable接口,我们调用base.GetObjectData就可以了;如果没有实现,则需要对父类的每个字段单独进行序列化。

58, 用抛出异常来取代错误返回码。

错误返回码会导致判断分支增加,给调用者带来麻烦,应充分利用.net的异常处理机制来简化代码。

59, 不要在不恰当的场合引发异常。

对于调用WindowsAPI或第三方DLL只能返回错误码的情况,可以考虑抛出自己的异常。代码存在资源泄漏,资源不可用等可以抛出异常。在捕获异常时,需要包装更有用的信息,可以抛出异常。正常的业务流程不应使用异常来处理,例如可控范围内的输入输出。

60, 重新抛出异常时使用InnerException。

将内部异常放置到打包异常里,以供外部程序了解到异常发生的真正原因。

61, 避免在finally块中写无效代码。

如果catch块中有return语句,return 语句是优先于finally块的,也就是如果return返回了变量的值,finally块再修改了这个变量的值,返回结果不会受到影响。当然返回引用的情况除外,因为两个引用指向的是同一个对象。编译器为我们新增加了一个变量用于保存Return处的值,在finally块结束后再返回这个值。另外,对于CSE(Corrupted State Exceptions)异常,在.net 4.0以后CLR不会捕获了,也就是如果程序抛出如AccessViolationException的异常,catch块和finally块代码不会被执行。注意,如果我们主动在托管代码中抛出的new AccessViolationException()是会被捕获的,CLR会检查异常的来源。要改变这种默认的行为,有以下几种方法。1)在调用的方法上添加[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions]特性。2)改变工程属性的.net版本为3.5或更早,再进行编译。即使这个程序运行在.net4.0的环境中,也会捕获CES异常。3)修改Config文件的配置,在Configuration/runtime下添加这个节点:<runtime><legacyCorruptedStateExceptionsPolicy enabled="true"/></runtime>。后面两种修改方法都是对整个工程生效的。

62, 避免无故的嵌套异常。

嵌套异常会使代码变得臃肿,除非我们要恢复一些状态,否则不应该每个函数都去try catch,这会给调用者带来麻烦,同时也会隐藏掉异常出错的真正原因,不利于排错。我认为内部函数如何仅仅是为了写日志,应该让最外层函数统一处理,内部函数不要捕获异常,如果非要捕获,也要用throw,而不能用throw e,后者会造成堆栈信息的丢失。

63, 避免吃掉异常。

如果我们不知道如何处理异常,就应该往上抛出,交给上层代码来处理。

64, 为循环添加Tester-Doer模式,而不是添加try-catch。

在大量失败的测试中使用try-catch会大大损害性能。可以添加条件判断(如IF等)来避免这种情况。

65, 总是捕获未处理的异常。

未捕获的异常,可以通过System.AppDomain.CurrentDomain.UnhandledException捕获到。Winform中还有一个Application.ThreadException用于捕获UI线程异常。WPF中是Dispatcher类的DispatcherUnhandledException,并且需要设定e.Handed=true;否则会继续引发System.AppDomain.CurrentDomain.UnhandledException异常。

66, 正确捕获多线程中的异常。

多线程中的异常不会主动传递到UI线程中,如果未捕获就会传递到System.AppDomain.CurrentDomain.UnhandledException这里。我们可以将线程中的异常通过代理的方式传递到UI线程中,this.BeginInvoke((Action)delegate { throw ex; });这样可以对未处理的异常进行统一的处理。

67, 慎用自定义异常。

通常自定义异常的理由是:1)方便调试,通过自定义的异常,可以准确知道代码所发生的事情。2)逻辑包装,将多种异常包装成一种异常,方便调用者编码。3)引入新的异常类。

68, 从System.Exception或其他常见的基本异常中派生异常。

注意,自定义异常如需实现序列化,要继承ISerilizable接口,将自定义字段序列化后调用父类的序列化方法GetObjectData。

69, 使用finally避免资源泄漏。

Finally块的特性使我们可以将释放资源的操作放在这里,因为即便出现异常,finally也会被执行。

70, 避免在调用栈较低的位置记录异常。

并不是所有的异常都需要被记录到日志,一类情况是异常发生的场景需要被记录,还有一类是未捕获的异常。为了避免异常被重复记录,应该在项目的初期就约定异常日志记录的规则。

时间: 2024-08-02 22:21:15

【进阶修炼】——改善C#程序质量(4)的相关文章

【进阶修炼】&mdash;&mdash;改善C#程序质量(1)

这是一个大纲形式的概要,以便自己可以花较少的时间反复阅读.在开发中,多加注意这些有用的建议,让自己成为一个更优秀的程序员.内容主要来自<编写高质量代码-改善C#程序的157个建议>(陆敏技),这本书写的真的很好,都是些实战经验的总结,建议大家购买,这其中的建议不仅仅适合于C#,只要你做.NET开发,阅读此书都会从中受益.同时,其他书籍和资料的一些好的编程建议,我也会不断更新到这里. 1, 字符串使用. 应避免发生装箱:避免分配额外的内存:考虑使用StringBuilder来替代string.s

【进阶修炼】&mdash;&mdash;改善C#程序质量(9)

140,使用默认的访问修饰符. 如果不加访问修饰符,成员变量的默认是private的,类默认是internal的.为了明确访问的权限,我倒是建议都加上访问修饰符,这省不了多少代码. 141,不知道该不该加大括号的时候就加上. 大括号会多占用两行代码,到底一行语句的代码需不需要加大括号这是一个争论.但是为了避免引入不必要的bug还是加上吧. 142,总是提供有意义的命名. 我们不希望看到如iTemp的命名,类似于i的命名只应该出现在循环中.变量的命名应尽可能表达它应该表达的意思,不要太随意的命名,

【进阶修炼】&mdash;&mdash;改善C#程序质量(2)

16, 元素可变的情况下应避免用数组. 数组是定长的集合,可以考虑用ArrayList或List<T>集合.ArrayList元素是object类型,有装箱的开销,性能较低.另外Array类提供了Array.CreateInstance来创建数组,Array.Copy来拷贝数组,但这牵涉到新数组的创建,会增加开销. 17, 多数情况下用foreach代替for循环. 18, Foreach不能代替for. Foreach不能对元素进行增删操作,不能访问序号. 19, 使用对象初始化器和集合初始

【进阶修炼】&mdash;&mdash;改善C#程序质量(7)

113,声明变量时考虑最大值. Ushort的最大值是65535,用于不同的用途这个变量可能发生溢出,所以设计时应充分了解每个变量的最大值. 114,MD5不再安全. MD5多用于信息完整性的校验.R=H(S),MD5的算法是不可逆的,也就是我们几乎没有可能根据生产的MD5码去还原原文.但是我们可以使用穷举的办法生成MD5码来对比,由于我们平常设定的密码都比较简单,如:123456,根本不用很长的时间就会被破解掉.为了有效防止破解,一个可行的做法是为我们的密码固定加一个字符串"%¥#--@!&a

【进阶修炼】&mdash;&mdash;改善C#程序质量(3)

32, 总是优先考虑泛型. 泛型代码有很好的重复利用性,和类型安全性. 33, 应尽量避免在泛型类中声明静态成员. 静态成员达不到共享的目的.List<int>和List<String>是两个不同的类型,而静态成员是针对类型的.当然2个List<int>之间是可以共享静态成员的,但为了不必要的混淆,应该避免使用静态成员. 34, 为泛型参数添加约束. 没有约束的参数,功能是有限的,添加了约束后,我们就可以使用约束类型的方法和属性了,程序更加灵活. 35, 使用Defau

【进阶修炼】&mdash;&mdash;改善C#程序质量(6)

90,不应为抽象类指定public的构造函数. 抽象类即使指定了public的构造函数,也是不能实例化的,编译通不过.抽象类的构造函数应该设定为protected,它的作用应该是初始化自己的成员,以及可以被子类构造函数调用.设定为public权限毫无意义. 91,可见字段应该重构为属性. 属性比字段有更好的灵活性,可以加入代码控制,可以在类型内部实现线程安全访问,可以让类型得到通知(如WPF系统),这些都是字段所不具备的.当然,如果仅限于类型内部使用,应该用字段. 92,谨慎将数组或集合作为属性

【进阶修炼】&mdash;&mdash;改善C#程序质量(5)

71, 区分异步和多线程的应用场景. 计算机的很多硬件,如硬盘,光驱,声卡,网卡都有DMA(Direct Memory Access)功能,它可以不占用cpu的资源,而异步的提出恰恰就是基于这个的.而多线程是操作系统上的并行执行的代码,是会占用cpu资源的.所以关于这两种的使用场景建议是:1)对于I/0密集型操作使用异步.2)对于计算密集型操作使用多线程. 72, 在线程同步中使用信号量. 值类型是不能被锁定的,引用类型上的等待机制,分为锁定和信号同步.锁定通过lock关键字或Monitor来完

【进阶修炼】&mdash;&mdash;改善C#程序性能(1)

这是一个大纲形式的提点,以便自己可以花较少的时间时常浏览.在开发中,多加注意这些有用的建议,让自己成为一个更优秀的程序员.内容主要来自<编写高质量代码-改善C#程序的157个建议>(陆敏技),这本书写的真的很好,都是些实战经验的总结,建议大家购买此书,这其中的建议不光适合于C#,只要你做.NET开发,阅读此书都会从中受益.同时,如其他书籍和资料有好的建议,我也会不断更新到这里. 1, 字符串使用. 应避免发生装箱:避免分配额外的内存:考虑使用StringBuilder来替代string.str

Android 控件进阶修炼-仿360手机卫士波浪球进度控件

技术:Android+java 概述 像360卫士的波浪球进度的效果,一般最常用的方法就是 画线的方式,先绘sin线或贝塞尔曲线,然后从左到右绘制竖线,然后再裁剪圆区域. 今天我这用图片bitmap的方式,大概的方法原理是: (1)首先用clipPath裁剪园区域, (2)然后用4张图来不断绘制到画布上,再用偏移量来控制移动的速度,从而形成波浪动态效果. (3)有一点需要注意的是,裁剪圆的时候用到的clipPath这个方法,在android 4.1,和4.2等某些系统上,裁剪出来不是圆,而是矩形