0

0

.Net 的 IDisposable interface

高洛峰

高洛峰

发布时间:2016-11-21 14:54:56

|

1509人浏览过

|

来源于php中文网

原创

.net framework 中的 garbage collection 会帮助程序员自动回收托管资源,这对类库的调用者而言,是个相当惬意的体验:可以在任何位置,任何时候,创建任何对象,gc 最后总是会兜底。易地而处,当自己是类库提供者的时候,则需要如何才能提供这样良好的体验呢?

首先,.Net framework 里面哪些是托管的资源,哪些是非托管的资源?

基本上,在 .Net framework 里面的所有类,都是托管资源,包括各种各样的 stream(例如 FileStream, MemoryStream), database connection, components 等等。。

可以写一个简单的小程序验证:(以 FileStream 为例)

一个方法,在后台线程中监控文件是否正在被占用:

        private static void MonitorFileStatus(string fileName)
        {
            Console.WriteLine("Start to monitor file: {0}", fileName);
            Task.Factory.StartNew(() =>
            {
                while(true)
                {
                    bool isInUse = IsFileInUse(fileName);

                    string messageFormat = isInUse ? "File {0} is in use." : "File {0} is released.";
                    Console.WriteLine(messageFormat, fileName);
                    Thread.Sleep(oneSeconds);
                }
            });
        }


        private static bool IsFileInUse(string fileName)
        {
            bool isInUse = true;
            FileStream stream = null;
            try
            {
                stream = File.Open(fileName, FileMode.Append, FileAccess.Write);
                isInUse = false;
            }
            catch
            {
            }
            finally
            {
                if (stream != null)
                {
                    stream.Dispose();
                }
            }
            return isInUse;
        }

再写一个占着文件不用的方法, FileStream 只是个局部变量,这个方法返回的时候,它应该被回收:

        private static void OpenFile()
        {
            FileStream stream  = File.Open(TestFileName, FileMode.Append, FileAccess.Write);
            Wait(fiveSeconds);
        }

最后是一个必不可少的等待:

        private static void Wait(TimeSpan time)
        {
            Console.WriteLine("Wait for {0} seconds...", time.TotalSeconds);
            Thread.Sleep(time);
        }

合并起来就是一个测试:
首先启动文件监视线程,然后打开文件不用。
OpenFile 方法返回,预测 FileStream 被回收
接着调用 GC, 看文件是否被释放了

        private static void FileTest()
        {
            MonitorFileStatus(TestFileName);

            OpenFile();

            CallGC();
            Wait(fiveSeconds);
        }

运行结果,可见 GC 自动把 FileStream 自动回收。无须调用 Dispose 方法,也无须使用 using

t0128f824aa7ed65fdd.png

那么,非托管资源包括哪些呢?

通常,涉及到 windows api 的 pinvoke,各种的 intptr 都是非托管资源。例如,同样是打开文件,如果写成以下的样子,就包括了非托管资源

MTTSHOP包包免费商城系统
MTTSHOP包包免费商城系统

一款非常包包、衣服、鞋子类网站,页面干净清洁、一目了然,mttshop打造精致、简单、易用、免费的商城。 系统要求:IIS5.1以后,必须安装.net 3.5 安装步骤: 1、下载完成后,直接解压文件mttshop.rar 2、附加数据库:解压后的可以找一个叫db的文件夹,解压后直接附加就可以,支持SQL 2000、2005、2008 3、配置web.config文件,找到key=&qu

