Effective C# Item 16: Minimize Garbage

      在我们使用C#时,GC(Garbage Collector)会非常好的帮助我们完成对内存资源的管理,它会非常高效的清理没有存在意义的对象。但是不管怎么说,即便再高效的工作,频繁的分配和销毁堆中的对象也会降低效率。如果我们在程序中创建了大量的引用类型,那将会在一定程度上影响到执行的效率。

      因此我们不要让GC过度工作。我们可以使用一些简单的方法来尽量减少GC在我们程序中的操作。所有的引用类型都在堆上分配内存。当一个函数调用结束时,其中的局部变量就会变成垃圾。下例是一个反面典型:

        protected override void OnPaint(PaintEventArgs e)
        
{
            
using(Font myFont = new Font("Arial"10.0f))
            
{
                e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, 
new PointF(00));
            }

            
base.OnPaint (e);
        }

      OnPaint()方法被调用非常频繁,每次调用时就会创建一个Font对象,而这个对象的内容却是相同的。GC需要在每次调用结束后清理这些资源,这就降低了效率。

      我们可以将Font对象由局部变量改为成员变量,这样我们就不必每次都重新创建它了。

        Font myFont = new Font("Arial"10.0f);
        
protected override void OnPaint(PaintEventArgs e)
        
{
            e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, 
new PointF(00));
            
base.OnPaint (e);
        }

      现在我们的程序在调用Paint时不会再产生不必要的垃圾了,GC做的工作也相应减少了,效率也就提升了。当我们将局部变量转变为成员变量以减少垃圾的时候,我们应该实现该对象类型的IDisposable接口。让我们可以在最后释放它的资源。

      当我们的局部变量是引用类型而且调用频繁的时候,我们就应当将其提升为成员变量。Font就是一个很好的例子。不过只有在频繁被调用时候才这样做。我们的目的是减少垃圾,而不是说要将所有的局部变量变为成员变量。

      避免创建重复对象的另一个方法是使用静态成员,一个例子就是Burshes.Black。每次当我们在绘制窗口的时候就要用到它。如果我们每次都要创建一个新的Brush,那么我们就需要销毁非常多的重复Brush。因此.Net Framework的设计者便创建了一个Brushes类,其中包含了很多的Brush的静态对象。

        private static Brush _blackBrush;
        
public static Brush Black
        
{
            
get
            
{
                
if(_blackBrush == null)
                
{
                    _blackBrush 
= new SolidBrush(Color.Black);
                }

                
return _blackBrush;
            }

        }

      当我们第一次需要一个黑色的Brush时,Brushes类会创建它。这个黑色的Brush会一直被我们使用下去。而且对于我们不需要的Brush,例如绿色的,永远也不会被创建。这样我们可以付出最少的代价来得到想要的结果。

      上面提到了两种减少垃圾出现的办法,一个是将局部变量提升为成员变量,一个是通过声明类的静态成员。还有一种方法是为不可变类型提供其最终值,而不要频繁修改它。System.String就是一个不可变类型。当我们构建了string之后,它的内容就是不修改的了。当我们修改string时,其实是创建了另外一个string的对象,而原来的对象就变成了垃圾:

string msg = "Hello,";
msg 
+= thisUser.Name;
msg 
+= ". Today is";
msg 
+= System.DateTime.Now.Tostring();

      上例中的做法等同于下面的代码:

string msg = "Hello,";
//这样声明是非法的,仅示例
string temp1 = new String(msg + thisUser.Name);
msg 
= temp1;
string temp2 = new String(msg + ". Today is";);
msg 
= temp2;
string temp3 = new String(msg + System.DateTime.Now.ToString());
msg 
= temp3;

      tmp1,tmp2,tmp3和最初创建的msg都便成了垃圾。+=操作在string类上会创建一个新的string进行修改,而并不是修改已经存在的对象。我们可以使用Format()方法来创建string:

string msg = string.Format("Hello, {0}.Today is {1}", thisUser.Name, DateTime.Now.ToString());

      对于更为复杂的string的创建,我们可以使用StringBuilder类:

StringBuilder msg = new StringBuilder("Hello, ");
msg.Append(thisUser.Name);
msg.Append(
". Today is ");
msg.Append(DateTime.Now.ToString());
string finalMsg = msg.ToString();

      StringBuilder是一个可变类型,它可以创建不可变的string类型。它提供了方便修改的可变字符串来让我们进行创建和修改工作。更重要的是,它可以为我们提供一些类的设计上的思路。当我们创建一个不可变类型时,也应当考虑为其创建一个构造器来完成频繁修改的工作并返回目标对象。

      GC虽然工作效率很高,但是创建和销毁堆上的对象依然要消耗时间。我们应当避免创建无用的对象,避免创建大量重复的对象。最后还应当注意不可变类型的创建和修改。

      译自   Effective C#:50 Specific Ways to Improve Your C#                      Bill Wagner著

      回到目录

posted on 2006-10-20 09:19  aiya  阅读(1262)  评论(1编辑  收藏  举报