C#析构函数(destructor)和终结器(Finalizer) .
使用析构函数释放资源
析构函数用于析构类的实例。
1) 不能在结构中定义析构函数。只能对类使用析构函数。
2) 一个类只能有一个析构函数。
3) 无法继承或重载析构函数。
4) 无法调用析构函数。它们是被自动调用的。
5) 析构函数既没有修饰符,也没有参数。
例如,下面是类 Car 的析构函数的声明:
- class Car
- {
- /// <summary>
- /// 析构函数
- /// </summary>
- ~Car()
- {
- // cleanup statements...
- }
- }
该析构函数隐式地调用对象基类的 Finalize。这样,该析构函数被隐式地转换为如下代码:
- protected override void Finalize()
- {
- try
- {
- // Cleanup statements...
- }
- finally
- {
- base.Finalize();
- }
- }
这意味着对继承链中的所有实例递归地调用 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 方法。
- 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();
- }
- }
输出结果如下:
- Instantiated object
- This instance of ExampleClass has been in existence for 00:00:00.0011060
- Finalizing object
- This instance of ExampleClass has been in existence for 00:00:00.0036294
资源的显式释放
如果应用程序在使用昂贵的外部资源,建议提供一种在垃圾回收器释放对象前显式地释放资源的方式。可通过实现来自 IDisposable 接口的 Dispose 方法来完成这一点,该方法为对象执行必要的清理。这样可大大提高应用程序的性能。即使有这种对资源的显式控制,析构函数也是一种保护措施,可用来在对 Dispose 方法的调用失败时清理资源。
有关更多详细信息,请参见清理非托管资源。
示例
创建三个类,这三个类构成了一个继承链。这三个类都有析构函数。在 Main() 中,创建了派生程度最大的类的实例。程序运行时,这三个类的析构函数将自动被调用,并按照从派生程度最大的到派生程度最小的次序调用。
- class First
- {
- ~First()
- {
- System.Diagnostics.Trace.WriteLine("First's destructor is called.");
- }
- }
- class Second : First
- {
- ~Second()
- {
- System.Diagnostics.Trace.WriteLine("Second's destructor is called.");
- }
- }
- class Third : Second
- {
- ~Third()
- {
- System.Diagnostics.Trace.WriteLine("Third's destructor is called.");
- }
- }
- class TestDestructors
- {
- static void Main()
- {
- Third t = new Third();
- }
- }
在VS“输出”窗口(快捷键Ctrl+W,O)会看到如下信息:
- ……
- Third'sdestructor is called.
- Second'sdestructor is called.
- 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) .的更多相关文章
- [翻译] 编写高性能 .NET 代码--第二章 GC -- 避免使用终结器,避免大对象,避免复制缓冲区
避免使用终结器 如果没有必要,是不需要实现一个终结器(Finalizer).终结器的代码主要是让GC回收非托管资源用.它会在GC完成标记对象为可回收后,放入一个终结器队列里,在由另外一个线程执行队列里 ...
- 垃圾回收GC:.Net自己主动内存管理 上(三)终结器
垃圾回收GC:.Net自己主动内存管理 上(三)终结器 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主 ...
- JVM强引用、软引用、弱引用、虚引用、终结器引用垃圾回收行为总结
JVM引用 我们希望能描述这样一类对象: 当内存空间还足够时,则能保留在内存中:如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象. -[既偏门又非常高频的面试题]强引用.软引用.弱引用.虚引 ...
- 【C# .Net GC】清除非托管类型(Finalize终结器、dispose模式以及safeHandler)
总结 1.一般要获取一个内核对象的引用,最好用SafeHandle来引用它,这个类可以帮你管理引用计数,而且用它引用内核对象,代码更健壮 2.托管中生成并引用非托管,一但非托管和托管中的引用断开(托管 ...
- Java:终结器
目录 背景Java版:终结器防卫者C#版:“终结器防卫者”备注 背景返回目录 多数情况我们不需要重写 finalize 方法,只有当我们需要持有未托管资源的时候才需要,而此时重写 finalize 方 ...
- Chapter 17_4 终结器
Lua中的垃圾回收主要是针对Lua对象,但是也可以做一些额外的资源管理工作. 可以为表设定垃圾收集的元方法(对于完全用户数据,则需要使用C API),该元方法称为 终结器. Lua用"__g ...
- Java:终结器防卫者,顺便看一下 C# 如何做的。
背景 多数情况我们不需要重写 finalize 方法,只有当我们需要持有未托管资源的时候才需要,而此时重写 finalize 方法,只是作为一个“安全网”,不能作为常规的资源释放模式,必须提供显式的释 ...
- 编写高质量代码改善C#程序的157个建议——建议47:即使提供了显式释放方法,也应该在终结器中提供隐式清理
建议47:即使提供了显式释放方法,也应该在终结器中提供隐式清理 在标准的Dispose模式中,我们注意到一个以~开头的方法,如下: /// <summary> /// 必须,防止程序员忘记 ...
- 构造函数constructor 与析构函数destructor(五)
我们知道当调用默认拷贝构造函数时,一个对象对另一个对象初始化时,这时的赋值时逐成员赋值.这就是浅拷贝,当成员变量有指针时,浅拷贝就会在析构函数那里出现问题.例如下面的例子: //test.h #ifn ...
随机推荐
- Java io 理解
任何程序都有io部分,io是对程序来说数据流的输入和输出.这里说的流,是指有字节组成的列,不断输入程序,或者从程序中输出,我们形象称为流.Java的io流有两种,一种叫字节流,最原始的:一种叫字符流. ...
- Missing artifact com.sun.jmx:jmxri:jar:1.2.1的解决方法
maven项目添加log4j-1.2.15依赖出现Missing artifact com.sun.jmx:jmxri:jar:1.2.1错误 解决方法一:修改log4j.jar的版本为1.2.16或 ...
- 【ORACLE语句备份】数据库表同步 ——定时任务管理器(EXPDP导出,IMPDP导入)
1.C:\Users\Administrator>sqlplus sys/xxx@xxx as sysdba; 2.SQL> create directory dbbak4 as 'e:\ ...
- Spring Boot 中初始化资源的几种方式(转)
假设有这么一个需求,要求在项目启动过程中,完成线程池的初始化,加密证书加载等功能,你会怎么做?如果没想好答案,请接着往下看.今天介绍几种在Spring Boot中进行资源初始化的方式,帮助大家解决和回 ...
- 【leetcode】1284. Minimum Number of Flips to Convert Binary Matrix to Zero Matrix
题目如下: Given a m x n binary matrix mat. In one step, you can choose one cell and flip it and all the ...
- 线程安全 Vs 非线程安全
线程安全:多线程访问时,采用了加锁机制,当一个线程读取数据时,其他线程不能访问直到该线程读取完毕.不会出现数据不一致或者脏数据. 非线程安全:不提供数据保护,可能出现其他线程访问时更改数据而该线程得到 ...
- Hibernate 5 Maven 仓库的 Artifacts
Hibernate artifacts 官方发布的仓库在 JBoss Maven repository 中.Hibernate 发布的 artifacts 也会同时同步到 Maven Central ...
- HZOJ 20190727 T2 单(树上dp+乱搞?+乱推式子?+dfs?)
考试T2,考试时想到了40pts解法,即对于求b数组,随便瞎搞一下就oxxk,求a的话,很明显的高斯消元,但考试时不会打+没开double挂成10pts(我真sb),感觉考试策略还是不够成熟,而且感觉 ...
- 「HEOI2014」大工程
问题分析 首先不难想到是虚树.建完虚树需要保持节点间原先的距离关系. 然后总距离和最小距离用树形DP求,最大距离用两遍dfs即可.注意统计的时候只对关键点进行统计. 真是麻烦 参考程序 ac的时候是l ...
- python-获取程序的路径
python获取当前路径 import os,sys 使用sys.path[0].sys.argv[0].os.getcwd().os.path.abspath(file).os.path.realp ...