下载
    [Flags]
    internal enum OpenFileStyle : uint
    {
        OF_CANCEL = 0x00000800,  // Ignored. For a dialog box with a Cancel button, use OF_PROMPT.
        OF_CREATE = 0x00001000,  // Creates a new file. If file exists, it is truncated to zero (0) length.
        OF_DELETE = 0x00000200,  // Deletes a file.
        OF_EXIST = 0x00004000,  // Opens a file and then closes it. Used to test that a file exists
        OF_PARSE = 0x00000100,  // Fills the OFSTRUCT structure, but does not do anything else.
        OF_PROMPT = 0x00002000,  // Displays a dialog box if a requested file does not exist 
        OF_READ = 0x00000000,  // Opens a file for reading only.
        OF_READWRITE = 0x00000002,  // Opens a file with read/write permissions.
        OF_REOPEN = 0x00008000,  // Opens a file by using information in the reopen buffer.

        // For MS-DOS–based file systems, opens a file with compatibility mode, allows any process on a 
        // specified computer to open the file any number of times.
        // Other efforts to open a file with other sharing modes fail. This flag is mapped to the 
        // FILE_SHARE_READ|FILE_SHARE_WRITE flags of the CreateFile function.
        OF_SHARE_COMPAT = 0x00000000,

        // Opens a file without denying read or write access to other processes.
        // On MS-DOS-based file systems, if the file has been opened in compatibility mode
        // by any other process, the function fails.
        // This flag is mapped to the FILE_SHARE_READ|FILE_SHARE_WRITE flags of the CreateFile function.
        OF_SHARE_DENY_NONE = 0x00000040,

        // Opens a file and denies read access to other processes.
        // On MS-DOS-based file systems, if the file has been opened in compatibility mode,
        // or for read access by any other process, the function fails.
        // This flag is mapped to the FILE_SHARE_WRITE flag of the CreateFile function.
        OF_SHARE_DENY_READ = 0x00000030,

        // Opens a file and denies write access to other processes.
        // On MS-DOS-based file systems, if a file has been opened in compatibility mode,
        // or for write access by any other process, the function fails.
        // This flag is mapped to the FILE_SHARE_READ flag of the CreateFile function.
        OF_SHARE_DENY_WRITE = 0x00000020,

        // Opens a file with exclusive mode, and denies both read/write access to other processes.
        // If a file has been opened in any other mode for read/write access, even by the current process,
        // the function fails.
        OF_SHARE_EXCLUSIVE = 0x00000010,

        // Verifies that the date and time of a file are the same as when it was opened previously.
        // This is useful as an extra check for read-only files.
        OF_VERIFY = 0x00000400,

        // Opens a file for write access only.
        OF_WRITE = 0x00000001
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct OFSTRUCT
    {
        public byte cBytes;
        public byte fFixedDisc;
        public UInt16 nErrCode;
        public UInt16 Reserved1;
        public UInt16 Reserved2;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string szPathName;
    }

    class WindowsApi
    {
        [DllImport("kernel32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]
        internal static extern IntPtr OpenFile([MarshalAs(UnmanagedType.LPStr)]string lpFileName, out OFSTRUCT lpReOpenBuff, OpenFileStyle uStyle);

        [DllImport("kernel32.dll", SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [SuppressUnmanagedCodeSecurity]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool CloseHandle(IntPtr hObject);
    }

处理非托管资源,需要实现 IDisposable interface。原因有两个:

不能依赖析构函数,因为异构函数的调用由 GC 决定。无法实时释放紧缺的资源。

有一通用的处理原则:析构函数处理托管资源,IDisposable interface 处理托管与非托管资源。

如上述的例子,完成的实现代码如下:

    public class UnmanagedFileHolder : IFileHolder, IDisposable
    {
        private IntPtr _handle;
        private string _fileName;

        public UnmanagedFileHolder(string fileName)
        {
            _fileName = fileName;
        }

        public void OpenFile()
        {
            Console.WriteLine("Open file with windows api.");
            OFSTRUCT info;
            _handle = WindowsApi.OpenFile(_fileName, out info, OpenFileStyle.OF_READWRITE);
        }

        #region IDisposable Support
        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // no managed resource
                }
                WindowsApi.CloseHandle(_handle);
                _handle = IntPtr.Zero;

                disposed = true;
            }
        }

        ~UnmanagedFileHolder()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }

如果同一个类里面既有托管资源,也有非托管资源,那样应该怎么办呢?

可以依照下面的模式:

    class HybridPattern : IDisposable
    {
        private bool _disposed = false;

        ~HybridPattern()
        {
            Dispose(false);
        }

        protected void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                // Code to dispose the managed resources of the class
                // internalComponent1.Dispose();
            }

            // Code to dispose the un-managed resources of the class
            // CloseHandle(handle);
            // handle = IntPtr.Zero;

            _disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

以下为完整的例子,有托管的 FileStream, 以及非托管的 Handler

    public class HybridHolder : IFileHolder, IDisposable
    {
        private string _unmanagedFile;
        private string _managedFile;

        private IntPtr _handle;
        private FileStream _stream;

        public HybridHolder(string unmanagedFile, string managedFile)
        {
            _unmanagedFile = unmanagedFile;
            _managedFile = managedFile;
        }

        public void OpenFile()
        {
            Console.WriteLine("Open file with windows api.");
            OFSTRUCT info;
            _handle = WindowsApi.OpenFile(_unmanagedFile, out info, OpenFileStyle.OF_READWRITE);

            Console.WriteLine("Open file with .Net libray.");
            _stream = File.Open(_managedFile, FileMode.Append, FileAccess.Write);
        }

        #region IDisposable Support
        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                //Console.WriteLine("string is null? {0}", _stream == null);
                if (disposing && _stream != null)
                {
                    Console.WriteLine("Clean up managed resource.");
                    _stream.Dispose();
                }

                Console.WriteLine("Clean up unmanaged resource.");
                WindowsApi.CloseHandle(_handle);
                _handle = IntPtr.Zero;

                disposed = true;
            }
        }

        ~HybridHolder()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }

最后,如果是没有实现 IDisposable interface 的类呢? 例如 byte[], StringBuilder

完全不要插手干预它们的回收, GC 做得很好。
尝试过在析构函数中把一个庞大的 byte[] 设置为 null,唯一的结果是导致它的回收被延迟到下一次 GC 周期。
原因也很简单,每一次引用到会导致它的引用树上的计数加一。。

完整代码见 Github:

https://github.com/IGabriel/IDisposableSample

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

1

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

21

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

106

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

50

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

87

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

27

2026.03.03

Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

79

2026.02.28

Golang 工程化架构设计:可维护与可演进系统构建
Golang 工程化架构设计:可维护与可演进系统构建

Go语言工程化架构设计专注于构建高可维护性、可演进的企业级系统。本专题深入探讨Go项目的目录结构设计、模块划分、依赖管理等核心架构原则,涵盖微服务架构、领域驱动设计(DDD)在Go中的实践应用。通过实战案例解析接口抽象、错误处理、配置管理、日志监控等关键工程化技术,帮助开发者掌握构建稳定、可扩展Go应用的最佳实践方法。

61

2026.02.28

Golang 性能分析与运行时机制:构建高性能程序
Golang 性能分析与运行时机制:构建高性能程序

Go语言以其高效的并发模型和优异的性能表现广泛应用于高并发、高性能场景。其运行时机制包括 Goroutine 调度、内存管理、垃圾回收等方面,深入理解这些机制有助于编写更高效稳定的程序。本专题将系统讲解 Golang 的性能分析工具使用、常见性能瓶颈定位及优化策略,并结合实际案例剖析 Go 程序的运行时行为,帮助开发者掌握构建高性能应用的关键技能。

50

2026.02.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 10.8万人学习

python编程入门系列图文教程
python编程入门系列图文教程

共65课时 | 25万人学习

vscode其实很简单
vscode其实很简单

共72课时 | 29.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号