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

本文转自gyzhao博客园博客,原文链接:http://www.cnblogs.com/IPrograming/archive/2012/11/27/EffectiveCSharp_16.html ,如需转载请自行联系原作者
相关文章
|
Web App开发 C# Windows
一款.NET开源的Windows资源管理器标签页工具
一款.NET开源的Windows资源管理器标签页工具
341 5
《More Effective C# 》读书笔记 第一章
《More Effective C# 》读书笔记 第一章
252 0
|
缓存 算法 Java
《深入理解Java虚拟机》读书笔记(四)--GC的回收条件及Java对象的引用
《深入理解Java虚拟机》读书笔记(四)--GC的回收条件及Java对象的引用
444 0
|
API C++
读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问
1.为什么需要访问资源管理类中的原生资源  资源管理类是很奇妙的。它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征。在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不要直接访问原生(raw)资源而玷污你的双手。
1170 0
|
C++
读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎
1. 自己实现一个资源管理类  Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如何用堆资源来表现这个概念的。
1247 0
|
C++ 容器
读书笔记 effective c++ Item 13 用对象来管理资源
1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, 1 class Investment { .
1205 0
|
C++ 编译器 安全
读书笔记 effective c++ Item 12 拷贝对象的所有部分
1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:拷贝构造函数和拷贝赋值运算符。我们把这两个函数统一叫做拷贝函数。从Item5中,我们得知,如果需要的话编译器会为你生成这两个拷贝函数,并且编译器生成的版本能够精确的做到你想做的:它们拷贝了对象的所有数据。
1005 0
|
C++ 安全 C#
读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
1.关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样。如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为这是c++同它们不一样的地方。
1093 0
|
安全 C++ Python
读书笔记 effective c++ Item 11 在operator=中处理自我赋值
1.自我赋值是如何发生的 当一个对象委派给自己的时候,自我赋值就会发生: 1 class Widget { ... }; 2 3 Widget w; 4 5 ... 6 7 w = w; // assignment to self、 这看上去是愚蠢的,但这是合法的,所以请放心,客户端是可以这么做的。
1197 0
|
C++ 容器 数据库连接
读书笔记 effective c++ Item 8 不要让异常(exceptions)离开析构函数
1.为什么c++不喜欢析构函数抛出异常 C++并没有禁止析构函数出现异常,但是它肯定不鼓励这么做。这是有原因的,考虑下面的代码: 1 class Widget { 2 3 public: 4 5 .
1034 0