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。
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
void IDisposable.Dispose()
{
_dialog.Unadvise(_eventCookie);
}
以上代码:dialog是COM组件,属于非托管资源,调用Unadvise()释放资源,_eventCookie 是注册事件时的Handle类似号码牌,通过这个标识来Unadvise()相应的资源。
using 语句是 try-finally 块的语法糖,确保 Dispose 一定会被调用。使用using语句中调用了非托管的资源file(OS Handle),GC没办法得知其存在(只能得知存储在堆上的资源),所以没办法释放它,必须使用Disposable()进行释放。
// 语法糖
using (var resource = new MyResource())
{
// 使用资源
}
// 编译器实际生成的代码
var resource = new MyResource();
try
{
// 使用资源
}
finally
{
if (resource != null)
{
((IDisposable)resource).Dispose();
}
}
当类实现了 IDisposable,使用时务必包裹在 using 块中,反之亦然。查看了OpenFileDialog类的继承和实现,并未实现释放句柄的接口IDisposable,所以使用using时报错了。
解决方法:手动实现Dispose()?
//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
安全性(句柄回收攻击) :
- 如果你手动管理 `IntPtr` 句柄,且在释放前 GC 运行了终结器,或者在释放后操作系统立即分配了相同的句柄值给另一个资源,可能会造成权限混淆或数据损坏。
可靠性(AppDomain 卸载) :
- 在旧的 .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;
// 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;
}
}
}