C#学习笔记(二) 标准Dispose模式的实现

2024-01-14 21:08

本文主要是介绍C#学习笔记(二) 标准Dispose模式的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!



需要明确一下C#程序(或者说.NET)中的资源。简单的说来,C#中的每一个类型都代表一种资源,而资源又分为两类:

托管资源:由CLR管理分配和释放的资源,即由CLR里new出来的对象;

非托管资源:不受CLR管理的对象,windows内核对象,如文件、数据库连接、套接字、COM对象等;

毫无例外地,如果我们的类型使用到了非托管资源,或者需要显式释放的托管资源,那么,就需要让类型继承接口IDisposable。这相当于是告诉调用者,该类型是需要显式释放资源的,你需要调用我的Dispose方法。

不过,这一切并不这么简单,一个标准的继承了IDisposable接口的类型应该像下面这样去实现。这种实现我们称之为Dispose模式:

复制代码
   
public class SampleClass : IDisposable
{
//演示创建一个非托管资源
private IntPtr nativeResource = Marshal.AllocHGlobal(100);
//演示创建一个托管资源
private AnotherResource managedResource = new AnotherResource();
private bool disposed = false;

/// <summary>
/// 实现IDisposable中的Dispose方法
/// </summary>
public void Dispose()
{
//必须为true
Dispose(true);
//通知垃圾回收机制不再调用终结器(析构器)
GC.SuppressFinalize(this);
}

/// <summary>
/// 不是必要的,提供一个Close方法仅仅是为了更符合其他语言(如C++)的规范
/// </summary>
public void Close()
{
Dispose();
}

/// <summary>
/// 必须,以备程序员忘记了显式调用Dispose方法
/// </summary>
~SampleClass()
{
//必须为false
Dispose(false);
}

/// <summary>
/// 非密封类修饰用protected virtual
/// 密封类修饰用private
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
if (disposing)
{
// 清理托管资源
if (managedResource != null)
{
managedResource.Dispose();
managedResource
= null;
}
}
// 清理非托管资源
if (nativeResource != IntPtr.Zero)
{
Marshal.FreeHGlobal(nativeResource);
nativeResource
= IntPtr.Zero;
}
//让类型知道自己已经被释放
disposed = true;
}

public void SamplePublicMethod()
{
if (disposed)
{
throw new ObjectDisposedException("SampleClass", "SampleClass is disposed");
}
//省略
}
}
复制代码

在Dispose模式中,几乎每一行都有特殊的含义。

在标准的Dispose模式中,我们注意到一个以~开头的方法:

复制代码
   
/// <summary>
/// 必须,以备程序员忘记了显式调用Dispose方法
/// </summary>
~SampleClass()
{
//必须为false
Dispose(false);
}
复制代码

这个方法叫做类型的终结器。提供终结器的全部意义在于:我们不能奢望类型的调用者肯定会主动调用Dispose方法,基于终结器会被垃圾回收器调用这个特点,终结器被用做资源释放的补救措施。

一个类型的Dispose方法应该允许被多次调用而不抛异常。鉴于这个原因,类型内部维护了一个私有的布尔型变量disposed:

        private bool disposed = false;

在实际处理代码清理的方法中,加入了如下的判断语句:

   
if (disposed)
{
return;
}
//省略清理部分的代码,并在方法的最后为disposed赋值为true
disposed = true;

这意味着类型如果被清理过一次,则清理工作将不再进行。

应该注意到:在标准的Dispose模式中,真正实现IDisposable接口的Dispose方法,并没有实际的清理工作,它实际调用的是下面这个带布尔参数的受保护的虚方法:

复制代码
   
/// <summary>
/// 非密封类修饰用protected virtual
/// 密封类修饰用private
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
//省略代码
}
复制代码
之所以提供这样一个受保护的虚方法,是为了考虑到这个类型会被其他类继承的情况。如果类型存在一个子类,子类也许会实现自己的Dispose模式。受保护的虚方法用来提醒子类必须在实现自己的清理方法的时候注意到父类的清理工作,即子类需要在自己的释放方法中调用base.Dispose方法。

还有,我们应该已经注意到了真正撰写资源释放代码的那个虚方法是带有一个布尔参数的。之所以提供这个参数,是因为我们在资源释放时要区别对待托管资源和非托管资源。

在供调用者调用的显式释放资源的无参Dispose方法中,调用参数是true:

   
public void Dispose()
{
//必须为true
Dispose(true);
//其他省略
}

这表明,这个时候代码要同时处理托管资源和非托管资源。

在供垃圾回收器调用的隐式清理资源的终结器中,调用参数是false:

   
~SampleClass()
{
//必须为false
Dispose(false);
}

这表明,隐式清理时,只要处理非托管资源就可以了。

