系列二:资源管理(利用using,try/finally,减少内存垃圾)
1.我们常使用IDisposable接口的Dispose()方法来精确的释放非托管系统资源。在日常代码中我们确保调用Dispose()的方法是用using和try/catch/finally语句。
2.所有包含非托管资源的类型都会实现IDisposable接口,他们还会创建终结器,以防止我们忘记调用Dispose()。如果你忘记调用Dispose(),那些非内存资源会在晚些时候,终结器会调用时会释放这些资源。这就使得这些对象在内存时待的时间更长,从而会使你的应用程序会因系统资源占用太多而速度下降。
3.代码展示:
public void ExecuteCommand( string connString,string commandString )
{
SqlConnection myConnection = new SqlConnection( connString );
SqlCommand mySqlCommand = new SqlCommand( commandString,myConnection );
myConnection.Open();
mySqlCommand.ExecuteNonQuery();
}
这个例子中的两个可处理对象没有被恰当的释放:SqlConnection和SqlCommand。两个对象同时保存在内存里直到析构函数被调用。(这两个类都是从System.ComponentModel.Component继承来的。) 解决这个问题的方法就是在使用完命令和链接后就调用它们的Dispose:
public void ExecuteCommand( string connString,string commandString )
{
SqlConnection myConnection = new SqlConnection( connString );
SqlCommand mySqlCommand = new SqlCommand( commandString,myConnection );
myConnection.Open();
mySqlCommand.ExecuteNonQuery();
mySqlCommand.Dispose( );
myConnection.Dispose( );
}
但如果SQL命令在执行时抛出异常,这时你的Dispose()调用就永远不会成功。using语句可以确保Dispose()方法被调用。当你把对象分配到using语句内时,C#的编译器就把这些对象放到一个try/finally块内:
public void ExecuteCommand( string connString,string commandString )
{
using ( SqlConnection myConnection = new SqlConnection( connString ))
{
using ( SqlCommand mySqlCommand = new SqlCommand( commandString, myConnection ))
{
myConnection.Open();
mySqlCommand.ExecuteNonQuery();
}
}
}
当你在一个函数内使用一个可处理对象时,using语句是最简单的方法来保证这个对象被恰当的处理掉。当这些对象被分配时,会被编译器放到一个try/finally块中。下面的两段代码编译成的IL是一样的:
SqlConnection myConnection = null;// Example Using clause:
using ( myConnection = new SqlConnection( connString ))
{
myConnection.Open();
}
// example Try / Catch block:
try {
myConnection = new SqlConnection( connString );
myConnection.Open();
}
finally {
myConnection.Dispose( );
}
4.我们不能对一个没有实现IDispose接口的对象使用using方式,编译器会报错。
5.每一个using语句生成了一个新的嵌套的try/finally块。我发现这是很糟糕的结构,所以,如果是遇到多个实现了IDisposable接口的对象时,我更愿意写自己的try/finally块:
public void ExecuteCommand( string connString,string commandString )
{
SqlConnection myConnection = null;
SqlCommand mySqlCommand = null;
try {
myConnection = new SqlConnection( connString );
mySqlCommand = new SqlCommand( commandString,myConnection );
myConnection.Open();
mySqlCommand.ExecuteNonQuery();
}
finally
{
if ( mySqlCommand != null )
mySqlCommand.Dispose();
if ( myConnection != null )
myConnection.Dispose();
}
}
6.有一些对象同时支持Disponse和Close两个方法来释放资源。SqlConnection就是其中之一,你可以像这样关闭
public void ExecuteCommand( string connString,string commandString )
{
SqlConnection myConnection = null;
try {
myConnection = new SqlConnection( connString );
SqlCommand mySqlCommand = new SqlCommand( commandString,myConnection );
myConnection.Open();
mySqlCommand.ExecuteNonQuery();
}
finally
{
if (myConnection != null )
myConnection.Close();
}
}
这个版本关闭了链接,但它却是与处理对象是不一样的。Dispose方法会释放更多的资源,它还会告诉GC,这个对象已经不再须要析构了(注:这个对象本身并没有删除出内存)。它是通过调用GC.SuppressFinalize()来实现这一点的。但Close()一般不会,因此调用了close()的对象仍然留在终结列表中,虽然这个时候不需要终结器的操作了。当你有选择时,Dispose()比Colse()要好。
7.我们应该对经常出现的局部变量提升为类的成员变量(当在一个方法中经常调用一个变量时候,方法调用结束后,垃圾收集器就会去处理这个变量,每调用一次垃圾收集器就要增加额外的开销),对那些是非托管资源的变量升级为成员变量我们还应该把类设计成继承IDisposable接口,在接口方法中释放非托管资源。下面举例说明:
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( 0,0 ));
}
base.OnPaint( e );
}
OnPaint()函数的调用很频繁的,每次调用它的时候,都会生成另一个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 PointF( 0,0 ));
base.OnPaint( e );
}
8.所有的引用类型(值类型没关系),即使是局部变量都分配在堆上。当垃圾回收器回收堆上的对象时候会花费而外的时间,随着函数的退出,引用类型的局部变量就会变成垃圾。这里强调的是在频繁的调用的时候,如果调用的很少我们没有必要把局部变量升级为成员变量。
9.前面例子中使用的静态属性Brushes.Black,演示了另一个避免重复创建相似对象的技术。使用静态成员变量来创建一些常用的引用类型的实例。考虑前面那个例子里使用的黑色画刷,每次当你要用黑色画刷来画一些东西时,你要在程序中创建和释放大量的黑色画刷。前面的一个解决方案就是在每个期望黑色画刷的类中添加一个画刷成员,但这还不够。程序可能会创建大量的窗口和控件,这同样会创建大量的黑色画刷。.Net框架的设计者预知了这个问题,他们为你创建一个简单的黑色画刷以便你在任何地方都可以重复使用。(也就是设计模式中的单例模式)
private static Brush _blackBrush;//类的静态成员
public static Brush Black//静态属性
{
get
{
if ( _blackBrush == null )//这里也可以用于多线程的处理
_blackBrush = new SolidBrush( Color.Black );
return _blackBrush;
}
}
10.System.String类就是一个恒定类型,在你创建一个字符串后,它的内容就不能更改了。当你编写代码来修改这些串的内容时,你实际上是创建了新的对象,并且让旧的串成为了垃圾。
string msg = "Hello, ";
msg += thisUser.Name;
msg += ". Today is ";
msg += System.DateTime.Now.ToString();
字符串tmp1,tmp2,tmp3以及最原始的msg构造的(“Hello”),都成了垃圾。+=方法在字符串类上会生成一个新的对象并返回它。对于更多的复杂的字符串操作,你应该使用StringBuilter类。通过StringBuilter学到了一种思想:一些要经过多次构造后才能最终得到的对象,可以考虑使用一些对象生成器来简化对象的创建。它提供了一个方法让你的用户来逐步的创建(你设计的)恒定类型,也用于维护这个类型。
11.初始化常量类型通常有三种策略,选择哪一种策略依赖于一个类型的复杂度。定义一组合适的构造器通常是最简单的策略。也可以创建一个工厂方法(factory method)来进行初始化工作。这种方式对于创建一些常用的值比较方便。.NET框架中的Color类型就采用了这种策略来初始化系统颜色。例如,静态方法Color.FromKnownColor()和Color.FromName()可以根据一个指定的系统颜色名,来返回一个对应的颜色值。最后,对于需要多个步骤操作才能完整构造出一个常量类型的情况,我们可以通过创建一个可变的辅助类来解决。.NET中的String类就采用了这种策略,其辅助类为System.Text.StringBuilder。我们可以使用StringBuilder类通过多步操作来创建一个String对象。