大家知道,托管代码一个重要的特点是自动管理内存,即我们常说的垃圾回收机制,那些高大上的理论我就不重复了,有兴趣的朋友可以翻书。我这个有个毛病——不喜欢很严肃地去说一些理论的东西,所以我不多介绍了。

一般而言,当代码执行超出某个变量的有效范围后,或者不再引用某个对象实例时,该实例会发生析构,垃圾回收器很可能就要清理门户了,当然也可能不是马上清理,也许会过一会儿再清理。

对于一些要自定义进行清理操作的类,我们会采取以下方案:

1、写上析构函数,在析构函数中清理。

2、实现IDisposable接口,并实现Dispose方法,在方法中编写自定义清理代码。当该类型被实例化后,最后不再使用时会调用Dispose方法清理,如果顺利清理,最后还会调用类型的析构函数。通常,如何实现了IDisposable接口,就不必再写上析构函数了。如果希望Dispose方法被自动调用,可以在实例化对象的代码包装在using语句块中,当执行完using块时会自动调用Dispose方法。

可能有人笑了,老周,你太逗了,这些基础知识谁不知道?当然,我说上面那些内容是为了绕个小圈子,以便进入主题。于是,我产生了一个疑问:是不是存在某些情景下,可能导致对象实例不会被回收呢?就算你调用了Dispose方法,就算你把变量设为null来解除引用,就算你调用GC类的方法来回收……

经过老周测试,还真有这种情况,而且很多朋友都很有可能会忽略,甚至在意识认知上误认为对象实例已经被回收,而实际上是没有回收的。

我简单说一下这种情形:

比如有一个静态类(静态类的成员必是静态的)A,里面有静态事件。随后在其他类的实例中处理A类的静态事件,并且处理事件的方法就位于这个实例对象上……

不急,我们还是看真实的例子吧。假如我定义了一个静态类MyChecker,它里面有个静态事件CheckEvent。

    public static class MyChecker
{
#region 静态事件
public static event EventHandler CheckEvent;
#endregion public static void CallEvent()
{
if (CheckEvent != null)
{
CheckEvent(new object(), EventArgs.Empty);
}
}
}

只要CallEvent方法被调用,CheckEvent事件会被引发。

然后,定义另一个类SampleClass,并在该类中处理刚才MyChecker中的静态事件。

    class SampleClass:IDisposable
{
public SampleClass()
{
MyChecker.CheckEvent += MyChecker_CheckEvent;
} void MyChecker_CheckEvent(object sender, EventArgs e)
{
new Form2().Show();
} ~SampleClass()
{
System.Diagnostics.Debug.WriteLine("\n看,析构函数调用了。\n");
} public void Dispose()
{
//……
}
}

在类的构造函数中,附加CheckEvent事件的处理,处理方法名为MyChecker_CheckEvent。

可能大家已经发现,老周写的SampleClass类有点恐怖气息,既实现了Dispose方法,怎么又写了析构函数,我这里写上析构函数是为了验证类的实例是否真的被清理,如果实例真的被回收,那么Debug类会在“输出”窗口中输出提示,如果没有提示输出,说明类的实例还霸占着内存。

接下来测试一下。

            SampleClass sc = new SampleClass();
await Task.Delay( * );
sc.Dispose();
sc = null;
GC.Collect();

实例化SampleClass后,然后Delay会暂停10秒,10秒钟过后会调用Dispose方法,并设置变量为null引用,我害怕不能及时清理,连GC.Collect方法也用上了。

而在等待这10秒期间,可以调用静态类的CallEvent方法来引发静态事件CheckEvent。

MyChecker.CallEvent();

按照一般理解,在10秒钟后,SampleClass实例应该被清理,并且在“输出”窗口会输出提示。

好,现在试一下。

……

实验结果表明,输出 窗口中连鸭毛都没有输出,这说明10秒钟后,SampleClass实例根本没有发生析构。于是又出问题了,这是怎么回事?SampleClass实例不是不存在引用了吗,怎么不发生析构?

其实我们忽略了一点:静态事件CheckEvent还跟SampleClass实例的方法绑定着呢,实质上,虽然将变量设为null,可是SampleClass实例中的MyChecker_CheckEvent方法还被静态类中的静态事件引用着,所以不会被回收。不知道你明白了没。

这个问题很多朋友在实际开发中都会忽略,还得意地以为Sample实例真的被回收了,实际上实例不会被回收,除非程序结束。因为MyChecker是静态类,不基于实例。如果MyChecker不是静态类,那么当MyChecker的实例释放后,SampleClass实例就可以被释放了。

那么,如何解决呢?很简单,只要在SampleClass类的Dispose方法中解除静态事件与方法的绑定即可,这样的话,静态事件就不再引用实例中的方法成员了,此时实例就可以发生析构了。

        public void Dispose()
{
MyChecker.CheckEvent -= MyChecker_CheckEvent;
}

这个例子研究,告诉我们:在类实例中处理静态事件时一定要小心

本示例的源码下载:http://files.cnblogs.com/files/tcjiaan/refsample.zip

好了,今天就扯到这里吧。

