《Effective C#》读书笔记——条目16:避免创建非必要的对象<.NET资源管理>

  我们知道:C#是一门虚拟机语言,C#编译器首先将C#代码编译成IL代码,运行程序时CLR(Common Language Runtime,公共语言运行时)通过调用JIT(just-in-time Compiler即时编译器)来将IL代动态即时编译成可执行的机器码。GC(Garbage Collector,垃圾收集器)自动为我们的应用程序进行内存管理的分配和释放,(具体参见:了解.NET 内存管理机制),以一种高效的方式来移除内存中的垃圾对象,不过不管有多高效,分配和销毁在堆上的对象总会花费掉时间。

  如果我们在一个方法中创建了过多的引用对象,会对应用程序的性能产生严重的影响。因此我们应该遵守下面的一些规则,可以尽量的降低GC的工作量。

 

阅读目录:

      1.将常用的局部变量提升为成员变量

      2.为常用的类型实例提供静态对象

      3.为不可变类型提供可变的创建对象

      小节

 

1.将常用的局部变量提升为成员变量

   所有的引用类型,包括那些局部变量,都会分配到堆上。在函数退出后,函数内的所有局部变量都会立即变成垃圾对象。所以我们可以得出结论:

若是某个引用类型(值类型无所谓)的局部变量用于被频繁调用的例程中,那么应该将其提升为成员变量。这既有助于减轻GC的负担,也可以提升程序运行的效率。

 在GUI编程中一个常见的错误就是:在窗体的Paint处理函数中创建GDI(Graphics Device Interface,图形设备接口)对象,如下:

1         protected override void OnPaint(PaintEventArgs e)
2         {
3             using (Font myFont = new Font("Arial", 10.0f))
4             {
5                 e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new Point(0, 0));
6             }
7             base.OnPaint(e);
8         }

 

OnPaint()将会被非常频繁的调用,每次调用都会创建一个Font对象,而包含的内容完全和上一次一样。所以GC需要每次都为你清扫这些垃圾,严重影响了应用程序的效率。其实我们完全可以将Font对象提升为成员变量,是每次窗体重绘时能够重用该Font对象:

1         private readonly Font myFont = new Font("Arial", 10.0f);
2 
3         protected override void OnPaint(PaintEventArgs e)
4         {
5                 e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new Point(0, 0));
6 
7                 base.OnPaint(e);
8         }

 将常用的局部变量提升为成员变量之后,程序无需每次重绘时生成垃圾对象,减轻了GC的负担,也提升了程序的效率。不过这里有一个小小的限制:将实现了IDisposable接口的局部变量提升为成员变量,那么这个成员变量依附类本身也需要实现IDisposable接口。

 

2.为常用的类型实例提供静态对象

  静态成员变量可以让引用类型在类的各个实例中共享。我们可以通过提供了一个类,存放某个类型常用的实例的单例对象,这样可以避免创建重复的对象。.NET Framework 的类库中就有很多这样的做法:Brushes类包含了一系列的静态Brush对象,每个都包含了一种常用的颜色。它们的简要实现如下:

 1         private static Brush blackBrush;
 2         public static Brush Black
 3         {
 4             get
 5             {
 6                 if (blackBrush == null)
 7                     blackBrush = new SolidBrush(Color.Black);
 8                 return blackBrush;
 9             }
10         }

在第一次请求黑色画刷时,Brushes将创建一个实例,随意Brushes类将保留该实例的引用,并在后续的请求时直接返回同一个句柄。也就是说我们只创建了一个黑色画刷,然后一直重用这个对象。至于其他的例如:红色的画刷,如果应用程序没有使用这个资源那么该对象也不会被分配。.NET 提供的这种方式能够在满足需求的前提下尽可能的少创建对象,我们在自己的应用程序中也应该这样做。

 

3.为不可变类型提供可变的创建对象

  System.String类型时一个不可变类型:即在构造一个字符串对象后,其内容不能被修改。如果对一个字符串进行修改时,实际上时创建了一个新的字符串对象,从前的字符串对象也就变成了垃圾。看下面的代码:

1             //How are you?
2             string msg = "How";
3             msg += " are";
4             msg += " you";
5             msg += "?";

 

string类型的+=操作符会创建一个新的字符串对象并返回,对于这类拼接字符串的工作应该交给更适合的string.Format()方法:

1 string msg = string.Format("{0} {1} {2}{3}", "How", "are", "you", "?");

 

如果需要进行一些比简单拼接字符串更加复杂的工作,可以考虑使用StringBuilder类,该类是一个可变的字符串,用来创建不可变的string对象。对于经常变化的stirng对象,使用StringBuilder对象来替换是一个非常好的选择 —— 当我们的某个设计需要不可变类型时,应该考虑提供一个创建对象,专门负责分步地构造出最终对象(例如:string对象之于StringBuilder对象)。这样既可让用户分步地创建出最终对象,也可以保证对象的不可变性

 

小节

  GC可以高效的管理应用程序使用的内存,不过创建和销毁堆上的对象仍旧需要时间,因此,应该避免创建过多的对象,不要创建那些非必要的对象,也不要在局部方法中创建太多的引用对象,可以考虑将常用的局部变量提升为成员变量,或者为最常用的类型实例提供静态对象,此外还可以考虑为不可变类型提供可变的创建对象。

posted @ 2012-11-27 09:03  gyzhao  阅读(1799)  评论(9编辑  收藏