使用实现 IDisposable 的对象

公共语言运行时的垃圾回收器 (GC) 回收托管对象使用的内存。 通常,使用非托管资源的类型将实现 IDisposableIAsyncDisposable 接口,以允许回收非托管资源。 在使用完实现 IDisposable 的对象后,应调用该对象的 DisposeDisposeAsync 实现以显式执行清理。 可以通过两种方法执行此操作:

  • 使用 C# using 语句或声明(Visual Basic 中的 Using)。
  • 通过实现 try/finally 块,并调用 finally 中的 DisposeDisposeAsync 方法。

重要

GC 不会处置你的对象,因为它不知道 IDisposable.Dispose()IAsyncDisposable.DisposeAsync()。 GC 只知道某个对象是否可终结(即它定义了一个 Object.Finalize() 方法),以及何时需要调用该对象的终结器。 有关详细信息,请参阅终结工作原理。 有关实现 DisposeDisposeAsync 的其他详细信息,请参阅:

无论变量范围如何,都应始终正确处置实现 System.IDisposableSystem.IAsyncDisposable 的对象,除非另有明确说明。 定义终结器以释放非托管资源的类型通常从其 DisposeDisposeAsync 实现调用 GC.SuppressFinalize。 调用 SuppressFinalize 会向 GC 指示终结器已运行,并且不应提升对象以进行终结。

using 语句

C# 中的 using 语句和 Visual Basic 中的 Using 语句可以简化为清理对象而必须编写的代码。 using 语句获取一个或多个资源,执行指定的语句,并自动处置对象。 但是,using 语句仅对在用于构造对象的方法的范围内使用的对象有用。

以下示例使用 using 语句创建并发布 System.IO.StreamReader 对象。

using System.IO;

class UsingStatement
{
    static void Main()
    {
        var buffer = new char[50];
        using (StreamReader streamReader = new("file1.txt"))
        {
            int charsRead = 0;
            while (streamReader.Peek() != -1)
            {
                charsRead = streamReader.Read(buffer, 0, buffer.Length);
                //
                // Process characters read.
                //
            }
        }
    }
}
Imports System.IO

Module UsingStatement
    Public Sub Main()
        Dim buffer(49) As Char
        Using streamReader As New StreamReader("File1.txt")
            Dim charsRead As Integer
            Do While streamReader.Peek() <> -1
                charsRead = streamReader.Read(buffer, 0, buffer.Length)
                ' 
                ' Process characters read.
                '
            Loop
        End Using
    End Sub
End Module

using 声明是另一种可用的语法,其中移除了大括号,并且范围是隐式的。

using System.IO;

class UsingDeclaration
{
    static void Main()
    {
        var buffer = new char[50];
        using StreamReader streamReader = new("file1.txt");

        int charsRead = 0;
        while (streamReader.Peek() != -1)
        {
            charsRead = streamReader.Read(buffer, 0, buffer.Length);
            //
            // Process characters read.
            //
        }
    }
}

虽然 StreamReader 类实现了 IDisposable 接口(这说明它使用的是非托管资源),但此示例不显式调用 StreamReader.Dispose 方法。 当 C# 或 Visual Basic 编译器遇到 using 语句时,它会发出与以下显式包含 try/finally 块的代码等效的中间语言 (IL)。

using System.IO;

class TryFinallyGenerated
{
    static void Main()
    {
        var buffer = new char[50];
        StreamReader? streamReader = null;
        try
        {
            streamReader = new StreamReader("file1.txt");
            int charsRead = 0;
            while (streamReader.Peek() != -1)
            {
                charsRead = streamReader.Read(buffer, 0, buffer.Length);
                //
                // Process characters read.
                //
            }
        }
        finally
        {
            // If non-null, call the object's Dispose method.
            streamReader?.Dispose();
        }
    }
}
Imports System.IO

