Usando objetos que implementam IDisposable
O GC (coletor de lixo) do common language runtime recupera a memória usada por objetos gerenciados. Normalmente, os tipos que usam recursos não gerenciados implementam a interface IDisposable ou a IAsyncDisposable para permitir que os recursos não gerenciados sejam recuperados. Depois de terminar de usar um objeto que implementa IDisposable, você deverá chamar a implementação de Dispose ou DisposeAsync do objeto para realizar explicitamente a limpeza. É possível fazer isso de duas formas:
- Com a instrução ou declaração
using
C# (Using
no Visual Basic). - Implementando um bloco
try/finally
e chamando o método Dispose ou DisposeAsync nofinally
.
Importante
O GC não descarta seus objetos, pois não tem conhecimento de IDisposable.Dispose() ou IAsyncDisposable.DisposeAsync(). O GC apenas sabe se um objeto é finalizável (ou seja, define um método Object.Finalize()) e quando o finalizador do objeto precisa ser chamado. Para saber mais, veja Como funciona a finalização. Para saber mais detalhes sobre a implementação de Dispose
e DisposeAsync
, consulte:
Objetos que implementam System.IDisposable ou System.IAsyncDisposable devem ser sempre descartados corretamente, independentemente do escopo de variável, a menos que seja explicitamente declarado. Os tipos que definem um finalizador para liberar recursos não gerenciados geralmente chamam GC.SuppressFinalize da implementação Dispose
ou DisposeAsync
. A chamada SuppressFinalize indica ao GC que o finalizador já foi executado e que o objeto não deve ser promovido para finalização.
A instrução using
A using
instrução no C# e a Using
instrução no Visual Basic simplificam o código que você deve escrever para criar e limpar um objeto. A instrução using
obtém um ou mais recursos, executa as instruções que você especifica e descarta o objeto automaticamente. Entretanto, a instrução using
é útil apenas para os objetos que são usados no escopo do método no qual eles são criados.
O exemplo a seguir usa a instrução using
para criar e liberar um objeto 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
Uma using
declaração é uma sintaxe alternativa disponível onde as chaves são removidas e o escopo está implícito.
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.
//
}
}
}
Embora a classe StreamReader implemente a interface IDisposable, o que indica que ela usa um recurso não gerenciado, o exemplo não chama explicitamente o método StreamReader.Dispose. Quando o compilador do C# ou do Visual Basic encontra a instrução using
, ele emite a IL (linguagem intermediária) que equivale ao seguinte código que contém explicitamente um bloco try/finally
.
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
A instrução using
do C# também permite que você adquira vários recursos em uma única instrução, o que é internamente equivalente a instruções using
aninhadas. O exemplo a seguir cria instancia dois objetos StreamReader para ler o conteúdo de dois arquivos diferentes.
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.
//
}
}
}
Bloco Try/finally
Em vez de incluir um bloco try/finally
em uma instrução using
, você pode optar por implementar diretamente o bloco try/finally
. Ele pode ser seu estilo pessoal de codificação ou talvez você queira fazer isso por um dos seguintes motivos:
- Para incluir um bloco
catch
para lidar com exceções geradas no blocotry
. Caso contrário, todas as exceções geradas na instruçãousing
não serão lidadas. - Para criar uma instância de um objeto que implementa IDisposable cujo escopo não é local para o bloco dentro do qual é declarado.
O exemplo a seguir é semelhante ao exemplo anterior, exceto que ele usa um bloco try/catch/finally
para criar uma instância, usar e descartar um objeto StreamReader e também para manipular todas as exceções lançadas pelo construtor StreamReader e pelo método ReadToEnd. O código no bloco finally
verifica se o objeto que implementa IDisposable não é null
antes de chamar o método Dispose. Não fazer isso poderá levar a uma exceção de NullReferenceException em tempo de execução.
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
Você poderá seguir esse padrão básico se optar por implementar ou precisar implementar um bloco try/finally
, pois a linguagem de programação não oferece suporte a uma instrução using
, mas permite chamadas diretas para o método Dispose.
Membros da instância IDisposable
Se uma classe tem um campo ou propriedade de instância e o tipo implementa IDisposable, a classe deve implementar IDisposable. Para mais informações, confira Implementar um descarte em cascata.