《Effective C#》读书笔记——条目16:避免创建非必要的对象<.NET资源管理>
2012-11-27 09:03
141 查看
我们知道:C#是一门虚拟机语言,C#编译器首先将C#代码编译成IL代码,运行程序时CLR(Common Language Runtime,公共语言运行时)通过调用JIT(just-in-time Compiler,即时编译器)来将IL代动态即时编译成可执行的机器码。GC(Garbage Collector,垃圾收集器)自动为我们的应用程序进行内存管理的分配和释放,(具体参见:了解.NET 内存管理机制),以一种高效的方式来移除内存中的垃圾对象,不过不管有多高效,分配和销毁在堆上的对象总会花费掉时间。
如果我们在一个方法中创建了过多的引用对象,会对应用程序的性能产生严重的影响。因此我们应该遵守下面的一些规则,可以尽量的降低GC的工作量。
阅读目录:
1.将常用的局部变量提升为成员变量
2.为常用的类型实例提供静态对象
3.为不可变类型提供可变的创建对象
小节
若是某个引用类型(值类型无所谓)的局部变量用于被频繁调用的例程中,那么应该将其提升为成员变量。这既有助于减轻GC的负担,也可以提升程序运行的效率。
在GUI编程中一个常见的错误就是:在窗体的Paint处理函数中创建GDI(Graphics Device Interface,图形设备接口)对象,如下:
OnPaint()将会被非常频繁的调用,每次调用都会创建一个Font对象,而包含的内容完全和上一次一样。所以GC需要每次都为你清扫这些垃圾,严重影响了应用程序的效率。其实我们完全可以将Font对象提升为成员变量,是每次窗体重绘时能够重用该Font对象:
将常用的局部变量提升为成员变量之后,程序无需每次重绘时生成垃圾对象,减轻了GC的负担,也提升了程序的效率。不过这里有一个小小的限制:将实现了IDisposable接口的局部变量提升为成员变量,那么这个成员变量依附类本身也需要实现IDisposable接口。
在第一次请求黑色画刷时,Brushes将创建一个实例,随意Brushes类将保留该实例的引用,并在后续的请求时直接返回同一个句柄。也就是说我们只创建了一个黑色画刷,然后一直重用这个对象。至于其他的例如:红色的画刷,如果应用程序没有使用这个资源那么该对象也不会被分配。.NET 提供的这种方式能够在满足需求的前提下尽可能的少创建对象,我们在自己的应用程序中也应该这样做。
string类型的+=操作符会创建一个新的字符串对象并返回,对于这类拼接字符串的工作应该交给更适合的string.Format()方法:
如果需要进行一些比简单拼接字符串更加复杂的工作,可以考虑使用StringBuilder类,该类是一个可变的字符串,用来创建不可变的string对象。对于经常变化的stirng对象,使用StringBuilder对象来替换是一个非常好的选择 —— 当我们的某个设计需要不可变类型时,应该考虑提供一个创建对象,专门负责分步地构造出最终对象(例如:string对象之于StringBuilder对象)。这样既可让用户分步地创建出最终对象,也可以保证对象的不可变性。
GC可以高效的管理应用程序使用的内存,不过创建和销毁堆上的对象仍旧需要时间,因此,应该避免创建过多的对象,不要创建那些非必要的对象,也不要在局部方法中创建太多的引用对象,可以考虑将常用的局部变量提升为成员变量,或者为最常用的类型实例提供静态对象,此外还可以考虑为不可变类型提供可变的创建对象。
如果我们在一个方法中创建了过多的引用对象,会对应用程序的性能产生严重的影响。因此我们应该遵守下面的一些规则,可以尽量的降低GC的工作量。
阅读目录:
1.将常用的局部变量提升为成员变量
2.为常用的类型实例提供静态对象
3.为不可变类型提供可变的创建对象
小节
1.将常用的局部变量提升为成员变量
所有的引用类型,包括那些局部变量,都会分配到堆上。在函数退出后,函数内的所有局部变量都会立即变成垃圾对象。所以我们可以得出结论:若是某个引用类型(值类型无所谓)的局部变量用于被频繁调用的例程中,那么应该将其提升为成员变量。这既有助于减轻GC的负担,也可以提升程序运行的效率。
在GUI编程中一个常见的错误就是:在窗体的Paint处理函数中创建GDI(Graphics Device Interface,图形设备接口)对象,如下:
protected override void OnPaint(PaintEventArgs e) { using (Font myFont = new Font("Arial", 10.0f)) { e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new Point(0, 0)); } base.OnPaint(e); }
OnPaint()将会被非常频繁的调用,每次调用都会创建一个Font对象,而包含的内容完全和上一次一样。所以GC需要每次都为你清扫这些垃圾,严重影响了应用程序的效率。其实我们完全可以将Font对象提升为成员变量,是每次窗体重绘时能够重用该Font对象:
private readonly Font myFont = new Font("Arial", 10.0f); protected override void OnPaint(PaintEventArgs e) { e.Graphics.DrawString(DateTime.Now.ToString(), myFont, Brushes.Black, new Point(0, 0)); base.OnPaint(e); }
将常用的局部变量提升为成员变量之后,程序无需每次重绘时生成垃圾对象,减轻了GC的负担,也提升了程序的效率。不过这里有一个小小的限制:将实现了IDisposable接口的局部变量提升为成员变量,那么这个成员变量依附类本身也需要实现IDisposable接口。
2.为常用的类型实例提供静态对象
静态成员变量可以让引用类型在类的各个实例中共享。我们可以通过提供了一个类,存放某个类型常用的实例的单例对象,这样可以避免创建重复的对象。.NET Framework 的类库中就有很多这样的做法:Brushes类包含了一系列的静态Brush对象,每个都包含了一种常用的颜色。它们的简要实现如下:private static Brush blackBrush; public static Brush Black { get { if (blackBrush == null) blackBrush = new SolidBrush(Color.Black); return blackBrush; } }
在第一次请求黑色画刷时,Brushes将创建一个实例,随意Brushes类将保留该实例的引用,并在后续的请求时直接返回同一个句柄。也就是说我们只创建了一个黑色画刷,然后一直重用这个对象。至于其他的例如:红色的画刷,如果应用程序没有使用这个资源那么该对象也不会被分配。.NET 提供的这种方式能够在满足需求的前提下尽可能的少创建对象,我们在自己的应用程序中也应该这样做。
3.为不可变类型提供可变的创建对象
System.String类型时一个不可变类型:即在构造一个字符串对象后,其内容不能被修改。如果对一个字符串进行修改时,实际上时创建了一个新的字符串对象,从前的字符串对象也就变成了垃圾。看下面的代码://How are you? string msg = "How"; msg += " are"; msg += " you"; msg += "?";
string类型的+=操作符会创建一个新的字符串对象并返回,对于这类拼接字符串的工作应该交给更适合的string.Format()方法:
string msg = string.Format("{0} {1} {2}{3}", "How", "are", "you", "?");
如果需要进行一些比简单拼接字符串更加复杂的工作,可以考虑使用StringBuilder类,该类是一个可变的字符串,用来创建不可变的string对象。对于经常变化的stirng对象,使用StringBuilder对象来替换是一个非常好的选择 —— 当我们的某个设计需要不可变类型时,应该考虑提供一个创建对象,专门负责分步地构造出最终对象(例如:string对象之于StringBuilder对象)。这样既可让用户分步地创建出最终对象,也可以保证对象的不可变性。
小节
GC可以高效的管理应用程序使用的内存,不过创建和销毁堆上的对象仍旧需要时间,因此,应该避免创建过多的对象,不要创建那些非必要的对象,也不要在局部方法中创建太多的引用对象,可以考虑将常用的局部变量提升为成员变量,或者为最常用的类型实例提供静态对象,此外还可以考虑为不可变类型提供可变的创建对象。
相关文章推荐
- 《Effective C#》读书笔记——条目26:避免返回对内部类对象的引用<使用C#表达设计>
- 《Effective C#》读书笔记——条目20:保证值类型的常量性和原子性<.NET资源管理>
- 《Effective C#》读书笔记——条目18:区分值类型和引用类型<.NET资源管理>
- 《Effective C#》读书笔记——条目19:保证0为值类型的有效状态<.NET资源管理>
- 《Effective C#》读书笔记——条目17:实现标准的销毁模式<.NET资源管理>
- 《Effective C#》读书笔记——条目14:尽量减少重复的初始化逻辑<.NET资源管理>
- 转:《Effective C#》读书笔记——条目17:实现标准的销毁模式<.NET资源管理>
- 《Effective C#》读书笔记——条目27:让类型支持序列化<使用C#表达设计>
- 《Effective C#》读书笔记——条目25:用事件模式实现通知<使用C#表达设计>
- 《Effective C#》读书笔记——条目23:理解接口方法和虚方法的区别<使用C#表达设计>
- Effective Java 3rd 条目6 避免创建不必要的对象
- Effective Java 读书笔记(五):Java避免创建不必要的对象
- 《Effective C#》读书笔记——条目28:提供粗粒度的互联网API<使用C#表达设计>
- 避免创建非必要的对象
- 《Effective C#》读书笔记——条目13:正确地初始化静态成员变量<.NET资源管理>
- <读书笔记>JavaScript系列之7种创建对象(面向对象)
- 《Effective Java》读书笔记04--避免创建不必要的对象
- 《Effective C#》读书笔记——条目2:用运行时常量而不是编译期常量<C#语言习惯>
- 《Effective C#》读书笔记——条目12:推荐使用成员初始化器而不是赋值语句<.NET资源管理>
- 《Effective C#》读书笔记——条目1:使用属性而不是可访问的数据成员<C#语言习惯>