C#析构函数(destructor)和终结器(Finalizer) .

使用析构函数释放资源

析构函数用于析构类的实例。

1)         不能在结构中定义析构函数。只能对类使用析构函数。

2)         一个类只能有一个析构函数。

3)         无法继承或重载析构函数。

4)         无法调用析构函数。它们是被自动调用的。

5)         析构函数既没有修饰符,也没有参数。

例如,下面是类 Car 的析构函数的声明:

[csharp] view plaincopy

  1. class Car
  2. {
  3. /// <summary>
  4. /// 析构函数
  5. /// </summary>
  6. ~Car()
  7. {
  8. // cleanup statements...
  9. }
  10. }

该析构函数隐式地调用对象基类的 Finalize。这样,该析构函数被隐式地转换为如下代码:

[csharp] view plaincopy

  1. protected override void Finalize()
  2. {
  3. try
  4. {
  5. // Cleanup statements...
  6. }
  7. finally
  8. {
  9. base.Finalize();
  10. }
  11. }

这意味着对继承链中的所有实例递归地调用 Finalize 方法。

说明:不要使用空的析构函数。如果类包含析构函数,则 Finalize  队列中则会创建一个项。当调用析构函数时,将调用垃圾回收器(GC)来处理该队列。如果析构函数为空,只会导致不必要的性能损失。

程序员无法控制何时调用析构函数,因为这是由垃圾回收器决定的。垃圾回收器检查是否存在应用程序不再使用的对象。如果垃圾回收器认为某个对象符合析构,则调用析构函数(如果有的话),回收该对象的内存。程序退出时也会调用析构函数。

可以通过调用 Collect 强制进行垃圾回收,但大多数情况下应避免这样做,因为会导致性能问题。

通常,.NET Framework 垃圾回收器会隐式地管理对象的内存分配和释放。但当应用程序封装窗口、文件和网络连接这类非托管资源时,应使用析构函数释放这些资源。当对象符合析构时,垃圾回收器将运行对象的 Finalize 方法。

虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。常见的非托管源有:ApplicationContext、Brush、Component、ComponentDesigner、Container、Context、Cursor、FileStream、Font、Icon、Image、Matrix、Object、OdbcDataReader、OleDBDataReader、Pen、Regex、Socket、StreamWriter、Timer、Tooltip 等。

Object.Finalize方法

允许 Object 在“垃圾回收”回收 Object 之前尝试释放资源并执行其他清理操作。Finalize 是受保护的,因此只能通过此类或派生类访问它。

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

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

注意:C# 编译器不允许你直接实现 Finalize 方法,因此 C# 析构函数自动调用其基类的析构函数。

Finalize 操作具有下列限制:

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

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

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

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

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

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

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

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

说明:默认情况下,Object.Finalize 不执行任何操作。只有在必要时才必须由派生类重写它,因为如果必须运行 Finalize 操作,垃圾回收过程中的回收往往需要长得多的时间。如果 Object 保存了对任何资源的引用,则 Finalize 必须由派生类重写,以便在垃圾回收过程中,在放弃 Object 之前释放这些资源。当类型使用文件句柄或数据库连接这类在回收使用托管对象时必须释放的非托管资源时,该类型必须实现 Finalize。Finalize 可以采取任何操作,包括在垃圾回收过程中清理了对象后使对象复活(即,使对象再次可访问)。但是,对象只能复活一次;在垃圾回收过程中,不能对复活对象调用 Finalize。

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

示例

下面例子验证,当一个重构Finalize 方法的对象被销毁时,会调用Finalize 方法。注意,在产品应用程序中,Finalize 方法会被重构,以便释放对象拥有的非托管资源。另外,C# 提供析构函数,而没有重构Finalize 方法。

