使用实现 IDisposable 的对象
公共语言运行时的垃圾回收器 (GC) 回收托管对象使用的内存。 通常,使用非托管资源的类型将实现 IDisposable 或 IAsyncDisposable 接口,以允许回收非托管资源。 在使用完实现 IDisposable 的对象后,应调用该对象的 Dispose 或 DisposeAsync 实现以显式执行清理。 可以通过两种方法执行此操作:
- 使用 C#
using
语句或声明(Visual Basic 中的Using
)。 - 通过实现
try/finally
块,并调用finally
中的 Dispose 或 DisposeAsync 方法。
重要
GC 不会处置你的对象,因为它不知道 IDisposable.Dispose() 或 IAsyncDisposable.DisposeAsync()。 GC 只知道某个对象是否可终结(即它定义了一个 Object.Finalize() 方法),以及何时需要调用该对象的终结器。 有关详细信息,请参阅终结工作原理。 有关实现 Dispose
和 DisposeAsync
的其他详细信息,请参阅:
无论变量范围如何,都应始终正确处置实现 System.IDisposable 或 System.IAsyncDisposable 的对象,除非另有明确说明。 定义终结器以释放非托管资源的类型通常从其 Dispose
或 DisposeAsync
实现调用 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。