IDispose(), Finalize()

时间:2022-05-19 12:15:11

C#  using 用法:

1. 作为指令,用于为命名空间创建别名或导入其他命名空间中定义的类型。(见例1-1)

2. 作为语句,用于定义一个范围,在此范围的末尾将释放对象。(见例1-2)

(例子1-1)

① 允许在命名空间中使用类型,这样,您就不必在该命名空间中限定某个类型的使用

using System.ServiceModel;

② 为命名空间或类型创建别名

③  等同于Try.{}Finally{}在语句结束时自动执行。

public class Foo: IDisposable

2 {

3     public void Dispose()

4     {

5        Dispose(true);

6        GC.SuppressFinalize(this);

7     }

8

9     protected virtual void Dispose(bool disposing)

10     {

11        if (!m_disposed)

12        {

13            if (disposing)

14            {

15               // Release managed resources

16            }

17

18            // Release unmanaged resources

19

20            m_disposed = true;

21        }

22     }

23

24     ~Foo()

25     {

26        Dispose(false);

27     }

28

29     private bool m_disposed;

30 }

Finalize 和 Dispose

Finalize 和 Dispose 都是释放资源,Finalize 隐式释放资源,Dispose 显式释放资源,怎么理解呢?Finalize 是对象不可访问后自动被调用的,Dispose 是类使用者调用的。

对于类设计者:

Finalize 和 Dispose 释放的资源应该相同,这样即使类使用者在没有调用 Dispose 的情况下,资源也会在 Finalize 中得到释放。

Dispose 中应该调用 GC.SuppressFinalize 方法,这样类使用者调用了 Dispose 后,就不会自动调用 Finalize 了,因为调用 Dispose 后没有必要再执行 Finalize。

Finalize 不应为 public。

对于类使用者:

有 Dispose 方法存在时,应该调用它,因为 Finalize 释放资源通常是很慢的。

也就是说,对于类使用者,我们只需要调用 Dispose 就可以了,不需要关注 Finalize,因为 Finalize 通常不是 public 的。如果类使用者没有调用 Dispose 方法,Finalize 是释放资源的最后防线。当然这些都是建立在类设计者遵照上述规则设计的前提下。

Close 是什么呢?

Close 这个方法在不同的类中有不同的含义,并没有任何规定要求 Close 具有特殊的含义,也就是说 Close 并不一定要释放资源,您也可以让 Close 方法表示“关门”。

不过,由于 Close 有“关”的意思,通常也把 Close 拿来释放资源,这也是允许的。比如文件操作中,用 Close 释放对象似乎比 Dispose 含义更准确,于是在设计类时,可以将 Close 设为 public,将 Dispose 设为 protected,然后由 Close 调用 Dispose。

也就是说 Close 表示什么意思,它会不会释放资源,完全由类设计者决定。网上说“Close 调用 Dispose”这种方法是很片面的。在 SqlConnection 中 Close 只是表示关闭数据库连接,并没有释放 SqlConnection 这个对象资源。

根据经验,Close 和 Dispose 同时存在的情况下(均为 public),Close 并不表示释放资源,因为通常情况下,类设计者不应该使用两个 public 方法来释放相同的资源。

1、 Finalize只释放非托管资源;

2、 Dispose释放托管和非托管资源;

3、 重复调用Finalize和Dispose是没有问题的;

4、 Finalize和Dispose共享相同的资源释放策略,因此他们之间也是没有冲突的。