【.NET深呼吸】清理对象引用,有一个问题容易被忽略的更多相关文章

  1. SMON功能(一):清理临时段

    温故而知新 SMON功能(一) SMON(system monitor process)系统监控后台进程,有时候也被叫做system cleanup process,这么叫的原因是它负责完成很多清理( ...

  2. 大家一起和snailren学java-(四)初始化与清理

    初始化和清理,是一个生命周期的起始.在java中,对象的初始化和对象的回收是怎样的呢? 在开发c++的时候,有构造器的概念.即对象的创建,首先默认调用构造器进行初始化.在java中也有“构造器”.ja ...

  3. Docker镜像仓库清理的探索之路

    用友云开发者中心是基于Docker容器进行微服务架构应用的落地与管理.相信各位同学在使用的过程中,会发现随着Docker镜像的增多,占用磁盘空间也约来越多.这时我们需要清理私有镜像仓库中不需要的镜像. ...

  4. 如何清理Docker占用的磁盘空间?

    摘要:用了 Docker,好处挺多的,但是有一个不大不小的问题,它会一不小心占用太多磁盘,这就意味着我们必须及时清理. 作为一个有信仰的技术公司,我们Fundebug的后台采用了酷炫的全 Docker ...

  5. 如何清理Docker占用的磁盘空间?(转载)

    本文转载自https://blog.fundebug.com/2018/01/10/how-to-clean-docker-disk/ , 感谢原作者. 摘要:用了Docker,好处挺多的,但是有一个 ...

  6. (原创)一个和c#中Lazy<T>类似的c++ Lazy<T>类的实现

    在.net 4.0中增加一个延迟加载类Lazy<T>,它的作用是实现按需延迟加载,也许很多人用过.一个典型的应用场景是这样的:当初始化某个对象时,该对象引用了一个大对象,需要创建,这个对象 ...

  7. iOS利用SDWebImage实现缓存的计算与清理

    概述 可以仅仅清理图片缓存, 也可以清理所有的缓存文件(包括图片.视频.音频等). 详细 代码下载:http://www.demodashi.com/demo/10717.html 一般我们项目中的缓 ...

  8. linux shell 脚本 历史文件清理脚本,按天,按月,清理前N天的历史文件,删除指定大小历史文件,历史文件归档清理

    不知道大家那有没有要清理的这个事情.需要清理目录历史文件.可能后续也会有很多其他地方需要清理历史文件,可能会用到. 我这两天空闲写了个脚本,清理比较方便,有要进行清理的大量历史文件的话可以用. 脚本用 ...

  9. Linux线程退出、资源回收、资源清理的方法

    首先说明线程中要回收哪些资源,理解清楚了这点之后在思考资源回收的问题. 1.子线程创建时从父线程copy出来的栈内存; 线程退出有多种方式,如return,pthread_exit,pthread_c ...

随机推荐

  1. 2016 windows安装phing:安装成功

    21:39 2016/7/212016 windows安装phing:安装成功注意:出现错误时就去更新pear:参见:http://www.cnblogs.com/pinnasky/archive/2 ...

  2. BZOJ‘s Usaco 奶牛题集锦

    1230 线段树 1231 状压DP 1232 最小生成树 1527 贪心 1600 打表找规律 1601 最小生成树 1602 prime 1606 DP 1607 筛法 1609 DP 1610 ...

  3. 实现Myxls设置行高的功能(转)

    MyXLS是一个导出Excel的好工具,速度快,体积小,而且也不用担心使用Com生成Excel时资源释放的问题了.但是作者提供的代码没有设置行高 要实现这个效果,首先需要修改两个文件: 1.Row.c ...

  4. 使用系统自带的GCD的timer倒计时模板语句遇到的小坑。。

    今天折腾了下系统gcd的 但是如果不调用这句dispatch_source_cancel()那么这个timer根本不工作....解决方法如下: 实现一个倒计时用自带的gcd如此简洁.. 原因可能是如果 ...

  5. 强制 history 不记住特定的命令

    使用 HISTCONTROL 强制 history 不记住特定的命令将 HISTCONTROL 设置为 ignorespace,并在不想被记住的命令前面输入一个空格: # export HISTCON ...

  6. Google Authentication的实现 - Odoo 安全登录

    在前边的一篇文章中,我们提到了利用二次验证增强Odoo登录的可靠性:http://www.cnblogs.com/kfx2007/p/6023991.html 今天我们来具体实现这一步: 后端的实现 ...

  7. nodejs学习之events

    在node里许多对象都发出事件:一个net.Server对象每次一个连接到来,都发出一个事件,一个fs.readStream对象在文件打开时放出一个事件.所有能放出事件的对象都是event.Event ...

  8. F.I.S初探(前端工程化)

    云笔记:http://note.youdao.com/share/?id=7c4a2dcf118f0ad7bb52a36aaee46a7a&type=note   一.初识FIS 在做项目中遇 ...

  9. SQL Server恢复软件SysTools SQL Recovery/SysTools SQL Server Recovery Manager

    SQL Server恢复软件SysTools SQL Recovery/SysTools SQL Server Recovery Manager http://www.systoolsgroup.co ...

  10. 一个基于Orchard的开源CRM --coevery简介

    Coevery是开源的.NET Web平台项目,力争打造一个开放而鲁棒的CRM系统,采用Orchard架构,并使用AngularJS改善页面体验.作为一个后发优势的CRM 产品,Coevery 具有一 ...