`
isiqi
  • 浏览: 16484339 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

c#是如何释放资源的

阅读更多

Effective C# Item 15: Utilize using and try/finally for Resource Cleanup

当我们使用非托管资源(unmanaged resources)类型时,应当使用IDisposable接口的Dispose()方法来释放资源。在.Net环境中,对非托管资源的回收不是系统的责任,我们必须自己调用Dispose()方法来释放资源。确保非托管资源会释放的最好方法是使用using或者try/finally。

所有的非托管资源类型都实现了IDisposable接口。另外当我们没有明确的释放资源,比如我们忘记了,C#还会防护性的通过创建终结器(finalizer)来释放资源。如果我们希望应用程序运行的更快时,就应当尽快释放一些不必要的资源。幸运的是在C#中有新的关键字来完成这项任务。我们先考虑下面的代码:

public void ExcuteCommand(string connectString, string commandString)
{
SqlConnection myConnection = new SqlConnection(connectString);
SqlCommand myCommand = new SqlCommand(commandString, myConnection);
myConnection.Open();
myCommand.ExecuteNonQuery();
}
有两个对象没有被释放掉:SqlConnection和SqlCommand。它们都会保存在内存中直到终结器被调用为止。

通过下面的修改,我们可以释放它们:

public void ExcuteCommand(string connectString, string commandString)
{
SqlConnection myConnection = new SqlConnection(connectString);
SqlCommand myCommand = new SqlCommand(commandString, myConnection);
myConnection.Open();
myCommand.ExecuteNonQuery();

myCommand.Dispose();
myConnection.Dispose();
}

这样做是正确的,但前提是SqlCommand没有抛出异常。一旦出现异常,我们的Dispose()方法就不会运行了。using关键字可以帮助我们确保Dispose()会被运行。当我们使用using的时候,C#的编译器会将它转换成为类似与try/finally的形式:

public void ExcuteCommand(string connectString, string commandString)
{
using(SqlConnection myConnection = new SqlConnection(connectString))
{
using(SqlCommand myCommand = new SqlCommand(commandString, myConnection))
{
myConnection.Open();
myCommand.ExecuteNonQuery();
}
}
}

下例中的两段代码会生成非常相似的IL

using(SqlConnection myConnection = new SqlConnection(connectString))
{
myConnection.Open();
}

try
{
SqlConnection myConnection = new SqlConnection(connectString);
myConnection.Open();
}
finally
{
myConnection.Dispose();
}

当我们使用非托管资源时,使用using是确保资源合理释放的简单途径。如果我们对不支持IDisposable接口的类型使用using关键字,编译器会报错:

//错误
using(string msg = "this is a message")
{
Console.WriteLine(msg);
}
另外using只检验编译时类型是否支持IDisposable接口,它不能识别运行时的对象。下例中即便Factory.CreateResource()返回的类型支持IDisposable接口也是不能通过编译的:

//错误
using(object obj = Factory.CreateResource)
{
}

对于可能支持可能不支持IDisposable接口的对象,我们可以这样来处理:

object obj = Factory.CreateResource();
using(obj as IDisposable)
{
}

如果对象实现了IDisposable接口,就可以生成释放资源的代码。如果不支持,则生成using(null),虽然不做任何工作,但也是安全的。如果我们拿不准是否应该将对象放在using中,那么比较稳妥的做法是将它放进去。

当我们在程序中使用了非托管资源类型时,我们应当将其放入using的括号中。当有多个需要释放的资源,例如前面的例子中的connection和command,我们应当创建多个using,每一个包含一个对应的对象。这些using会被转化为不同的try/finally块,在效果上看就好像是下面这段代码:

public void ExcuteCommand(string connectString, string commandString)
{
SqlConnection myConnection = null;
SqlCommand myCommand = null;
try
{
myConnection = new SqlConnection(connectString);
try
{
myCommand = new SqlCommand(commandString, myConnection);
myConnection.Open();
myCommand.ExecuteNonQuery();
}
finally
{
if(myCommand != null)
{
myCommand.Dispose();
}
}
}
finally
{
if(myConnection != null)
{
myConnection.Dispose();
}
}
}
每个using声明都创建了一个try/finally程序块。我们自己也可以通过这样写来取消多层嵌套:

public void ExcuteCommand(string connectString, string commandString)
{
SqlConnection myConnection = null;
SqlCommand myCommand = null;
try
{
myConnection = new SqlConnection(connectString);
myCommand = new SqlCommand(commandString, myConnection);

myConnection.Open();
myCommand.ExecuteNonQuery();
}
finally
{
if(myCommand != null)
{
myCommand.Dispose();
}
if(myConnection != null)
{
myConnection.Dispose();
}
}
}

虽然看起来很简洁,但是我们不要这样使用using声明:

public void ExcuteCommand(string connectString, string commandString)
{
SqlConnection myConnection = new SqlConnection(connectString);
SqlCommand myCommand = new SqlCommand(commandString, myConnection);
using(myConnection as IDisposable)
{
using(myCommand as IDisposable)
{
myConnection.Open();
myCommand.ExecuteNonQuery();
}
}
}

这样做是有潜在bug的。一旦SqlCommand的构造函数抛出异常,SqlConnection就无法被释放了。我们必须保证每个非托管资源对象都可以被顺利的释放,否则可能会造成内存资源的浪费。对于单个的非托管资源对象,使用using关键字是最好的方法。对于多个对象,我们可以使用嵌套using或者自己写try/finally的方法来释放资源。

在释放资源上还有一个细节,有些类型不仅有Dispose()方法,还有Close()方法,例如SqlConnection。我们这样可以关闭SqlConnection的连接:

myConnection.Close();
这样做的确能够释放连接,但是并不是和Dispose()方法一样。Dispose()方法除了释放资源之外,还有其他的工作:它会通知垃圾收集器(Garbage Collector)这个对象的资源已经被释放了,而不必在终结器中进行重复的操作。Dispose()调用了GC.SuppressFinalize()方法,这个方法请求系统不要调用指定对象的终结器。而Close()不是这样的。使用Close()释放的对象虽然已经不必调用终结器,但是它还是存在于终结器的释放资源队列当中。Dispose()比Close()的工作做的更加彻底。

Dispose()并没有将对象移出内存。它为对象添加了一个释放资源的钩子(hook)。这就意味着我们可以对正在使用的对象使用Dispose(),我们应当小心这一点。

在C#中大部分的类型都不支持Dispose()。在超过1500种类型中只有100来种实现了IDispose接口。当我们使用实现了这个接口的对象时,我们应当在适当的时候使用using或try/finally块的方法来释放资源。

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

回到目录

P.S. 以下为个人想法:

Dispose()和Close()都是可以显示调用来释放资源。而Finalize()是受保护的,析构函数更是不知道什么时候会被执行。被Close()掉的对象是有可能再次复活的,比如SqlConnection,还可以通过Open()继续使用,好像是休眠状态。而被Dispose()掉的对象从理论上来说是不应当再复活了,因为我们在Dispose的同时应当告诉GC这个对象已经over了,以避免重复的对象清除工作。当然我们可以通过释放资源时获得其引用的诡异方法来继续访问对象,但是由于Dispose()调用GC.SuppressFinalize()方法免除终结,因此这些Dispose()掉又复活的对象的资源就再也不会自动被GC释放了。

public class DisposeClass : IDisposable
{
private string insideResources;

public void UseInsideResources()
{
insideResources = "use resouces";
Console.WriteLine(insideResources);
}

public DisposeClass()
{
Console.WriteLine("create object");
}

~DisposeClass()
{
Console.WriteLine("destroy~ object");
}

IDisposable 成员#region IDisposable 成员

public void Dispose()
{
// TODO: 添加 DisposeClass.Dispose 实现
Console.WriteLine("dispose object");
GC.SuppressFinalize( this );
}

#endregion
}
上例中的类在使用Dispose()释放资源后是不会调用析构函数的,没有调用Dispose()时才会在需要终结时调用析构函数。但是如果我们这样使用它:

static void Main(string[] args)
{
ArrayList myList = new ArrayList();
using(DisposeClass a = new DisposeClass())
{
myList.Add(a);
}
((DisposeClass)(myList[0])).UseInsideResources();
Console.ReadLine();
}
已经被Dispose掉的对象又复活了。而且析构函数不会再被调用。我们同样可以在析构函数中做这种事,只是我们不知道它什么时候会发生而已。

Finalize()是一个神奇的函数,在没有析构函数时它可以像诸如abc()一样做为普通的成员函数,但是一旦析构函数存在,就会报出“已经存在名为Finalize()成员”的错误。更匪夷所思的是当我们将其声明为保护成员函数(开始时我将其声明为public的,不会出现特殊的结果,后来才改成protected)时,它就会被其派生类的析构函数调用。在C#中,派生类的析构函数会自动调用基类的析构函数,因此在这里派生类看来已经把它当作是基类的析构函数了。在编译器中应该将它同析构函数划了等号了吧,可是object.Finalize()明明是不能重写的。对于protected void Finalize()来说,它到底算是个什么东西呢?这应该是在编译阶段就可以发现的错误吧?代码如下:

static void Main(string[] args)
{
ExtendDisposeClass a = new ExtendDisposeClass();
Console.ReadLine();
}

public class DisposeClass : IDisposable
{
public DisposeClass()
{
Console.WriteLine("create object");
}

// ~DisposeClass()
// {
// Console.WriteLine("destroy~ object");
// }

protected void Finalize()
{
Console.WriteLine("finalize object");
}

IDisposable 成员#region IDisposable 成员

public void Dispose()
{
// TODO: 添加 DisposeClass.Dispose 实现
Console.WriteLine("dispose object");
}

#endregion
}

public class ExtendDisposeClass : DisposeClass
{
~ExtendDisposeClass()
{
Console.WriteLine("destroyExtendDisposeClass object");
}
}
释放个资源可真是不简单啊...


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/orichisonic/archive/2007/06/25/1665591.aspx

分享到:
评论

相关推荐

    C#内存释放-线程控制-线程启动-线程暂停

    此外,可以使用`using`语句或`Dispose`模式来确保资源(如文件流或数据库连接)在使用完毕后得到正确释放。 在多线程环境中,内存管理变得更为复杂。线程间的共享数据可能导致竞态条件,这可能会导致意外的行为。...

    C# winform 运行前释放令一个文件(或DLL)

    总之,C# WinForm程序中通过运行前释放DLL,可以实现动态加载、优化资源使用、提高安全性等多种目标。不过,这也需要开发者对.NET框架、程序部署和异常处理有深入的理解,以便在实际操作中避免潜在的问题。

    C#资源释放方法实例分析

    `using`语句是C#中释放资源的一种便捷方式,它适用于实现了`IDisposable`接口的对象。`IDisposable`接口有一个`Dispose()`方法,用于释放非托管资源。当使用`using`语句创建对象时,C#编译器会自动生成对应的`try-...

    C# 简单秒表 不要资源分

    标题 "C# 简单秒表 不要资源分" 描述的是一个使用C#编程语言实现的秒表程序,该程序能够在运行环境为.NET Framework 2.0或更高版本的系统上正常运行。秒表功能是软件开发中常见的实用工具,通常用于测量代码执行时间...

    c#中的非托管资源释放 (Finalize和Dispose)

    - 如果实现了`Finalize`方法,则应该在`Dispose`方法中调用`GC.SuppressFinalize(this)`来抑制`Finalize`方法的执行,以避免重复释放资源。 - `Dispose`方法应该能够释放所有非托管资源。 - 当`Dispose`方法完成...

    C# Winform windows运行内存释放

    本项目“C# Winform Windows运行内存释放”旨在解决计算机运行过程中内存占用过高导致的性能问题。通过编写这样的小程序,我们可以帮助用户管理他们的系统资源,提高计算机的响应速度和整体性能。 在Windows操作...

    C# Com组件释放案件

    在.NET框架中,C#与COM(Component Object Model)组件的交互是常见的跨技术平台通信方式。...5. 在必要时,触发垃圾收集以辅助释放资源。 了解这些知识点,将有助于你在实际开发中更好地管理和使用C#中的COM组件。

    C# DirectX 使用例子

    这是一个C# 使用DirectX的例子,它根据配置文件,创建一个M*N的地图(用BOX表示每格)。可以创建随机障碍块,并且计算A\B两点路径。其中包含BOX的绘画、纹理设置、摄像机移动与旋转。详细见博客:...

    C#资源管理器 资源管理器是一项系统服务

    5. **智能指针**:C#的`using`语句是另一种资源管理机制,它自动调用`Dispose`方法来释放资源,确保在代码块结束时清理资源。 6. **内存管理**:C#使用垃圾收集(Garbage Collection,GC)来自动管理内存。尽管...

    C#资源管理器 C#源码编写 很O

    6. 资源管理:在C#中,非托管资源(如文件句柄)需要手动释放,而托管资源(如.NET对象)则由垃圾回收器自动管理。了解何时使用using语句或IDisposable接口是确保资源有效管理的关键。 7. UI设计:良好的用户界面...

    C# 从资源中读取wav

    最后关闭流以释放资源。 4. **播放声音**: ```csharp SoundPlayer simpleSound = new SoundPlayer("Sound.wav"); simpleSound.Play(); ``` 最后,通过`SoundPlayer`类播放之前创建的WAV文件。`SoundPlayer`类...

    C#资源管理器 适合初学者

    5. **异常处理**:在资源管理中,异常处理是非常重要的,以确保在出现错误时能正确释放资源,防止资源泄露。C#提供了`try-catch-finally`语句块来捕获和处理异常,`finally`块通常用于确保资源的清理。 6. ** ...

    探讨C#中Dispose方法与Close方法的区别详解

    简单的说来,C#中的每一个类型都代表一种资源,而资源又分为两类:托管资源:由CLR管理分配和释放的资源,即由CLR里new出来的对象;非托管资源:不受CLR管理的对象,windows内核对象,如文件、数据库连接、套接字、

    C#资源管理以及IDisposable的实现

    除了`IDisposable`接口,C# 6.0引入了`async` disposing,允许异步地释放资源。通过实现`IAsyncDisposable`接口,可以创建一个支持异步清理的类: ```csharp public class MyClass : IDisposable, IAsyncDisposable...

    C# 安全移除USB设备

    在IT领域,尤其是在Windows操作系统开发中,C#是一种常用的编程语言,用于构建各种应用程序,包括与硬件交互的程序。在本实例中,我们将探讨如何使用C#来安全地检测和移除USB设备。这个主题涉及到Windows API调用、...

    C#操作Excel后的进程释放

    在.net和C#中调用了Excel后,往往Excel进程不会被关闭,此文档给了一个思路

    C#调用海康威视相机

    7. **释放资源**: - 当不再需要相机时,务必调用`ExitDevice`关闭连接,并释放所有已分配的资源。 8. **示例代码**: - `hk_0906(海康相机采集保存设置参数)`可能是包含示例代码的文件名,这将帮助你理解如何...

    C#高效率SOCKET并发端口例子

    为了管理这些并发连接,通常会采用线程池或者Task并行库(TPL)来创建工作线程,这样可以有效地复用线程资源,减少线程创建和销毁的开销。另外,使用异步回调和事件处理机制也是常见的做法,例如,当接收到数据时,...

    c# UdpClient 使用示例

    当完成通信后,记得关闭UdpClient以释放资源: ```csharp udpClient.Close(); ``` 在标签中提到的"C# Udp 协议",说明示例可能还会涵盖UDP协议的基础知识,包括其特性、工作原理以及如何适应不同应用场景。 综...

Global site tag (gtag.js) - Google Analytics