.NET C#基础(9):资源释放 - 需要介入的资源管理
1. 什么是IDisposable?
IDisposable接口是一个用于约定可进行释放资源操作的接口,一个类实现该接口则意味着可以使用接口约定的方法Dispose来释放资源。其定义如下:public interface IDisposable
{
void Dispose();
}
1.1 资源
1.2 为什么要手动释放资源
IDisposable接口来对资源释放做出约定——当程序员看到一个类实现IDisposable接口时,就应该想到在使用完该类的实例后就应该调用其Dispose方法来及时释放资源。IDispose接口的类,在C#中你通常可以采用如下方式来释放资源:UnmanagedResource resource = /* ... */;
try
{
// 各种操作
}
finally
{
resource.Dispose();
}
(注:在finally中释放是为了确保即便运行时出错也可以顺利释放资源)
2:using
using (UnmanagedResource resource = /* ... */)
{
// 离开using的作用域后会自动调用resource的Dispose方法
}
// 或者如果不需要额外控制作用域的简写
using UnmanagedResource resource = /* ... */;
2. 如何实现IDisposable
2.1 不太完美的基本实现
IDisposable很容易实现,毕竟它只有一个方法需要实现,并且看上去只要在方法里释放掉需要释放的资源即可:class UnmanagedResource : IDisposable
{
public void Dispose()
{
// 释放需要释放的资源
}
}
2.2 如果使用者忘记了调用Dispose方法释放资源
Disposable接口的类的实例调用Dispose方法,但是,出于各种原因,或许是他是一名新手,或许他受到老板的催促,或许他昨天没睡好等等,这些都可能导致他没有仔细检查自己的代码。永远不要假设你的代码会被一直正确地使用,总得留下些兜底的东西,提高健壮性——把你的用户当做一个做着布朗运动的白痴,哪怕他可能是个经验丰富的程序员,甚至你自己。
Dispose方法释放资源,就留着让GC来调用释放。还好,C#允许你让GC来帮助你调用一些方法——通过终结器。~。如下:class UnmanagedResource : IDisposable
{
// UnmanagedResource的终结器
~UnmanagedResource()
{
// 一些操作
}
}
class UnmanagedResource : IDisposable
{
public void Dispose()
{
// 释放需要释放的资源
}
~UnmanagedResource()
{
// 终结器调用Dispose释放资源
Dispose();
}
}
2.3 手动调用了Dispose后,终结器再次调用Dispose
Dispose方法后,并不表示你就告诉了GC不要再调用它的终结器,实际上,在你调用Dispose方法后,GC还是会在某一时刻调用终结器,而由于我们在终结器里调用了Dispose方法,这会导致Dispose方法再次被调用——Double Free!Dispose方法里检查这个字段的值,一旦发现已经释放则过就立刻返回。如下:class UnmanagedResource : IDisposable
{
public void Dispose()
{
// 如果已经释放过就立刻返回
if (_disposed)
{
return;
}
// 释放需要释放的资源
// 标记已释放
_disposed = true;
}
~UnmanagedResource()
{
Dispose();
}
// 用于标记是否已经释放的字段
private bool _disposed;
}
Dispose重复调用是安全的。不过,要知道终结器是会影响性能的,因此为了性能考虑,我们还是希望在Dispose方法调用后阻止终结器的执行(毕竟这时候已经不需要GC兜底了)。而要实现这一目标十分简单,只需要在Dipose方法中使用GC.SuppressFinalize(this)告诉GC不要调用终结器即可。如下:class UnmanagedResource : IDisposable
{
public void Dispose()
{
if (_disposed)
{
return;
}
// 释放需要释放的资源
_disposed = true;
// 告诉GC不要调用当前实例(this)的终结器
GC.SuppressFinalize(this);
}
~UnmanagedResource()
{
Dispose();
}
private bool _disposed;
}
Dispose方法,就会“抑制”GC对终结器的调用;而让终结器调用Dispose也不会产生什么问题。2.4 不是任何时候都需要释放所有资源
class UnmanagedResource : IDisposable
{
// 其他代码
private FileStream _fileStream;
}
FileStream是一个实现了IDisposable的类,也就是说,FileStream也需要进行释放。UnmanagedResource不仅要释放自己的非托管资源,还要释放FileStream。你或许认为只需要在UnmanagedResource的Dispose方法中调用一下FileStream的Dispose方法就行。如下:class UnmanagedResource : IDisposable
{
// 其它代码
public void Dispose()
{
// 其他代码
_fileStream.Dispose();
// 其它代码
}
private FileStream _fileStream;
}
UnmanagedResource的Dispose方法是由终结器调用的会发生什么?FileStream的终结器先被调用了,执行过了其Dispose方法释放资源,随后UnmanagedResource的终结器调用Dispose方法时会再次调用FileStream的Dispose方法——Double Free, Again。Dispose方法是由终结器调用的,就不应该手动释放那些本身就实现了终结器的托管资源——这些资源的终结器很可能先被执行。仅当手动调用Dispose方法时才手动释放那些实现了终结器的托管资源。Dispose方法,用一个参数来指示Dispose是否释放托管资源。稍作调整,实现如下:class UnmanagedResource : IDisposable
{
// 其它代码
private void Dispose(bool disposing)
{
// 其他代码
if (disposing)
{
// 释放托管资源
_fileStream.Dispose();
}
// 释放非托管资源
// 其它代码
}
}
disposing参数的Dispose(bool disposing)方法,当disposing为true时,同时释放托管资源和非托管资源;当disposing为false时,仅释放托管资源。另外,为了不公开不必要的接口,将其声明为private。Dispose方法和终结器中按需调用Dispose(bool disposing)方法即可。class UnmanagedResource : IDisposable
{
// 其它代码
public void Dispose()
{
// disposing=true,手动释放托管资源
Dispose(true);
GC.SuppressFinalize(this);
}
~UnmanagedResource()
{
// disposing=false,不释放托管资源,交给终结器释放
Dispose(false);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// 释放托管资源
}
// 释放非托管资源
_disposed = true;
}
}
2.5 考虑一下子类的资源释放
UnmanagedResource的子类:class HandleResource : UnmanagedResource
{
private HandlePtr _handlePtr;
}
HandleResource有自己的资源HandlePtr,显然如果只是简单继承UnmanagedResource的话,UnmanagedResource的Dispose方法并不能释放HandleResource的HandlePtr。UnmanagedResource的Dispose方法声明为virtual并在HandleResource里覆写;或者在HandleResource里使用new重新实现Dispose似乎都可以:// 使用多态
class UnmanagedResource : IDisposable
{
public virtual void Dispose() { /* ... */}
}
class HandleResource : UnmanagedResource
{
public override void Dispose() { /* ... */}
}
// 重新实现
class UnmanagedResource : IDisposable
{
public void Dispose() { /* ... */}
}
class HandleResource : UnmanagedResource
{
public new void Dispose() { /* ... */}
}
HandleResource重复做那些在它的父类UnmanagedResource做过的事——解决重复释放、定义终结器以及区分对待托管和非托管资源。这太不“继承了”——显然,有更好的实现方法。
UnmanagedResource的的Dispose(bool disposing)方法访问权限更改为protected,并修饰为virtual,以让子类访问/覆盖:class UnmanagedResource : IDisposable
{
protected virtual void Dispose(bool disposing) { /* ... */ }
}
Dispose(bool disposing)来实现自己想要的释放功能:
class UnmanagedResource : IDisposable
{
protected override void Dispose(bool disposing)
{
// 其他代码
base.Dispose(disposing);
}
}
Dispose(bool disposing)是虚方法,因此父类UnmanagedResource的终结器和Dispose方法中对Dispose(bool disposing)的调用会受多态的影响,调用到正确的释放方法,故子类可以不必再做那些重复工作。3. 总结
3.1 代码总览
class UnmanagedResource : IDisposable
{
// 对IDisposable接口的实现
public void Dispose()
{
// 调用Dispose(true),同时释放托管资源与非托管资源
Dispose(true);
// 让GC不要调用终结器
GC.SuppressFinalize(this);
}
// UnmanagedResource的终结器
~UnmanagedResource()
{
// 调用Dispose(false),仅释放非托管资源,托管资源交给GC处理
Dispose(false);
}
// 释放非托管资源,并可以选择性释放托管资源,且可以让子类覆写的Dispose(bool disposing)方法
protected virtual void Dispose(bool disposing)
{
// 防止重复释放
if (_disposed)
{
return;
}
// disposing指示是否是否托管资源
if (disposing)
{
// 释放托管资源
}
// 释放非托管资源
// 标记已释放
_disposed = true;
}
}
参考资料/更多资料:
【1】:IDisposable 接口
【2】:实现 Dispose 方法
.NET C#基础(9):资源释放 - 需要介入的资源管理的更多相关文章
- 基于webrtc的资源释放问题(二)
基于webrtc的资源释放问题(二) ——建立连接的过程中意外中断 应用背景: 我们在打电话的时候会不会遇到这种情况?打电话的时候未接通之前挂掉了电话,或者在接通之后建立的连接的过程中挂掉电话? 特别 ...
- 基于webrtc的资源释放问题(一)
基于webrtc的资源释放问题(一) ——重复释放webrtc的相关资源 背景: 视频通讯大都只是作为一个功能存在于各种应用中,比如微信,qq .既然只是应用的一部分,这样就涉及反复的开启和关闭视频通 ...
- C#资源释放
转自:http://www.cnblogs.com/psunny/archive/2009/07/07/1518812.html 深刻理解C#中资源释放 今天我的一个朋友看到我写的那篇<C#中用 ...
- TList,TObjectList 使用——资源释放
TOjectList = Class (Tlist); TOjectList继承Tlist,从名字上看就可以知道它是专门为对象列表制作的,那么他到底丰富了那些功能呢? 首先是 TObject 作为对象 ...
- Delphi中关于资源释放(Free,Relealse,FreeAndNil)
根据日常编程经验,得出一些Delphi中关于资源释放的体会. 假如有对象Obj为TObject类型: 1) Obj.Free直接释放资源后,调用OnDestroy事件,但是没有将Obj指针值置为Nil ...
- 深刻理解C#中资源释放
今天我的一个朋友看到我写的那篇<C#中用AJAX验证用户登录>时,给我指出了点小毛病.就是在用户登录时,如果用户登录失败,在下面这段代码中,都会new出来一个User对象,如果连续登录失败 ...
- Unity3d: 资源释放时存储空间不足引发的思考和遇到的问题
手机游戏第一次启动基本上都会做资源释放的操作,这个时候需要考虑存储空间是否足够,但是Unity没有自带获取设备存储空间大小的 接口,需要调用本地方法分别去android或ios获取,这样挺麻烦的.而且 ...
- .net 资源释放(托管资源和非托管资源)
1.托管资源 像int.float.DateTime等都是托管资源:net中80%的资源都是托管资源: 托管资源的回收通过GC(垃圾回收器)自动释放分配给该对象的内存,但无法预测进行垃圾回收的时间,我 ...
- C#资源释放及Dispose、Close和析构方法
https://www.cnblogs.com/luminji/archive/2011/01/05/1926468.html C#资源释放及Dispose.Close和析构方法 备注:此文的部分 ...
- atitit.资源释放机制--attilax总结
atitit.资源释放机制--attilax总结 1. .全手工, 1 2. 引用计数, 1 2.1. 成本也显而易见. 1 2.2. 循环引用的问题, 2 2.3. 引用计数方式事实上也有经典的卡顿 ...
随机推荐
- 代码随想录算法训练营Day27 回溯算法|39. 组合总和 40.组合总和II 131.分割回文串
代码随想录算法训练营 39. 组合总和 题目链接:39. 组合总和 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 ta ...
- 浅析HTTPS的通信机制
什么是HTTPS? HTTPS 是在HTTP(Hyper Text Transfer Protocol)的基础上加入SSL(Secure Sockets Layer),在HTTP的基础上通过传输加密和 ...
- Python连接es笔记三之es更新操作
本文首发于公众号:Hunter后端 原文链接:Python连接es笔记三之es更新操作 这一篇笔记介绍如何使用 Python 对数据进行更新操作. 对于 es 的更新的操作,不用到 Search() ...
- Python-Loguru:让记录日志更装13
" 今天勇哥来介绍一款让日志记录在 Python 中变得更加轻松愉快的库--Loguru.它提供了强大的功能和简洁的接口,使我们能够以更加灵活和直观的方式记录和管理日志信息,据说比loggi ...
- 如何使用Stable Diffusion生成艺术二维码?
硬件准备 物理内存:至少16G(8G直接安装阶段就卡死) N卡:此处我使用GTX 1660 6G (2019年双12购买) 操作系统 windows 11 软件准备 网络要通畅 git: https: ...
- .Net7基础类型的优化和循环克隆优化
前言 .Net7里面对于基础类型的优化,是必不可少的.因为这些基础类型基本上都会经常用到,本篇除了基础类型的优化介绍之外,还有一个循环克隆的优化特性,也一并看下. 概括 1.基础类型优化 基础类型的优 ...
- 一次性掌握innodb引擎如何解决幻读和不可重复读
了解mysql的都知道,在mysql的RR(可重复)隔离级别下解决了幻读和不可重复.你知道RR下是怎么解决的吗,很多人会回答是通过MVCC和next-key解决的,具体是怎么解决的,今天来重点分析下. ...
- 微信小程序脚手架火爆来袭,集成 Taro、uniapp 第三方模版,支持小程序 CI 上传,预览,发布
微信小程序脚手架 @wechat-mp/cli 微信小程序脚手架,集成 Taro.uniapp 第三方模版,支持小程序 CI 上传,预览,发布 注意事项 需要在微信公众平台开发管理-开发设置-IP白名 ...
- mysql截取函数,拼接函数,大写函数例子
题目:这题目是牛客网sql题,因为牵扯到3个函数,都是自己没怎么用过的,所以记录一下. 答案:是别人的解题思路 可以看出在mysql中提供的函数可以供我们使用来操作字段,非常的方便
- Java扩展Nginx之六:两大filter
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<Java扩展Nginx> ...