Compartir a través de


Uso de objetos que implementan IDisposable

El recolector de elementos no utilizados (GC) de Common Language Runtime reclama la memoria usada por los objetos administrados. Normalmente, los tipos que usan recursos no administrados implementan la IDisposable interfaz o IAsyncDisposable para permitir que se recuperen los recursos no administrados. Cuando termine de usar un objeto que implemente IDisposable, llame a la implementación Dispose o DisposeAsync del objeto para realizar explícitamente la limpieza. Puede hacerlo de una de las maneras siguientes:

  • Mediante la instrucción o declaración using de C# (Using en Visual Basic).
  • Mediante la implementación de un bloque try/finally y una llamada al método Dispose o DisposeAsync en finally.

Importante

El GC no elimina los objetos, ya que no tiene conocimiento de IDisposable.Dispose() ni de IAsyncDisposable.DisposeAsync(). El GC solo sabe si un objeto es finalizable (es decir, define un Object.Finalize() método) y cuándo se debe llamar al finalizador del objeto. Para obtener más información, consulte Cómo funciona la finalización. Para obtener más información sobre la implementación Dispose y DisposeAsync, consulte:

Los objetos que implementan System.IDisposable o System.IAsyncDisposable siempre deben eliminarse correctamente, independientemente del ámbito de las variables, a menos que se indique explícitamente lo contrario. Los tipos que definen un finalizador para liberar recursos no administrados suelen llamar a GC.SuppressFinalize desde su implementación de Dispose o DisposeAsync. Llamar a SuppressFinalize indica al GC que el finalizador ya se ha ejecutado y que el objeto no debe ser promovido para su finalización.

La instrucción using

La using instrucción en C# y la Using instrucción de Visual Basic simplifican el código que debe escribir para limpiar un objeto. La using instrucción obtiene uno o varios recursos, ejecuta las instrucciones que especifique y elimina automáticamente el objeto . Sin embargo, la using instrucción solo es útil para los objetos que se usan dentro del ámbito del método en el que se construyen.

En el ejemplo siguiente se usa la using instrucción para crear y liberar un System.IO.StreamReader objeto .

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

Una using declaración es una sintaxis alternativa disponible donde se quitan las llaves, y cuyo ámbito es 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.
            //
        }
    }
}

Aunque la StreamReader clase implementa la IDisposable interfaz , que indica que usa un recurso no administrado, el ejemplo no llama explícitamente al StreamReader.Dispose método . Cuando el compilador de C# o Visual Basic encuentra la using instrucción , emite lenguaje intermedio (IL) equivalente al código siguiente que contiene explícitamente un try/finally bloque.

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

La instrucción C# using también permite adquirir varios recursos en una sola instrucción, que es internamente equivalente a instrucciones anidadas using . En el ejemplo siguiente se crean instancias de dos StreamReader objetos para leer el contenido de dos archivos 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.
            //
        }
    }
}

Bloque try/finally

En lugar de ajustar un bloque try/finally en una instrucción using, puede elegir implementar el bloque try/finally directamente. Puede ser su estilo de codificación personal, o puede que quiera hacerlo por una de las siguientes razones:

  • Para incluir un bloque catch que maneje las excepciones lanzadas en el bloque try. De lo contrario, las excepciones iniciadas en la instrucción using no se controlan.
  • Para crear una instancia de un objeto que implementa IDisposable y cuyo ámbito no es local dentro del bloque en el que se declara.

El ejemplo siguiente es similar al ejemplo anterior, excepto que usa un try/catch/finally bloque para crear instancias, usar y eliminar de un StreamReader objeto, y para controlar las excepciones producidas por el StreamReader constructor y su ReadToEnd método. El código en el bloque finally verifica que el objeto que implementa IDisposable no sea null antes de llamar al método Dispose. Si no se hace esto, se puede producir una NullReferenceException excepción en tiempo de ejecución.

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

Puede seguir este patrón básico si decide implementar o debe implementar un try/finally bloque, ya que el lenguaje de programación no admite una using instrucción, pero permite llamadas directas al Dispose método .

Miembros de instancia de IDisposable

Si una clase posee un campo o propiedad de instancia y su tipo implementa IDisposable, la clase también debe implementar IDisposable. Para más información, consulte cómo implementar una eliminación en cascada.

Consulte también