C#高级编程七十四天----释放非托管资源

2023-11-21 10:48

本文主要是介绍C#高级编程七十四天----释放非托管资源,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

释放非托管资源

在介绍释放非托管资源的时候,我觉得有必要先来认识一下啥叫非托管资源,既然有非托管资源,肯定有托管资源.

 

 

托管资源指的是.net可以自棕进行回收的资源,主要是指托管堆上分配的内存资源.托管资源的回收工作是不需要人工干预的,.net运行库在合适的调用垃圾回收器进行回收.

 

非托管资源指的是.net不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等.这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法.默认情况下,方法是空的,对于非托管资源,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源.

 

 

.net,Object.Finalize()方法是无法重载的,编译器是根据类的析构函数来自动生成Object.Finalize()方法的,所以对于包含非托管资源的类,可以将释放非托管资源的代码放在析构函数中.

 

注意,不能再析构函数中释放托管资源,因为析构函数是由垃圾回收器调用的,可能在析构函数调用之前,类包含的托管资源已经被回收了,从而导致无法预知的结构.

 

 

本来呢,如果按照上面做法,非托管资源也能够由垃圾回收器进行回收,但是非托管资源一般是有限的,比较宝贵的,而垃圾回收器是由CLR(不知道CLR的看我上一篇文章)自动调用的,这样就无法保证一级的释放非托管资源,因此定义了一个Dispose()方法,让使用者能够手动的释放非托管资源.Dispose()方法是防雷的托管资源和非托管资源,使用者手动调用此方法后,垃圾回收期不会对此类实例再次进行回收.Dispose()方法是有使用者调用的,在调用时,类的托管资源和非托管资源肯定都还没有被回收,所以可以同时回收这两者资源.

 

微软为非托管资源的回收专门定义了一个接口:IDisposable,接口中只能包含一个Dispose()方法.任何包含非托管资源的类,都应该继承此接口.

 

 

在一个包含非托管资源的类中,关于资源释放的标准做法是:

(1)  继承IDisposeable接口;

(2) 事项Dispose()方法,在其中释放托管资源和非托管资源,并将对象本身从垃圾回收器中移除(垃圾回收器不在回收此资源);

(3) 实现类析构函数,在其中释放非托管资源

 

 

在使用的时候,显示调用Dispose()方法,可以及时的释放资源,同时通过移除Finalize()方法的执行,提高了性能;如果没有显示调用Dispose()方法,垃圾回收器也可以通过析构函数来释放非托管资源,垃圾回收器本身就具有回收托管资源的功能,从而保证资源的正常释放,只不过由垃圾回收器回收会导致非托管资源的未能及时释放.

 

.net中应该尽可能的少使用析构函数释放资源.在没有析构函数的对象在垃圾处理器一次处理中从内存删除,但有析构函数的对象,需要两次,第一次调用析构函数,第二次删除对象.而且在析构函数中包含大量的释放资源代码,会降低垃圾回收器的工作项链,影响性能.所以对于包含非托管资源的对象,最好及时的调用Dispose()方法来回收资源,而不是依赖垃圾回收器.

 

上面就是.net中对包含非托管资源的类的资源释放几只,只要按照上述的步骤编写代码,累就属于资源安全的类.

案例:

class MyResourceWrapper:IDisposable

{

    public void Dispose()

    {

        Console.WriteLine("release resources with Dispose");

        Console.Beep();

    }

}

 

class Program

{

    static void Main(string[] args)

    {

        MyResourceWrapper mr = new MyResourceWrapper();

        mr.Dispose();

    }

}

 

 

当我们显示调用Dispose方法的时候,可以听到系统的蜂鸣声.

 

注意:通多Dispose进行资源的释放也是有潜在的风险的,因为Dispose方法需要被程序员显示的滴啊用,如果代码中漏掉了Dispose的调用或者在Dispose调用之前产生了异常从而没有指定Dispose,那么有些资源可能就一直留在内存中了.

 

所以我们应该使用下面的方式保证Dispose方法可以被调用到:

static void Main(string[] args)

{

    MyResourceWrapper mr = new MyResourceWrapper();

    try

    {

        //do something wiht mr object

    }

    finally

    {

        mr.Dispose();

    }

}

 

 

