使用析构函数释放资源

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

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. }

输出结果如下:

  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)会看到如下信息:

  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) .的更多相关文章

  1. [翻译] 编写高性能 .NET 代码--第二章 GC -- 避免使用终结器,避免大对象,避免复制缓冲区

    避免使用终结器 如果没有必要,是不需要实现一个终结器(Finalizer).终结器的代码主要是让GC回收非托管资源用.它会在GC完成标记对象为可回收后,放入一个终结器队列里,在由另外一个线程执行队列里 ...

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

    垃圾回收GC:.Net自己主动内存管理 上(三)终结器 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主 ...

  3. JVM强引用、软引用、弱引用、虚引用、终结器引用垃圾回收行为总结

    JVM引用 我们希望能描述这样一类对象: 当内存空间还足够时,则能保留在内存中:如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象. -[既偏门又非常高频的面试题]强引用.软引用.弱引用.虚引 ...

  4. 【C# .Net GC】清除非托管类型(Finalize终结器、dispose模式以及safeHandler)

    总结 1.一般要获取一个内核对象的引用,最好用SafeHandle来引用它,这个类可以帮你管理引用计数,而且用它引用内核对象,代码更健壮 2.托管中生成并引用非托管,一但非托管和托管中的引用断开(托管 ...

  5. Java:终结器

    目录 背景Java版:终结器防卫者C#版:“终结器防卫者”备注 背景返回目录 多数情况我们不需要重写 finalize 方法,只有当我们需要持有未托管资源的时候才需要,而此时重写 finalize 方 ...

  6. Chapter 17_4 终结器

    Lua中的垃圾回收主要是针对Lua对象,但是也可以做一些额外的资源管理工作. 可以为表设定垃圾收集的元方法(对于完全用户数据,则需要使用C API),该元方法称为 终结器. Lua用"__g ...

  7. Java:终结器防卫者,顺便看一下 C# 如何做的。

    背景 多数情况我们不需要重写 finalize 方法,只有当我们需要持有未托管资源的时候才需要,而此时重写 finalize 方法,只是作为一个“安全网”,不能作为常规的资源释放模式,必须提供显式的释 ...

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

    建议47:即使提供了显式释放方法,也应该在终结器中提供隐式清理 在标准的Dispose模式中,我们注意到一个以~开头的方法,如下: /// <summary> /// 必须,防止程序员忘记 ...

  9. 构造函数constructor 与析构函数destructor(五)

    我们知道当调用默认拷贝构造函数时,一个对象对另一个对象初始化时,这时的赋值时逐成员赋值.这就是浅拷贝,当成员变量有指针时,浅拷贝就会在析构函数那里出现问题.例如下面的例子: //test.h #ifn ...

随机推荐

  1. Bridge 桥梁模式

    注:桥梁模式是结构型设计模式,将抽象部分与它的实现部分相分离,使他们可以独立的变化.  抽象部分可能有n种实现,而实现部分可能有n种实现方式,采用享元模式,减少子类数据. 曾经看过一个桥梁模式相关的例 ...

  2. 部署NFS共享

    一:NFS工作原理 什么是NFS服务器 NFS就是Network File System的缩写,它最大的功能就是可以通过网络,让不同的机器.不同的操作系统可以共享彼此的文件. NFS服务器可以让PC将 ...

  3. 分布式 一致性Paxos算法(转载)

    比较通俗易懂,可以入门,转载地址是http://www.cnblogs.com/linbingdong/p/6253479.html Paxos算法在分布式领域具有非常重要的地位.但是Paxos算法有 ...

  4. 理论基础+实战控制台程序实现AutoFac注入

    [半小时大话.net依赖注入](一)理论基础+实战控制台程序实现AutoFac注入   系列目录# 第一章|理论基础+实战控制台程序实现AutoFac注入 第二章|AutoFac的常见使用套路 第三章 ...

  5. IIFE 立即执行函数表达式-模块化

    1)立即执行 2)表达式 3)括号,分号结束 | 前缀运算符 | 表达式上下文 4)只需要执行一次,内部变量无需维护,可用于初始化 (function( ) { })( ); 或 (function( ...

  6. Python-multiprocessing-Process模块

    获取当前执行该文件的进程ID import os # 获取当前执行该文件的进程ID print("Process (%s) start..." % os.getpid()) mul ...

  7. 【CUDA 基础】2.3 组织并行线程

    title: [CUDA 基础]2.3 组织并行线程 categories: CUDA Freshman tags: Thread Block Grid toc: true date: 2018-03 ...

  8. nu.random.seed()如何理解

    结论: np.random.seed(a) # 按照规定的顺序生成随机数 # 参数a指定了随机数生成的起始位置: # 如果两处都采用了np.random.seed(a),且两处的参数a相同,则生成的随 ...

  9. vue 钩子函数的使用

    1.什么是自定义指令,有哪些钩子函数及自定义指令的使用场景 ①自定义指令是什么?以及自定义指令的使用场景 在Vue中,有很多内置指令,但是这些指令只能满足我们最基础的使用,当我们在实际项目中遇到了必须 ...

  10. Node.js安装及环境配置

     1.Node.js简介 简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境. Node.js ...