1、Finalize方法(C#中是析构函数,以下称析构函数)是用于释放非托管资源的,而托管资源会由GC自动回收。所以,我们也可以这样来区分 托管和非托管资源。所有会由GC自动回收的资源,就是托管的资源,而不能由GC自动回收的资源,就是非托管资源。在我们的类中直接使用非托管资源的情况很 少,所以基本上不用我们写析构函数。

2、大部分的非托管资源会给系统带来很多负面影响,例如数据库连接不被释放就可能导致连接池中的可用数据库连接用尽。文件不关闭会导致其它进程无法读写这个文件等等。

实现模型:

1、由于大多数的非托管资源都要求可以手动释放,所以,我们应该专门为释放非托管资源公开一个方法。实现IDispose接口的Dispose方法是最好的模型,因为C#支持using语句快,可以在离开语句块时自动调用Dispose方法。

2、虽然可以手动释放非托管资源,我们仍然要在析构函数中释放非托管资源,这样才是安全的应用程序。否则如果因为程序员的疏忽忘记了手动释放非托管资源, 那么就会带来灾难性的后果。所以说在析构函数中释放非托管资源,是一种补救的措施,至少对于大多数类来说是如此。

3、由于析构函数的调用将导致GC对对象回收的效率降低,所以如果已经完成了析构函数该干的事情(例如释放非托管资源),就应当使用SuppressFinalize方法告诉GC不需要再执行某个对象的析构函数。

4、析构函数中只能释放非托管资源而不能对任何托管的对象/资源进行操作。因为你无法预测析构函数的运行时机,所以,当析构函数被执行的时候,也许你进行操作的托管资源已经被释放了。这样将导致严重的后果。

5、(这是一个规则)如果一个类拥有一个实现了IDispose接口类型的成员,并创建(注意是创建,而不是接收,必须是由类自己创建)它的实例对象,则 这个类也应该实现IDispose接口,并在Dispose方法中调用所有实现了IDispose接口的成员的Dispose方法。

只有这样的才能保证所有实现了IDispose接口的类的对象的Dispose方法能够被调用到,确保可以手动释放任何需要释放的资源。

///鱼类

namespace Fish

{

///为命名空间GoldFish取别名fish

using fish = Fish.Goldfish;

class FishColor

{

void M()

{

fish.Colors color = new fish.Colors();

}

}

///金鱼

namespace Goldfish

{

public class Colors

{

}

}

}

④ 根据编程习惯using可以放在namespace内部也可以放到外部

namespace Fish

{

using System.Text;

}

等同于

using System.Text;

namespace Fish{ }

⑤ using 别名指令的右侧不能有开放式泛型类型。 例如,不能为 List<T> 创建 using 别名,但可以为 List<int> 创建。

(例子1-2)

提供能确保正确使用 IDisposable 对象的方便语法,using 语句确保调用 Dispose,即使在调用对象上的方法时发生异常也是如此。 通过将对象放入 try 块中,并在调用 finally 块中的 Dispose,可以获得相同的结果;实际上,这就是编译器转换 using 语句的方式

1、

Font font1 = new Font("Arial", 10.0f);

try

{

byte charset = font1.GdiCharSet;

}

finally

{

if (font1 != null)

((IDisposable)font1).Dispose();

}

2、

using (Font font3 = new Font("Arial", 10.0f),

font4 = new Font("Arial", 10.0f))

{

// Use font3 and font4.

}

IDisposable 接口

此接口的主要用途是释放非托管资源。 当不再使用托管对象时,垃圾回收器会自动释放分配给该对象的内存。 但无法预测进行垃圾回收的时间。 另外,垃圾回收器对窗口句柄或打

示例

下面的示例演示如何创建用来实现 IDisposable 接口的资源类。

using System;

using System.ComponentModel;

// and the IDisposable.Dispose method.

public class DisposeExample

{

// instances of this type allocate scarce resources.

public class MyResource: IDisposable

{

// Pointer to an external unmanaged resource.

private IntPtr handle;

// Other managed resource this class uses.

private Component component = new Component();

// Track whether Dispose has been called.

private bool disposed = false;

// The class constructor.

public MyResource(IntPtr handle)

{

this.handle = handle;

}

// Implement IDisposable.

public void Dispose()

{

Dispose(true);

// This object will be cleaned up by the Dispose method.

GC.SuppressFinalize(this);

}

protected virtual void Dispose(bool disposing)

{

// Check to see if Dispose has already been called.

if(!this.disposed)

{

// and unmanaged resources.

if(disposing)

{

// Dispose managed resources.

component.Dispose();

}

// Call the appropriate methods to clean up

CloseHandle(handle);

handle = IntPtr.Zero;

// Note disposing has been done.

disposed = true;

}

}

// Use interop to call the method necessary

// to clean up the unmanaged resource.

[System.Runtime.InteropServices.DllImport("Kernel32")]

private extern static Boolean CloseHandle(IntPtr handle);

// Do not provide destructors in types derived from this class.

~MyResource()

{

// readability and maintainability.

Dispose(false);

}

}

public static void Main()

{

// Insert code here to create

// and use the MyResource object.

}

}

实现 Dispose 方法

类型的 Dispose 方法应该释放它拥有的所有资源。 它还应该释放其基类型拥有的所有资源通过调用其父类型的Dispose 方法。 父类型的 Dispose 方法应该释放它拥有进而调用其父类型的 Dispose 方法的所有资源,传播此模式通过基类型层次结构。 若要帮助确保始终正确地清理资源, Dispose 方法应该可以多次调用的,而不会引发异常。

未在执行 Dispose 方法的性能优点。仅使用托管资源的类型 (如数组),因为它们由垃圾回收器自动回收。 使用Dispose 方法主要在使用本机资源的托管对象和向 .NET framework 公开 COM 对象。 使用本机资源的托管对象 (如 FileStream 类) 实现 IDisposable 接口。

Dispose 方法应调用该配置对象的 SuppressFinalize 方法。 如果对象当前在终止队列中, SuppressFinalize 防止其 Finalize 方法调用。 请记住执行 Finalize 方法会非常大的性能。 如果您的 Dispose 方法已完成清理对象的工作,则调用对象的方法 Finalize 垃圾回收器并不是必需的。

示例

下面的代码示例演示一个执行的 Dispose 方法的建议设计模式封装非托管资源的类。

资源类从复杂的本机类或 API 通常派生,并且必须相应地自定义。 使用此编码模式,创建资源类的起始点并根据封装的资源提供必要的自定义。

using System;

using System.IO;

class Program

{

static void Main()

{

try

{

// Initialize a Stream resource to pass

// to the DisposableResource class.

Console.Write("Enter filename and its path: ");

string fileSpec = Console.ReadLine();

FileStream fs = File.OpenRead(fileSpec);

DisposableResource TestObj = new DisposableResource(fs);

// Use the resource.

TestObj.DoSomethingWithResource();

// Dispose the resource.

TestObj.Dispose();

}

catch (FileNotFoundException e)

{

Console.WriteLine(e.Message);

}

}

}

// or memory in the unmanaged heap.

public class DisposableResource : IDisposable

{

private Stream _resource;

private bool _disposed;

// The stream passed to the constructor

// must be readable and not null.

public DisposableResource(Stream stream)

{

if (stream == null)

throw new ArgumentNullException("Stream in null.");

if (!stream.CanRead)

throw new ArgumentException("Stream must be readable.");

_resource = stream;

_disposed = false;

}

// Demonstrates using the resource.

// It must not be already disposed.

public void DoSomethingWithResource() {

if (_disposed)

throw new ObjectDisposedException("Resource was disposed.");

// Show the number of bytes.

int numBytes = (int) _resource.Length;

Console.WriteLine("Number of bytes: {0}", numBytes.ToString());

}

public void Dispose()

{

Dispose(true);

// Use SupressFinalize in case a subclass

// of this type implements a finalizer.

GC.SuppressFinalize(this);

}

protected virtual void Dispose(bool disposing)

{

// If you need thread safety, use a lock around these

// operations, as well as in your methods that use the resource.

if (!_disposed)

{

if (disposing) {

if (_resource != null)

_resource.Dispose();

Console.WriteLine("Object disposed.");

}

// Indicate that the instance has been disposed.

_resource = null;

_disposed = true;

}

}

}

可以实例化资源对象,然后将变量传递给 using 语句,但这不是最佳做法。 在这种情况下,该对象将在控制权离开 using 块之后保持在范围内,即使它可能将不再具有对其非托管资源的访问权也是如此。 换句话说,再也不能完全初始化该对象。 如果尝试在 using 块外部使用该对象,则可能导致引发异常。 由于这个原因,通常最好是在 using 语句中实例化该对象并将其范围限制在 using 块中。

Font font2 = new Font("Arial", 10.0f);

using (font2) // not recommended

{

// use font2

}

// font2 is still in scope

// but the method call throws an exception

float f = font2.GetHeight();

在当前对象销毁之前,该 Finalize 方法用于执行当前对象占用的非托管资源上的清理操作。 方法是受保护的,因此只能通过此类或派生类访问它。

对象变为不可访问后将自动调用此方法,除非已通过 GC.SuppressFinalize 调用使对象免除了终结。 在应用程序域的关闭期间,Finalize 将自动在没有免于终止的对象,甚至是那些仍可以访问的对象上调用。 对于给定的实例仅自动调用 Finalize 一次,除非使用 GC.ReRegisterForFinalize 这类机制重新注册该对象并且后面没有调用 GC.SuppressFinalize

派生类型中的每个 Finalize 实现都必须调用其基类型的 Finalize 实现。 这是唯一一种允许应用程序代码调用Finalize 的情况。

Finalize 操作具有下列限制:

垃圾回收过程中执行终结器的准确时间是不确定的。 不保证资源在任何特定的时间都能释放,除非调用 Close 方法或 Dispose 方法。

即使一个对象引用另一个对象,也不能保证两个对象的终结器以任何特定的顺序运行。 即,如果对象 A 具有对对象 B 的引用,并且两者都有终结器,则当对象 A 的终结器启动时,对象 B 可能已经终结了。

运行终结器的线程是未指定的。

在下面的异常情况下,Finalize 方法可能不会运行完成或可能根本不运行:

另一个终结器无限期地阻止(进入无限循环,试图获取永远无法获取的锁,诸如此类)。 由于运行时试图运行终结器来完成,所以如果一个终结器无限期地阻止,则可能不会调用其他终结器。

进程终止,但不给运行时提供清理的机会。 在这种情况下,运行时的第一个进程终止通知是 DLL_PROCESS_DETACH 通知。

在关闭过程中,只有当可终结对象的数目继续减少时,运行时才继续 Finalize 对象。

如果 Finalize 或 Finalize 的重写引发异常,并且运行时并非寄宿在重写默认策略的应用程序中,则运行时将终止进程,并且不执行任何活动的 try-finally 块或终结器。 如果终结器无法释放或销毁资源,此行为可以确保进程完整性。

对实现者的说明

默认情况下,Object.Finalize 不执行任何操作。 只有在必要时才必须由派生类重写它,因为如果必须运行Finalize 操作,垃圾回收过程中的回收往往需要长得多的时间。

如果 Object 保存了对任何资源的引用,则 Finalize 必须由派生类重写,以便在垃圾回收过程中,在放弃Object 之前释放这些资源。

当类型使用文件句柄或数据库连接这类在回收使用托管对象时必须释放的非托管资源时,该类型必须实现Finalize。 有关辅助和具有更多控制的资源处置方式,请参见 IDisposable 接口。

Finalize 可以采取任何操作,包括在垃圾回收过程中清理了对象后使对象复活(即,使对象再次可访问)。但是,对象只能复活一次;在垃圾回收过程中,不能对复活对象调用 Finalize。

析构函数是执行清理操作的 C# 机制。 析构函数提供了适当的保护措施,如自动调用基类型的析构函数。 在 C# 代码中,不能调用或重写 Object.Finalize。

示例

当重写 Finalize 的对象销毁时,下面的示例验证调用的 Finalize 方法。 请注意,在成品应用程序中,Finalize方法将被重写,以释放由该对象占用的非托管资源。 此外请注意,C# 示例提供析构函数而不是重写 Finalize方法。

using System;

using System.Diagnostics;

public class ExampleClass

{

Stopwatch sw;

public ExampleClass()

{

sw = Stopwatch.StartNew();

Console.WriteLine("Instantiated object");

}

public void ShowDuration()

{

Console.WriteLine("This instance of {0} has been in existence for {1}",

this, sw.Elapsed);

}

~ExampleClass()

{

Console.WriteLine("Finalizing object");

sw.Stop();

Console.WriteLine("This instance of {0} has been in existence for {1}",

this, sw.Elapsed);

}

}

public class Demo

{

public static void Main()

{

ExampleClass ex = new ExampleClass();

ex.ShowDuration();

}

}