但是,每次编写Idspose的代码都是用try块会觉得很麻烦,还好C#,我们可以重用using关键字来简化Dispose的调用.

 

重用using关键字

C#,using语句提供了一个高效的调用对象Dispose方法的方式.对于任何IDisposable接口的类型,都可以使用using语句,而对于那些没有实现IDisposable接口的类型,使用using语句会导致一个编译错误.

 

static void Main(string[] args)

{

    using (MyResourceWrapper mr = new MyResourceWrapper())

    {

        //do something with mr object

    }

}

 

using语句块结束的时候,mr实例的Dispose方法会被自动调用,using语句不仅免除了程序员输入Dispose调用的代码,他还保证Dispose方法被调用,无论using语句块是否顺利执行事实上,C#编译器为using语句自动添加了try/finally语句块.

 

 

 

.NET提供了两种释放非托管资源的方式,一种是Finalize方法和Dispose方法.

 

接下来再来看Finalize方法

.net的基类System.Object,定义了名为Finalize()的虚方法,这个方法默认什么都不做.我们可以为自定义的类型重写Finalize方法,在该方法中加入必要的非托管资源清理逻辑.当要从内尊中删除这个类型的对象时,垃圾回收器会调用对象的Finalize方法.所以,无论.net进行一次自发的垃圾回收,还是通过GC.Collect()进行强制垃圾回收,Finalize方法总是会被调用.另外,当承载应用程序的AppDomain从内存中移除时,同样会调用Finalize方法.

 

重写Finalize方法

假设我们现在有一个是由非托管资源的类型,我们就需要重写Finalize方法来进行非托管资源的清理,但是当通过下面的方式重写Finalize方法的时候,会出现编译错误:

    class MyResourceWrapper 

    {

        protected override void Finalize()

        { }

}

 

其实,当我们想要重写Finalize方法时,C#为我们提供了(类似C++)析构函数语法(C#终结器)来重写该方法.C#终结器和构造函数语法类似,方法名和类型名一样;不同的是,终结器具有~前缀,并且不能使用访问修饰符,不接受参数,不能重载,所以一个类只能有一个终结器.

 

    class MyResourceWrapper

    {

        ~MyResourceWrapper()

        {

            Console.WriteLine("release unmanaged resources");

            Console.Beep();//t通过控制台播放器播放提示音

        }

    }

 

之所以C#只支持这种方式进行Finalize方法的重写,是因为C#编译器回味Finalize方法隐式的加入一些必要的基础代码.添加的那些基础代码我就不给你们看了,反正就是保证finalize方法总是能被执行.

 

 

当我们执行下面的代码的时候,我们就可以听到蜂鸣声了:

MyResourceWrapper mr = new MyResourceWrapper();

 

 

Finalize的工作机制

他的工作机制很复杂,想要深入研究,您可以自己上网查看(估计没人查),我只是参考网上一些比较简单的说法来理解Finalize的工作机制.

其实,Finalize方法的调是相当耗费资源的,Finalize方法的作用是保证.net对象能够在垃圾回收时清零非托管资源,如果创建了一个不使用非托管资源的类型,实现终结器是没有任何作用的,所以,如果没有特殊的需要应该避免重写Finalize方法.

 

 

 

 

DisposeFinalize的结合

 

Finalize可以通过垃圾回收进行自动的调用,Dispose需要被代码显示的调用.so,为了保险起见,对于一些非托管资源,还是有必要实现终结器的(什么是终结器呢,看最后),也就是说,如果我们忘记了显式的调用Dispose,那么垃圾回收也会调用Finalize,从而保证非托管资源的回收.

 

MSND中提供了一种很好的模式来实现IDisposable接口来结合DisposeFInalize,案例如下:

    class MyResourceWrapper:IDisposable

    {

        private bool IsDisposed = false;

        ~MyResourceWrapper()

        {

 

            Dispose(false);           

 

        }

        public void Dispose(bool disposing)

        {

            if (!this.IsDisposed)

            {

                if (disposing)

                {

                    //清除托管资源

                }

                //清除非托管资源

            }

            this.IsDisposed = true;

        }

        public void Dispose()

        {

            Dispose(true);

            //告诉GC不调用Finalize方法

            GC.SuppressFinalize(this);

        }

}

 