[csharp] view plaincopy

  1. using System;
  2. using System.Diagnostics;
  3. public class ExampleClass
  4. {
  5. Stopwatch sw;
  6. public ExampleClass()
  7. {
  8. sw = Stopwatch.StartNew();
  9. Console.WriteLine("Instantiated object");
  10. }
  11. public void ShowDuration()
  12. {
  13. Console.WriteLine("This instance of {0} has been in existence for {1}", this, sw.Elapsed);
  14. }
  15. ~ExampleClass()
  16. {
  17. Console.WriteLine("Finalizing object");
  18. sw.Stop();
  19. Console.WriteLine("This instance of {0} has been in existence for {1}", this, sw.Elapsed);
  20. }
  21. }
  22. public class Demo
  23. {
  24. public static void Main()
  25. {
  26. ExampleClass ex = new ExampleClass();
  27. ex.ShowDuration();
  28. }
  29. }

输出结果如下:

[plain] view plaincopy

  1. Instantiated object
  2. This instance of ExampleClass has been in existence for 00:00:00.0011060
  3. Finalizing object
  4. This instance of ExampleClass has been in existence for 00:00:00.0036294

资源的显式释放

如果应用程序在使用昂贵的外部资源,建议提供一种在垃圾回收器释放对象前显式地释放资源的方式。可通过实现来自 IDisposable 接口的 Dispose 方法来完成这一点,该方法为对象执行必要的清理。这样可大大提高应用程序的性能。即使有这种对资源的显式控制,析构函数也是一种保护措施,可用来在对 Dispose 方法的调用失败时清理资源。

有关更多详细信息,请参见清理非托管资源

示例

创建三个类,这三个类构成了一个继承链。这三个类都有析构函数。在 Main() 中,创建了派生程度最大的类的实例。程序运行时,这三个类的析构函数将自动被调用,并按照从派生程度最大的到派生程度最小的次序调用。

[csharp] view plaincopy

  1. class First
  2. {
  3. ~First()
  4. {
  5. System.Diagnostics.Trace.WriteLine("First‘s destructor is called.");
  6. }
  7. }
  8. class Second : First
  9. {
  10. ~Second()
  11. {
  12. System.Diagnostics.Trace.WriteLine("Second‘s destructor is called.");
  13. }
  14. }
  15. class Third : Second
  16. {
  17. ~Third()
  18. {
  19. System.Diagnostics.Trace.WriteLine("Third‘s destructor is called.");
  20. }
  21. }
  22. class TestDestructors
  23. {
  24. static void Main()
  25. {
  26. Third t = new Third();
  27. }
  28. }

在VS“输出”窗口(快捷键Ctrl+W,O)会看到如下信息:

[plain] view plaincopy

  1. ……
  2. Third‘sdestructor is called.
  3. Second‘sdestructor is called.
  4. First‘sdestructor is called.

程序“[3796] TestDestructors.vshost.exe: 托管”已退出,返回值为0 (0x0)。

有关更多信息,请参见 C# 语言规范中的以下各章节:

a)         1.6.7.6 析构函数

b)         10.3.9.4 为析构函数保留的成员名称

c)         10.13 析构函数(类)

d)         11.3.9 析构函数(结构)

C# 语言规范位于 Visual Studio 2008 安装目录下的 VC#/Specifications/1033/ 目录。

C#析构函数(destructor)和终结器(Finalizer) .

时间: 2024-10-28 16:53:17

C#析构函数(destructor)和终结器(Finalizer) .的相关文章

垃圾回收GC:.Net自动内存管理 上(三)终结器

垃圾回收GC:.Net自动内存管理 上(三)终结器 垃圾回收GC:.Net自动内存管理 上(一)内存分配 垃圾回收GC:.Net自动内存管理 上(二)内存算法 垃圾回收GC:.Net自动内存管理 上(三)终结器 前言 .Net下的GC完全解决了开发者跟踪内存使用以及控制释放内存的窘态.然而,你或午想要理解GC是怎么工作的.此系列文章中将会解释内存资源是怎么被合理分配及管理的,并包含非常详细的内在算法描述.同时,还将讨论GC的内存清理流程及什么时清理,怎么样强制清理. 终结器 GC提供了另外一个能

垃圾回收GC:.Net自己主动内存管理 上(三)终结器

垃圾回收GC:.Net自己主动内存管理 上(三)终结器 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主动内存管理 上(三)终结器 前言 .Net下的GC全然攻克了开发人员跟踪内存使用以及控制释放内存的窘态.然而,你或午想要理解GC是怎么工作的.此系列文章中将会解释内存资源是怎么被合理分配及管理的,并包括很具体的内在算法描写叙述.同一时候,还将讨论GC的内存清理流程及什么时清理,怎么样强制清理. 终结