那么,为什么要区别对待托管资源和非托管资源。在认真阐述这个问题之前,我们需要首先弄明白:托管资源需要手动清理吗?不妨先将C#中的类型分为两类,一类继承了IDisposable接口,一类则没有继承。前者,我们暂时称之为非普通类型,后者我们称之为普通类型。非普通类型因为包含非托管资源,所以它需要继承IDisposable接口,但是,这个包含非托管资源的类型本身,它是一个托管资源。所以说,托管资源需要手动清理吗?这个问题的答案是:托管资源中的普通类型,不需要手动清理,而非普通类型,是需要手动清理的(即调用Dispose方法)。

Dispose模式设计的思路基于:如果调用者显式调用了Dispose方法,那么类型就该按部就班为自己的所以资源全部释放掉。如果调用者忘记调用Dispose方法,那么类型就假定自己的所有托管资源(哪怕是那些上段中阐述的非普通类型)全部交给垃圾回收器去回收,而不进行手工清理。理解了这一点,我们就理解了为什么Dispose方法中,虚方法传入的参数是true,而终结器中,虚方法传入的参数是false。

 

注意:我们提到了需要及时释放资源,却并没有进一步细说是否需要及时让引用等于null这一点。有一些人认为等于null可以帮助垃圾回收机制早点发现并标识对象是垃圾。其他人则认为这没有任何帮助。下一篇“引用类型赋值为null与加速垃圾回收”我们再细说这一点。

原文地址:http://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html

这篇关于C#学习笔记(二) 标准Dispose模式的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/606515

相关文章

Nginx实现高并发的项目实践

《Nginx实现高并发的项目实践》本文主要介绍了Nginx实现高并发的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录使用最新稳定版本的Nginx合理配置工作进程(workers)配置工作进程连接数(worker_co

python中列表list切分的实现

《python中列表list切分的实现》列表是Python中最常用的数据结构之一,经常需要对列表进行切分操作,本文主要介绍了python中列表list切分的实现,文中通过示例代码介绍的非常详细,对大家... 目录一、列表切片的基本用法1.1 基本切片操作1.2 切片的负索引1.3 切片的省略二、列表切分的高

基于Python实现一个PDF特殊字体提取工具

《基于Python实现一个PDF特殊字体提取工具》在PDF文档处理场景中,我们常常需要针对特定格式的文本内容进行提取分析,本文介绍的PDF特殊字体提取器是一款基于Python开发的桌面应用程序感兴趣的... 目录一、应用背景与功能概述二、技术架构与核心组件2.1 技术选型2.2 系统架构三、核心功能实现解析

C++ Primer 标准库vector示例详解

《C++Primer标准库vector示例详解》该文章主要介绍了C++标准库中的vector类型,包括其定义、初始化、成员函数以及常见操作,文章详细解释了如何使用vector来存储和操作对象集合,... 目录3.3标准库Vector定义和初始化vector对象通列表初始化vector对象创建指定数量的元素值

C#中的 Dictionary常用操作

《C#中的Dictionary常用操作》C#中的DictionaryTKey,TValue是用于存储键值对集合的泛型类,允许通过键快速检索值,并且具有唯一键、动态大小和无序集合的特性,常用操作包括添... 目录基本概念Dictionary的基本结构Dictionary的主要特性Dictionary的常用操作

使用Python实现表格字段智能去重

《使用Python实现表格字段智能去重》在数据分析和处理过程中,数据清洗是一个至关重要的步骤,其中字段去重是一个常见且关键的任务,下面我们看看如何使用Python进行表格字段智能去重吧... 目录一、引言二、数据重复问题的常见场景与影响三、python在数据清洗中的优势四、基于Python的表格字段智能去重

Spring AI集成DeepSeek实现流式输出的操作方法

《SpringAI集成DeepSeek实现流式输出的操作方法》本文介绍了如何在SpringBoot中使用Sse(Server-SentEvents)技术实现流式输出,后端使用SpringMVC中的S... 目录一、后端代码二、前端代码三、运行项目小天有话说题外话参考资料前面一篇文章我们实现了《Spring

C# winform操作CSV格式文件

《C#winform操作CSV格式文件》这篇文章主要为大家详细介绍了C#在winform中的表格操作CSV格式文件的相关实例,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录实例一实例效果实现代码效果展示实例二实例效果完整代码实例一实例效果当在winform界面中点击读取按钮时 将csv中

Nginx中location实现多条件匹配的方法详解

《Nginx中location实现多条件匹配的方法详解》在Nginx中,location指令用于匹配请求的URI,虽然location本身是基于单一匹配规则的,但可以通过多种方式实现多个条件的匹配逻辑... 目录1. 概述2. 实现多条件匹配的方式2.1 使用多个 location 块2.2 使用正则表达式

使用Apache POI在Java中实现Excel单元格的合并

《使用ApachePOI在Java中实现Excel单元格的合并》在日常工作中,Excel是一个不可或缺的工具,尤其是在处理大量数据时,本文将介绍如何使用ApachePOI库在Java中实现Excel... 目录工具类介绍工具类代码调用示例依赖配置总结在日常工作中,Excel 是一个不可或缺的工http://