避免创建非必要的对象

.NET的垃圾收集器(GC)让我们从繁杂的内存管理工作中解脱出来。它可以很好的管理内存,也会以一种非常高效的方式来移除内存对象中的垃圾对象。但是,尽管高效,如果我们的代码写的不够良好,不仅分配对象需要花费时间,GC帮我们销毁垃圾对象也是需要花费时间的。因此,我们应该通过一些方法来避免这些花费。

1)将被频繁调用的引用类型的局部变量提升为成员变量

举个例子,窗体的Paint处理函数中创建GDI对象

        protected override void OnPaint(PaintEventArgs eventArgs)
        {
            using (Font myFont = new Font("Arial", 10.0f))
            {
                e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new PointF(0, 0));
            }
            base.OnPaint(e);
        }
在这个例子中,如果OnPaint被频繁的调用,那么,每次调用的时候,我们都必须创建一个myFont对象,而这个对象事实上是不变的都是10号的Arial字体,因此,我们可以通过以下方式来改进:
 
        private readonly Font myFont = new Font("Arial", 10.0f);

        protected override void OnPaint(PaintEventArgs eventArgs)
        {
            e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new PointF(0, 0));
            base.OnPaint(e);
        }

 

也就是,我们将myFont从局部变量提升为成员变量,这样,就不用每次调用的时候都去创建这样一个对象,也减少了GC每次调用OnPaint后都要回收一次。

另外,我们还应该避免写出这样的代码:

for (int i = 0; i < 10; i++)
            {
                object obj = new object();
                // do something
            }

也就是避免在循环里面创建对象

 

Tips:

若某个引用类型(值类型则无所谓)的局部变量用于将被频繁调用的例程中,那么应将其提升为成员变量。当然,只有会被频繁调用时才有这个必要。

 

2) 通过静态成员变量或单例可以让引用类型在类的各个实例中共享

上个例子中的Brushes.Black就是这样的一个例子。如果我们每次需要一个黑色笔刷时,就去创建一个,那么在程序的执行过程中,我们就会需要创建和销毁大量的黑色笔刷。如果按照上面的方法,将黑色笔刷提升为成员变量,也是有所帮助的,但是,我们在程序的其他地方也是需要这样的黑色笔刷的。

另外一种方法是单例,假设在一个电子商务网站中,我们创建了一个打折的规则,我们知道,这个规则的内容对于所有的地方都是一样的,因此,我们就可以将这个规则写成一个单例,这样,程序中需要用到的时候都是共享一个实例,不用每个地方/线程都去创建一个这样的规则。

 

3)直接创建不可变类型的最终值

以string类型为例子吧。我们知道,如果我们将2个string类型的变量进行拼接,事实上,我们会产生第三个string类型,他是前面2个拼接后的结果。也就是说string类型是不可变得。

如:

string msg = "Hello,";
msg+=User.Name;
msg+="Welcome to China."

 

尽管我们这样写看上去很简单很高效,但是,上面的代码等价于:

string msg = "Hello,";
string temp1 = new string(msg+User.Name);
msg = temp1;
string temp2 = new string(msg + "Welcome to China.");
msg = temp2;

 

当这段代码执行完后,msg,temp1都将成为垃圾呗回收.一种替代的做法是使用string.Format();

string msg = string.Format(“Hello,{0}Welcome to China.”,User.Name);

 

我们来对比以下2段代码吧:

string test = "Now is :" + DateTime.Now + ". Welcome!";

vs

string test = string.Format("Now is :{0}.Welcome",DateTime.Now);

通过Reflector工具,我们来看看这2段代码生成的IL代码:

image

 

image

在第一个图中,我们看到这段代码的执行时这样子的:

1. 创建字符串”Now is:”(ldstr “Now is”)

2. 获取DateTime.Now()并装箱

3. 创建字符串 “.Welcome”(ldstr “.Welcome”)

4. 将上述三部分连接起来(Stirng.Concat(object,object,object))

而在第二个图中,我们只找到了一个ldstr,这样就为GC减轻了工作量。

 

总之,GC可以高效管理应用程序使用内存,但是创建和回收堆上的对象还是需要一定的时间的,因此,我们应该尽量避免没有必要的浪费。

 

参考:《Effective C#》

posted @ 2012-05-12 12:01  Xiao Tian  阅读(271)  评论(0编辑  收藏  举报