不,不使用非托管资源就不可能编写 C# 程序。不可避免的是,C# 程序在 100% 不受管理的操作系统上运行。如果使用文件,则使用操作系统资源。网络连接。一根线。控制台。等等,所有非常不受管理的资源。
然而,这一事实在 .NET 中隐藏得很好。框架库为这些原生对象提供了很好的包装类。文件流、套接字、线程、控制台等。内存也是一种操作系统资源,垃圾收集器是它的包装器。
在所有这些资源中,只有内存资源是真正自动管理的。他们中的其他人通过他们的包装类获得了一些帮助。它们的终结器是关键,它在调用时释放操作系统资源。这非常接近自动,垃圾收集器注意到包装类对象不再在任何地方引用,因此它释放它,终结器然后确保非托管资源也被释放。
这通常效果很好,您通常可以忽略代码中的这些实现细节。许多程序员都这样做。
但是终结器有一个问题,它们需要一段时间才能开始运行。启动它们需要进行垃圾收集,这可能需要几毫秒到几分钟不等。这是不可预测的,它取决于您在代码中消耗内存的速率。如果你不使用很多,那么它会花费很长时间。
您不能总是等待那么长时间来释放非托管资源。文件就是一个很好的例子。当您打开一个以从文件中读取数据时,您确实应该在完成读取后关闭该文件。如果您等到终结器完成该工作,那么当您需要一段时间后再次打开文件时,您将面临程序失败的风险。您可能已通过使用 FileShare.None 打开文件将自己锁定,它也会锁定您自己的代码。没什么大不了的:当你完成阅读时,你调用 Close() 来关闭文件。为了确保它被关闭,您应该将 Close() 调用放在 finally 块中,这样即使代码由于异常而中止,它也会运行。实际上,您显式地运行终结器代码。
更严重的情况是操作系统资源非常昂贵。很好的例子是位图,它们可以占用大量非托管内存或数据库连接,它们有一个池,默认情况下只包含 100 个。对于这些,您可能会让自己陷入这样一种情况,即让终结器负责释放资源只是不起作用,因为它需要太长时间。在终结器可以运行之前,您的程序因异常而死。通常很难诊断,因为这往往只在您的程序处于负载状态时发生。当很多事情发生时,在一台不在桌面上的机器上总是很难调试出现的问题。
.NET 设计者认识到了这种需求并设计了 IDisposable 接口。它的 Dispose() 方法旨在运行通常由终结器运行的代码,为您提供一种显式释放资源的方法,而不是等待垃圾收集器处理它。并且语言设计者通过将using关键字添加到他们的语言中,从而确保自动调用 IDisposable.Dispose() 来赶上潮流。
如上所述,在代码中对任何实现 IDisposable 的对象使用using或 Dispose() 是可选的,但许多 .NET 程序员认为这是至关重要的。主要是因为每个人都在没有它的情况下开始 .NET 编程,并且在他们的程序变大时迟早会遇到问题。它甚至被规定在调用 Dispose() 没有意义的类上,比如 MemoryStream。当一个类应该实现 IDisposable 但没有实现时,会导致精神上的痛苦,比如 Thread。或者当一个类同时实现 Dispose 和 Close 时(没有区别)。作为比较,Java 有相同的考虑,但没有 IDisposable。