对托管资源使用终结器

重要提示:有的人可能有这样的心态,永远不要对托管资源使用终结器,我在很大程度上赞成这个观点,所以可以完全跳过本节,对托管资源使用终结器,是非常高的编码方式,只有极少数情况下才应该使用,要是使用必须对Finalize方法中的调用的代码有一个全面和深刻的认识.另外,还必须保证调用的代码的行为在未来的版本中不会发生改变.具体的说,Finalize方法中调用的任何代码都不能使用其他任何可能已终结的对象. 虽然终结操作是专门来释放本地资源,但偶尔也用于托管资源,下面这个类造成计算机在垃圾回收器每执行一次回

使用终结器来释放本地资源

前面我们基本了解了垃圾回收和托管堆得情况了,包含垃圾回收期如何回收对象的内存,幸运的是,大多数类型只要内存就可以正常工作,但是,另外有一些类型除了使用内存,还要使用本地资源. 例如:System.IO.FileStream类型需要打开一个文件(本地资源)并保存文件的句柄.然后,该类型的Read和Write方法使用该句柄来操作文件,类似的,System.Threading.Mutex类型打开一个WINDOWS互斥体内核对象(本地资源)并保存其句柄,并在调用Mutex方法时使用该句柄.终结(fina

编写高质量代码改善C#程序的157个建议——建议47:即使提供了显式释放方法,也应该在终结器中提供隐式清理

建议47:即使提供了显式释放方法,也应该在终结器中提供隐式清理 在标准的Dispose模式中,我们注意到一个以~开头的方法,如下: /// <summary> /// 必须,防止程序员忘记了显式调用Dispose方法 /// </summary> ~SampleClass() { //必须为false Dispose(false); } 这个方法叫做类型的终结器.提供类型终结器的意义在于,我们不能奢望类型的调用者肯定会主动调用Dispose方法,基于终结器会被垃圾回收这个特点,它被

【转】c++析构函数(Destructor)

创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存.关闭打开的文件等,这个函数就是析构函数. 析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行.构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号. 注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数.如果用户没有定义,编译器会自动生成一个默认的析构函数. 上

构造函数constructor 与析构函数destructor(一)

构造函数定义:构造函数c++中在创建对象时自动调用,用来初始化对象的特殊函数. (1)构造函数的名字必须与类的名字相同,不能有返回值,哪怕是void 也不行. (2)通常情况下构造函数应声明为公有函数,否则它不能像其他成员函数那样被显式地调用 构造函数被声明为私有有特殊的用途,这个以后再写. (3)但是可以有参数,因为有参数,故可以被重载. 1 #ifndef TEST_H 2 #define TEST_H 3 class Test{ 4 int m_i; 5 public: 6 Test(in

构造函数constructor 与析构函数destructor(二)

(1)转换构造函数 转换构造函数的定义:转换构造函数就是把普通的内置类型转换成类类型的构造函数,这种构造函数只有一个参数.只含有一个参数的构造函数,可以作为两种构造函数,一种是普通构造函数用于初始化对象,一种是转换构造函数 1 //test.h 2 #ifndef TEST_H 3 #define TEST_H 4 class Test{ 5 int m_i; 6 public: 7 Test(int i = 0);//转换构造函数,也是普通构造函数 8 ~Test(); 9 10 }; 11

构造函数constructor 与析构函数destructor(四)

(1)构造函数初始化列表: 1 class Test{ 2 int i; 3 public: 4 Test(int vi):i(vi){}//这里的从冒号开始,到右大括号结束,这一段是构造函数初始化列表 5 6 }; 构造函数的执行分为两个阶段:(1)初始化阶段  (2)普通计算阶段 初始化是我们定义一个变量,分配内存时直接给变量赋值,例如 int i=10;  而int k; k=10;这样的就不是初始化,是赋值.所以在初始化列表里面的初始化才是真正的初始化,即初始化阶段.而在构造函数内的那些