GC、Dispose、Unmanaged Resource 和 Managed Resource

1.遇到的问题

跑代码时使用using时出现问题

using (OpenFileDialog openFileDialog = new OpenFileDialog())

报错

using 语句中使用的类型必须可隐式转换为"System.IDisposable"

代码是昨天写在winform中直接复制过来的,在WPF中运用报错。查看了OpenFileDialog的继承和实现

winform :中OpenFileDialog 继承链:OpenFileDialogFileDialogCommonDialogComponentMarshalByRefObject, IComponent, IDisposableobject

winform是通过using System.ComponentModel来实现继承IDisposable。

csharp 复制代码
    public event EventHandler Disposed
        {
            add
            {
                Events.AddHandler(EventDisposed, value);
            }
            remove
            {
                Events.RemoveHandler(EventDisposed, value);
            }
        }

        ~Component()
        {
            Dispose(disposing: false);
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposing)
            {
                return;
            }

            lock (this)
            {
                if (site != null && site.Container != null)
                {
                    site.Container.Remove(this);
                }

                if (events != null)
                {
                    ((EventHandler)events[EventDisposed])?.Invoke(this, EventArgs.Empty);
                }
            }
        }

WPF中OpenFileDialogFileDialogCommonDialog Microsoft.Win32下的公共抽象类CommonDialog并未继承IDisposable。其下 private sealed class VistaDialogEvents继承了IDisposable

scss 复制代码
             void IDisposable.Dispose()
            {
                _dialog.Unadvise(_eventCookie);
            }

以上代码:dialog是COM组件,属于非托管资源,调用Unadvise()释放资源,_eventCookie 是注册事件时的Handle类似号码牌,通过这个标识来Unadvise()相应的资源。

using 语句是 try-finally 块的语法糖,确保 Dispose 一定会被调用。使用using语句中调用了非托管的资源file(OS Handle),GC没办法得知其存在(只能得知存储在堆上的资源),所以没办法释放它,必须使用Disposable()进行释放。

csharp 复制代码
// 语法糖
using (var resource = new MyResource())
{
    // 使用资源
}

// 编译器实际生成的代码
var resource = new MyResource();
try
{
    // 使用资源
}
finally
{
    if (resource != null)
    {
        ((IDisposable)resource).Dispose();
    }
}

当类实现了 IDisposable,使用时务必 包裹在 using 块中,反之亦然。查看了OpenFileDialog类的继承和实现,并未实现释放句柄的接口IDisposable,所以使用using时报错了。

解决方法:手动实现Dispose()?

arduino 复制代码
            //finally
            //{
            //    ((IDisposable)openFileDialog).Dispose();
            //}

报错了,无法将类型"Microsoft.Win32.OpenFileDialog"转换为"System.IDisposable" 是因为private的限制以及是显示实现IDisposable吗? 直接放弃Dispose() ,由GC的finalizer回收,运行成功。

2.Unmanaged Resource 和 Managed Resource、OS Handle