在这个模式中,void Dispose(bool disposing)函数通过一个disposing参数来区别当前是否是被Dispose()调用.如果是被Dispose()调用.如果是被Dispose()调用,那么需要同时释放托管和非托管资源.如果是被终结器调用了,那么只需要释放非托管的资源即可.Dispose()函数是被其他代码显式调用应要求释放资源的,Finalize是被GC调用的.

 

另外,由于在Dispose()中已经释放了托管和非托管的资源,因此在对象中被GC回收时再次调用Finalize是没有必要的,所以在Dispose()中调用GC.SuppressFinalize(this)避免重复调用Finalize.同样,因为IsDisposed变量的存在,资源只会被释放一次,多余的调用会被忽略.

 

这个模式的优点如下:

1.如果没有显式的调用Dispose(),未释放托管和非托管资源,那么在垃圾回收时,还会执行Finalize,释放非托管资源,同时GC会释放托管资源.

2.如果调用了Dispose(),就能及时释放托管和非托管资源,那么该对象被垃圾回收时,就不会执行Finalize(),提高了非托管资源的使用效率并提升了系统性能.





终结器(finalizer主要用于C#.Net中的非托管代码清理中,通常同时实现终结器和Dispose方式。这样对于细心的使用者直接显示调用Dispose方法会提高垃圾回收的性能,对于粗心的使用者虽然忘记了调用Dispose方法,但也不至于使得非托管资源得不到释放。代码如下:

这篇关于C#高级编程七十四天----释放非托管资源的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

C#中DrawCurve的用法小结

《C#中DrawCurve的用法小结》本文主要介绍了C#中DrawCurve的用法小结,通常用于绘制一条平滑的曲线通过一系列给定的点,具有一定的参考价值,感兴趣的可以了解一下... 目录1. 如何使用 DrawCurve 方法(不带弯曲程度)2. 如何使用 DrawCurve 方法(带弯曲程度)3.使用Dr

MySQL高级查询之JOIN、子查询、窗口函数实际案例

《MySQL高级查询之JOIN、子查询、窗口函数实际案例》:本文主要介绍MySQL高级查询之JOIN、子查询、窗口函数实际案例的相关资料,JOIN用于多表关联查询,子查询用于数据筛选和过滤,窗口函... 目录前言1. JOIN(连接查询)1.1 内连接(INNER JOIN)1.2 左连接(LEFT JOI

前端高级CSS用法示例详解

《前端高级CSS用法示例详解》在前端开发中,CSS(层叠样式表)不仅是用来控制网页的外观和布局,更是实现复杂交互和动态效果的关键技术之一,随着前端技术的不断发展,CSS的用法也日益丰富和高级,本文将深... 前端高级css用法在前端开发中,CSS(层叠样式表)不仅是用来控制网页的外观和布局,更是实现复杂交

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

使用C#代码在PDF文档中添加、删除和替换图片

《使用C#代码在PDF文档中添加、删除和替换图片》在当今数字化文档处理场景中,动态操作PDF文档中的图像已成为企业级应用开发的核心需求之一,本文将介绍如何在.NET平台使用C#代码在PDF文档中添加、... 目录引言用C#添加图片到PDF文档用C#删除PDF文档中的图片用C#替换PDF文档中的图片引言在当

详解C#如何提取PDF文档中的图片

《详解C#如何提取PDF文档中的图片》提取图片可以将这些图像资源进行单独保存,方便后续在不同的项目中使用,下面我们就来看看如何使用C#通过代码从PDF文档中提取图片吧... 当 PDF 文件中包含有价值的图片,如艺术画作、设计素材、报告图表等,提取图片可以将这些图像资源进行单独保存,方便后续在不同的项目中使

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

C#数据结构之字符串(string)详解

《C#数据结构之字符串(string)详解》:本文主要介绍C#数据结构之字符串(string),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录转义字符序列字符串的创建字符串的声明null字符串与空字符串重复单字符字符串的构造字符串的属性和常用方法属性常用方法总结摘

C#如何动态创建Label,及动态label事件

《C#如何动态创建Label,及动态label事件》:本文主要介绍C#如何动态创建Label,及动态label事件,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#如何动态创建Label,及动态label事件第一点:switch中的生成我们的label事件接着,

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.