以前曾看见过这样一个问题:托管代码会不会导致内存泄漏。自己对GC的了解也不是很深,但还是比较赞成这样的观点:托管代码不会产生内存泄漏,除非你没有正确释放非托管资源。
今天看到一个非常有趣的例子,关于没有释放事件的Handler导致的内存泄漏。
以前对于释放Handler的观念是一点也没有,这主要因为没此方面的意识,没有养成好的习惯。只知道当关心这个事件的时候就注册一下, 暂时不关心了就移除掉。却从来没有想到最终不移除不必要的Handler会导致此类无法被正常回收,导致不必要的内存浪费。
您知道WeakReference吗? 您知道用此种方式实现的Event吗?
以前曾看见过这样一个问题:托管代码会不会导致内存泄漏。自己对GC的了解也不是很深,但还是比较赞成这样的观点:托管代码不会产生内存泄漏,除非你没有正确释放非托管资源。
今天看到一个非常有趣的例子,关于没有释放事件的Handler导致的内存泄漏。
以前对于释放Handler的观念是一点也没有,这主要因为没此方面的意识,没有养成好的习惯。只知道当关心这个事件的时候就注册一下, 暂时不关心了就移除掉。却从来没有想到最终不移除不必要的Handler会导致此类无法被正常回收,导致不必要的内存浪费。
事情是这样的,今天在看项目Source Code的时候发现一个有趣的字眼:"WeakEvent". 自己以前对WeakReference有点了解,所以就好奇地看看这是个啥玩意。
发现其是一种通过弱引用实现的Delegate。因为没有太多的注释,所有不知其为啥用此种方式来封装事件。于是顺手Google了一下,找到了一篇关于weak event的非常有意思的文章。
文章里提出了一个问题,场景如下:

UnRelease Event Handler
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;

namespace ConsoleApplication16


{
class DisplaySettingsListener

{
byte[] m_ExtraMemory = new byte[1000000];

public DisplaySettingsListener()

{
SystemEvents.DisplaySettingsChanged += new EventHandler(ehDisplaySettingsChanged);
}

private void ehDisplaySettingsChanged(object sender, EventArgs e)

{
}
}

class Program

{
static void DisplayMemory()

{
Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
}

static void Main()

{
DisplayMemory();
Console.WriteLine();
for (int i = 0; i < 5; i++)

{
Console.WriteLine("--- New Listener #{0} ---", i + 1);
DisplaySettingsListener listener = new DisplaySettingsListener();
listener = null;
GC.Collect();

DisplayMemory();
}
Console.Read();
}

}
}
运行的结果如下:
虽然我们释放了对listener的引用,并且强制GC进行回收,但我们可以看到其内存占用量还是变大了,出乎了我的意料。
这就是该文作者指出的事件列表里保存的是一个强引用而非弱引用。虽然上面释放了listener变量对Listener实例的引用,但因为仍然在DisplaySettingsChanged事件列表里保存了对Listener实例的引用,导致Listener实例并不能被垃圾回收(有人引用,自然不会回收)。
那么接下来看看下面的代码:

Release Event Hanlder
class DisplaySettingsListener : IDisposable

{
byte[] m_ExtraMemory = new byte[1000000];

public DisplaySettingsListener()

{
SystemEvents.DisplaySettingsChanged += new EventHandler(ehDisplaySettingsChanged);
}

private void ehDisplaySettingsChanged(object sender, EventArgs e)

{
}


IDisposable Members#region IDisposable Members

public void Dispose()

{
SystemEvents.DisplaySettingsChanged -= new EventHandler(ehDisplaySettingsChanged);
}

#endregion
}

class Program

{
static void DisplayMemory()

{
Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
}

static void Main()

{
DisplayMemory();
Console.WriteLine();
for (int i = 0; i < 5; i++)

{
Console.WriteLine("--- New Listener #{0} ---", i + 1);
DisplaySettingsListener listener = new DisplaySettingsListener();
listener.Dispose();
listener = null;
GC.Collect();

DisplayMemory();
}
Console.Read();
}

}
运行结果如下:
结果是不是正如您猜测的呢:)。已经成功地回收了listener实例。 不知为何从432944字节变到446980字节,哪位高手赐教一下啊:)
详情可以看原文 The Problem With Delegates
在后续的文章中作者类似文章开头提到的Weak Event来解决这个问题: Solving the Problem with Events: Weak Event Handlers
也许您觉得写这样的一个Weak Event没有必要或者显得麻烦,但您一定要记得及时地在必要的地方调用 -= 取消不再关心的事件。本文的目的也只是在此方面提个善意的提醒。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述