【设计原则和建议】 构造和析构对象


良好的构造和析构对象,控制对象生命周期可以较大的提高程序的性能,降低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

View Code
    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 ,而这东西的运行时间是不确定的。。。而且还有性能问题
View Code
    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:我个人喜欢静态方法胜于实例方法(不喜欢每次构造对象),除非是设计需要,或者逻辑上应该是实例方法

 

部分资料来源于网络,因为本人水平有钱,如有谬误还请指正。

 

posted on 2011-11-17 12:47  听说读写  阅读(1846)  评论(2编辑  收藏  举报

导航