托管资源managed resource : 由CLR (Common Language Runtime )控制,储存在托管堆Managed Heap 上,包括:字符串、集合、自定义类等,由GC(Garbage Collector)控制回收。回收时机是非确定的(None_deterministic),即系统空闲或内存不足时回收(前提是对象不再被引用)
非托管资源Unmanaged Resource :由操作系统OS控制,主要包括OS Handle、数据库(SQL等)网络连接(Socket:TCP/IP)、COM类,无法由GC自由释放,由Disposable手动释放,释放方式是确定性的。(Deterministic_CleanUp

OS Handle 系统句柄

OS Handle直接由操作系统内核管理,本质是个int或者IntPtr(指针)。主要是用来标识和管理资源(文件、进程、线程、window、socket)(类似号码牌),应用程序是不允许直接访问操作系统的核心资源的,想要进行交互,必须通过句柄间通过操作系统间接操作。

比喻:就像相亲,操作系统是中间人,应用程序(女)领一个号码牌,像中间人询问想要接触的号码牌(男),中间人通过号码牌找到程序要的男方,询问女想要的信息,然后中间人再把信息资源带回去给女方应用程序,然后他俩结束,收回应用程序中拥有的号码牌。(释放句柄------防止资源占用:其他人也要了解男方,以及资源堵塞:还有很多女方在排队呢)

危害

这些直接由操作系统控制的句柄,GC是不知道的,所以没办法进行释放,所以要通过Dispose()进行释放。未释放可能造成:
资源泄露(Handle leak) : 创建的对象被GC回收了,但是Handle并没有被释放,GC并不知道,这时这个Handle就会一直被占用,每个进程打开的Handle数是有限制的(Window 16000),一旦超过这个数量,进程会崩溃。IOException: Too many open filesOutOfMemoryException

安全性(句柄回收攻击)

markdown 复制代码
-   如果你手动管理 `IntPtr` 句柄,且在释放前 GC 运行了终结器,或者在释放后操作系统立即分配了相同的句柄值给另一个资源,可能会造成权限混淆或数据损坏。

可靠性(AppDomain 卸载)

c 复制代码
-   在旧的 .NET Framework 中,普通的终结器(Finalizer)在某些极端情况(如 AppDomain 卸载、线程 abort)下可能不会被执行,导致句柄永远无法释放。

处理方法

SafeHandle:相当于将IntPtr和Dispose封装起来?

GC Dispose Finalizer

  • 职责 :GC 只负责回收托管堆 (Managed Heap) 上的内存
  • 局限性 :GC 无法 自动释放非托管资源。
  • 回收时机:非确定性 (Non-deterministic)。当内存不足或系统空闲时触发,你不知道它具体什么时候运行。
  • 代 (Generations) :0 代、1 代、2 代。对象存活越久,代越高,回收频率越低

Dispose():手动准确的释放所有资源(托管和非托管),由用户调用。

Finalizer:用戶並未手動關閉非托管資源,GC的托底機制,首先GC標記未被關閉的非托管资源,进入Finalizer队列,释放非托管资源,下一轮GC再回收内存

复制代码

public class MyResource : IDisposable { private bool _disposed = false;

scss 复制代码
// 1. 非托管资源句柄
private IntPtr _handle; 
// 2. 托管资源
private Stream _stream; 

// 3. 公开 Dispose 方法 (供开发者调用)
public void Dispose()
{
    Dispose(true);
    // 4. 告诉 GC 不需要运行终结器了 (性能优化)
    GC.SuppressFinalize(this);
}

// 5. 终结器 (供 GC 调用,作为安全网)
~MyResource()
{
    Dispose(false);
}

// 6. 核心清理逻辑
protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            // 【 dispose=true 】: 由用户代码调用
            // 可以安全地释放托管资源
            _stream?.Dispose(); 
            _stream = null;
        }

        // 【 dispose=true/false 】: 都要释放非托管资源
        // 因为无论是用户手动释放,还是 GC 兜底,非托管资源都必须关掉
        if (_handle != IntPtr.Zero)
        {
            CloseHandle(_handle);
            _handle = IntPtr.Zero;
        }

        _disposed = true;
    }
}

}

复制代码
相关推荐
蝎子莱莱爱打怪3 小时前
OpenClaw 从零配置指南:接入飞书 + 常用命令 + 原理图解
java·后端·ai编程
倚栏听风雨4 小时前
【ES避坑指南】明明存的是 "CodingAddress",为什么 term 查询死活查不到?彻底搞懂 text 和 keyword
后端
程序员爱钓鱼4 小时前
Go 操作 Windows COM 自动化实战:深入解析 go-ole
后端·go·排序算法
回家路上绕了弯4 小时前
深入解析Agent Subagent架构:原理、协同逻辑与实战落地指南
分布式·后端
子玖4 小时前
实现微信扫码注册登录-基于参数二维码
后端·微信·go
IT_陈寒4 小时前
JavaScript代码效率提升50%?这5个优化技巧你必须知道!
前端·人工智能·后端
IT_陈寒4 小时前
Java开发必知的5个性能优化黑科技,提升50%效率不是梦!
前端·人工智能·后端
东风t西瓜4 小时前
飞书项目与多维表格双向同步
后端
初次攀爬者4 小时前
Kafka的Rebalance基础介绍
后端·kafka