Module TryFinallyGenerated
    Public Sub Main()
        Dim buffer(49) As Char
        Dim streamReader As New StreamReader("File1.txt")
        Try
            Dim charsRead As Integer
            Do While streamReader.Peek() <> -1
                charsRead = streamReader.Read(buffer, 0, buffer.Length)
                ' 
                ' Process characters read.
                '
            Loop
        Finally
            If streamReader IsNot Nothing Then DirectCast(streamReader, IDisposable).Dispose()
        End Try
    End Sub
End Module

使用 C# using 语句,还可以在一个语句(在内部相当于嵌套语句 using)中获取多个资源。 下面的示例实例化两个 StreamReader 对象以读取两个不同文件的内容。

using System.IO;

class SingleStatementMultiple
{
    static void Main()
    {
        var buffer1 = new char[50];
        var buffer2 = new char[50];

        using StreamReader version1 = new("file1.txt"),
                           version2 = new("file2.txt");

        int charsRead1, charsRead2 = 0;
        while (version1.Peek() != -1 && version2.Peek() != -1)
        {
            charsRead1 = version1.Read(buffer1, 0, buffer1.Length);
            charsRead2 = version2.Read(buffer2, 0, buffer2.Length);
            //
            // Process characters read.
            //
        }
    }
}

Try/finally 块

可以选择直接实现 try/finally 块,而不是将 try/finally 块包装在 using 语句中。 它可以是私有编码样式,或者你可能出于下列原因之一需要这样做:

  • 包含 catch 块以处理 try 块中引发的异常。 否则,不会处理 using 语句中引发的任何异常。
  • 实例化实现 IDisposable(其范围对于声明它的块是非本地的)的对象。

下面的示例与上一示例类似,不同之处在于此示例使用 try/catch/finally 块实例化、使用和清理 StreamReader 对象,同时处理 StreamReader 构造函数及其 ReadToEnd 方法抛出的任何异常。 finally 块中的代码检查实现 IDisposable 的对象在其调用 Dispose 方法之前不为 null。 此操作失败会导致运行时发生 NullReferenceException 异常。

using System;
using System.Globalization;
using System.IO;

class TryExplicitCatchFinally
{
    static void Main()
    {
        StreamReader? streamReader = null;
        try
        {
            streamReader = new StreamReader("file1.txt");
            string contents = streamReader.ReadToEnd();
            var info = new StringInfo(contents);
            Console.WriteLine($"The file has {info.LengthInTextElements} text elements.");
        }
        catch (FileNotFoundException)
        {
            Console.WriteLine("The file cannot be found.");
        }
        catch (IOException)
        {
            Console.WriteLine("An I/O error has occurred.");
        }
        catch (OutOfMemoryException)
        {
            Console.WriteLine("There is insufficient memory to read the file.");
        }
        finally
        {
            streamReader?.Dispose();
        }
    }
}
Imports System.Globalization
Imports System.IO

Module TryExplicitCatchFinally
    Sub Main()
        Dim streamReader As StreamReader = Nothing
        Try
            streamReader = New StreamReader("file1.txt")
            Dim contents As String = streamReader.ReadToEnd()
            Dim info As StringInfo = New StringInfo(contents)
            Console.WriteLine($"The file has {info.LengthInTextElements} text elements.")
        Catch e As FileNotFoundException
            Console.WriteLine("The file cannot be found.")
        Catch e As IOException
            Console.WriteLine("An I/O error has occurred.")
        Catch e As OutOfMemoryException
            Console.WriteLine("There is insufficient memory to read the file.")
        Finally
            If streamReader IsNot Nothing Then streamReader.Dispose()
        End Try
    End Sub
End Module

如果选择实现或必须实现 try/finally 块,可以遵循此基本模式,因为编程语言不支持 using 语句,但允许直接调用 Dispose 方法。

IDisposable 实例成员

如果类拥有一个实例字段或属性,并且其类型实现 IDisposable,则该类还应实现 IDisposable。 有关详细信息,请参阅实现级联 Dispose

请参阅