GC、Dispose、Unmanaged Resource 和 Managed Resource

1.遇到的问题

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

using (OpenFileDialog openFileDialog = new OpenFileDialog())

报错

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

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

winform :中OpenFileDialog 继承链:OpenFileDialog → FileDialog → CommonDialog → Component → MarshalByRefObject, IComponent, IDisposable → object

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中OpenFileDialog → FileDialog → CommonDialog 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 files 或 OutOfMemoryException

安全性(句柄回收攻击) :

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;
    }
}

}

复制代码
相关推荐
金銀銅鐵8 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
念何架构之路9 小时前
go-grpc设计思想
开发语言·后端·golang
system_error_x9 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
步行cgn11 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
码事漫谈12 小时前
AI 读你的提示词,其实很没耐心
后端
用户83562907805112 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我1234513 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒13 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记13 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端
Mk5987813 小时前
P4:把 Agent 的记忆搬进 Redis —— AgentScope-Java 工程化实战
后端