IDisposable을 구현하는 개체 사용

공용 언어 런타임의 GC(가비지 수집기)는 관리되는 개체가 사용하는 메모리를 회수합니다. 일반적으로 관리되지 않는 리소스를 사용하는 유형은 IDisposable 또는 IAsyncDisposable 인터페이스를 구현하여 관리되지 않는 리소스를 회수할 수 있도록 합니다. IDisposable을(를) 구현하는 개체 사용을 마치면 개체의 Dispose 또는 DisposeAsync 구현을 호출하여 명시적으로 정리를 수행합니다. 이 작업은 다음 두 가지 방법 중 한 가지로 수행할 수 있습니다.

  • C# using 문 또는 선언(Visual Basic에서는 Using)을 사용합니다.
  • try/finally 블록을 구현하고 finally에서 Dispose 또는 DisposeAsync 메서드를 호출합니다.

Important

GC는 IDisposable.Dispose() 또는 IAsyncDisposable.DisposeAsync()에 대한 지식이 없으므로 객체를 폐기하지 않습니다. GC는 개체를 종료할 수 있는지 여부(즉, Object.Finalize() 메서드를 정의함)와 개체의 종료자를 호출해야 하는 시기만 알고 있습니다. 자세한 내용은 종료의 작동 방식을 참조하세요. DisposeDisposeAsync을(를) 구현하는 방법에 대한 자세한 내용은 다음을 참조하세요.

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 블록

using 문에서 try/finally 블록을 래핑하는 대신 try/finally 블록을 직접 구현하도록 선택할 수 있습니다. 개인적인 코딩 스타일에 따라서 또는 다음 이유 중 하나로 이를 수행할 수 있습니다.

  • catch 블록에서 throw된 예외를 처리하기 위해 try 블록을 포함하려는 경우. 그렇지 않으면 using 문 내에서 throw되는 모든 예외가 처리되지 않습니다.
  • 범위가 선언된 범위 내의 블록에 대해 로컬이 아닌 IDisposable을 구현하는 개체를 인스턴스화하려는 경우

try/catch/finally 블록을 사용하여 StreamReader 개체를 인스턴스화, 사용 및 삭제하고 StreamReader 생성자 및 해당 ReadToEnd 메서드에서 throw된 예외를 처리한다는 점을 제외하면 다음 예제는 이전 예제와 유사합니다. finally 블록의 코드가 IDisposable 메서드를 호출하기 전에 null을 구현하는 개체가 Dispose이 아닌지 확인합니다. 이렇게 하지 않으면 런타임에 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

프로그래밍 언어가 using 문을 지원하지 않지만, Dispose 메서드에 대한 직접 호출을 허용하므로 try/finally 블록을 구현하도록 선택하거나 구현해야 하는 경우 이 기본 패턴을 따를 수 있습니다.

IDisposable 인스턴스 멤버

클래스가 인스턴스 필드 또는 속성을 소유하고 해당 형식이 IDisposable을(를) 구현하는 경우 클래스도 IDisposable을(를) 구현해야 합니다. 자세한 내용은 계단식 배열 Dispose를 참조하세요.

참고 항목