使用析构函数释放资源

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

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. SpringBoot 上传读取图片 巨坑

    之前自己也做过文件上传,不过存储路径放在那个tomcat服务器路径下,就没遇到什么问题 但前几天在做图片的上传,想把文件放在项目下指定的一个文件夹下,就感觉有点麻烦 修改配置文件 在springboo ...

  2. 使用IDEA创建JavaWeb项目 部署本地tomcat并运行

    一.下载商业版IDEA 官方链接:https://www.jetbrains.com/idea/download/#section=windows 二.新建JavaWeb项目 1.在菜单栏找到File ...

  3. github高速下载的方法

    windows修改host文件: C:\Windows\System32\drivers\etc\hostslinux 修改host文件: /etc/hosts 在文件后面加上这两行 151.101. ...

  4. 洛谷-P2292-L语言(字典树)

    链接: https://www.luogu.org/problem/P2292 题意: 标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的.现在你要处理的就是一段没有标点的文章. 一段文章T是 ...

  5. Spring下的@Order和@Primary与javax.annotation-api下@Priority【Spring4.1后】等方法控制多实现的依赖注入(转)

    @Order 可以作用在类.方法.属性. 影响加载顺序. 若不加,spring的加载顺序是随机的. @Primary 当注入bean冲突时,以@Primary定义的为准. @Order是控制配置类的加 ...

  6. The Cost of JavaScript --------引用

    tl;dr: 想要保持页面的快速运行,你需要仅加载当前页面所需的 JavaScript 代码.优先考虑用户所需,之后运用代码分离懒加载其他内容. Is it happening - 在这个时期,你可以 ...

  7. Spring Batch 4.2 新特性

    Spring Batch 4.2 的发行版主要增强了下面的改进: 使用 Micrometer 来支持批量指标(batch metrics) 支持从 Apache Kafka topics 读取/写入( ...

  8. 二叉树的序遍历x(内含结构体与非结构体版x)

    3143 codevs 二叉树的序遍历 题目描述 Description 求一棵二叉树的前序遍历,中序遍历和后序遍历 输入描述 Input Description 第一行一个整数n,表示这棵树的节点个 ...

  9. python celery 异步学习

    1.运行redis 2.安装celery:pip install celery[redis] 3.vim task.py import time from celery import Celery b ...

  10. 访问zabbix首页无法正常登陆

    访问: http://IP/zabbix/ (1) You should see the first screen of the frontend installation wizard. (2) 检 ...