【设计原则和建议】 构造和析构对象
良好的构造和析构对象,控制对象生命周期可以较大的提高程序的性能,降低GC的压力,减少BUG几率。
本文还是比较简单的,主要还是经验的总结,很多东西也许各位已经知道,也许不知道。希望大家一起讨论。
1.如果可能,避免静态构造函数 (也成为类型构造函数)
- 性能原因 (不过因为一个类的静态构造函数只会执行一次,这不是一个大问题)
- 静态构造函数不应该抛出异常
2.如果可以,构造函数应该尽可能轻量级
- 职责上说,构造函数只应该构造出一个对象,而不是执行一大堆初始化等的操作
- 如果有很重量级的代码,用静态方法Create出来 例如WebRequest.Create
3.一个常识,调用构造函数时,会先调用父类的构造函数
4.一个类必须输入的参数请放在构造函数的输入参数中(或Create方法)
- 主要是逻辑和保障执行顺序的原因
- 不要构造完了以后一个个属性去赋值
5.如果要按照顺序初始化私有成员,请在构造函数中进行 (或Create方法)
- 虽然目前CLR是从上至下的初始化私有成员,但是不保证将来版本的初始化顺序也是一样的
- 某些第三方工具自动格式化代码的时候会改变
6.构造函数中不应该调用虚方法
7.使用this()和base()可以方便构造函数之间的相互调用
8.不要在不必要的情况下构造对象 (虽然有可能被优化掉)
DataSet ds = new DataSet();
ds = SqlHelper.ExecuteDataSet("select * from table1");
9.在更考虑性能的情况下,缩短对象生命周期 (大部分程序都不会这么考虑性能)
- 尽量迟的初始化
- 尽量早的销毁对象
- 尽量小的作用域
- 如果对象有可能被复用,研究一下GC的对象复活
- 一个小提示
-
SingletonClass item1 = null;
for (int i = 0; i < 100; i++)
{
SingletonClass item2 = null;//在这两个地方声明,没有区别,被优化掉了
}
10.如果对象实现了IDisposable
- 请使用using 或者直接调用Dispose
- 有些系统对象实现了Close, 其实Close内部就是调用了Dispose()
11.如果想实现一个IDisposable
简单的实现
class ConstructorDemo : IDisposable
{
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); //告诉CLR不要调用Finalize 这是一个非常消耗性能的方法
}
private void Dispose(bool disposing)
{
if (!disposing)
{
//清理托管资源
}
//清理非托管资源
}
}
完整的实现 http://msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
12.如果想实现Singleton
public class ConstructorDemo1
{
private static SingletonClass singleton = new SingletonClass();
//简单的类 我最喜欢用这种方式了,不够构造函数出问题了就很讨厌了
//只初始化一次 不用太关心性能问题
//如果SingletonClass构造函数抛出异常 那就完蛋了
private ConstructorDemo1()
{
}
public SingletonClass Create()
{
return singleton;
}
}
public class ConstructorDemo2
{
private static SingletonClass singleton = null;
private static object asyncLock = new object();
private ConstructorDemo2()
{
}
public SingletonClass Create()//只初始化一次 不用太关心性能问题,初始化有问题可以在这里处理啊
{
if (singleton == null)
{
lock (asyncLock)
{
if (singleton == null)
{
singleton = new SingletonClass();
}
}
}
return singleton;
}
}
public class ConstructorDemo3
{
private static Lazy<SingletonClass> singleton = new Lazy<SingletonClass>();
private ConstructorDemo3()
{
}
public SingletonClass Create()
{
return singleton.Value;//4.0 还是这样简单
}
}
13.了解GC的大致原理将有利于设计出符合预期的程序
- 建议参考CLR via C#
- 如果不想被GC的最简单方式把对象放在一个static 成员上
- 一个例子关于资源被意外回收
using System;
using System.Threading;
public static class Program
{
public static void Main()
{
//注意要在Release模式下跑
Timer t = new Timer(TimerCallback, null, 0, 2000);
Console.ReadLine();
}
private static void TimerCallback(Object o)
{
Console.WriteLine("In TimerCallback: " + DateTime.Now);
GC.Collect(); //触发GC
}
}
14.一般不使用析构函数释放资源
- 微软推荐用析构函数释放非托管资源 (例如文件,网络连接)
- 我个人建议是最好都别用析构,而是显式回收资源(IDisposable),因为析构内部是调用Finalize ,而这东西的运行时间是不确定的。。。而且还有性能问题
public class Deconstructor
{
//非常不推荐使用Finalize,主要问题就是其调用时间是不确定的,并且有性能问题
~Deconstructor()
{
// 清理代码 //将被转换为下面的代码
}
//protected override void Finalize()
//{
// try
// {
//// 清理代码
// }
// finally
// {
// base.Finalize();
// }
//}
}
15.不要创建大量的小对象
- 例如string
- 例如WCF,webservice中序列化反序列化
PS: GC.SuppressFinalize 经常在Dispose被调用, 用于通知CLR:我自己会搞定,你不要调用Finalize
PS: GC有workstation和server模式
PS: 有GC也是会存在内存泄漏的,一般常见用有些对象一直被引用 所以无法被GC
PS: GC超时也会抛出OutOfMemoryException, 不一定是真的没有内存了
PS:使用DotNetTrace 之类的工具分析内存中都存在什么对象,也可以分析构造和析构函数的性能
PS:使用WinDBG+SOS分析内存也很不错(特别是生产环境)
PS:我个人喜欢静态方法胜于实例方法(不喜欢每次构造对象),除非是设计需要,或者逻辑上应该是实例方法
部分资料来源于网络,因为本人水平有钱,如有谬误还请指正。