避免创建非必要的对象
.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代码:
在第一个图中,我们看到这段代码的执行时这样子的:
1. 创建字符串”Now is:”(ldstr “Now is”)
2. 获取DateTime.Now()并装箱
3. 创建字符串 “.Welcome”(ldstr “.Welcome”)
4. 将上述三部分连接起来(Stirng.Concat(object,object,object))
而在第二个图中,我们只找到了一个ldstr,这样就为GC减轻了工作量。
总之,GC可以高效管理应用程序使用内存,但是创建和回收堆上的对象还是需要一定的时间的,因此,我们应该尽量避免没有必要的浪费。
参考:《Effective C#》