수색…


소개

IDisposable 개체의 올바른 사용을 보장하는 편리한 구문을 제공합니다.

통사론

  • 사용 (일회용) {}
  • (IDisposable disposable = new MyDisposable ()) {}을 사용하여

비고

using 문에있는 개체는 IDisposable 인터페이스를 구현해야합니다.

using(var obj = new MyObject())
{
}

class MyObject : IDisposable
{
    public void Dispose()
    {
        // Cleanup
    }
}

IDisposable 구현에 대한보다 완벽한 예제는 MSDN 문서 에서 찾을 수 있습니다.

문 기본 사용

using 은 명시 적 try-finally 블록을 사용하지 않고 리소스를 정리할 수 있도록 허용하는 구문 설탕입니다. 즉, 코드가 훨씬 깨끗해지고 관리되지 않는 리소스가 누출되지 않습니다.

IDisposable 인터페이스 ( FileStream 의 기본 클래스 Stream 이 .NET에서 수행하는)를 구현하는 객체에 대한 표준 Dispose 정리 패턴입니다.

int Foo()
{
    var fileName = "file.txt";

    {
        FileStream disposable = null;

        try
        {
            disposable = File.Open(fileName, FileMode.Open);

            return disposable.ReadByte();
        }
        finally
        {
            // finally blocks are always run
            if (disposable != null) disposable.Dispose();
        }
    }
}

using 은 명시 적 try-finally 숨김으로써 구문 using 단순화합니다.

int Foo()
{
    var fileName = "file.txt";

    using (var disposable = File.Open(fileName, FileMode.Open))
    {
        return disposable.ReadByte();
    }
    // disposable.Dispose is called even if we return earlier
}

마찬가지로 finally 블록은 항상 오류 또는 반환에 관계없이 실행 using 항상 호출 Dispose() 에도 오류가 발생할 경우 :

int Foo()
{
    var fileName = "file.txt";

    using (var disposable = File.Open(fileName, FileMode.Open))
    {
        throw new InvalidOperationException();
    }
    // disposable.Dispose is called even if we throw an exception earlier
}

참고 : Dispose 는 코드 흐름에 관계없이 호출되므로, IDisposable 을 구현할 때 Dispose 가 예외를 throw하지 않도록하는 것이 좋습니다. 그렇지 않으면 실제 예외가 새로운 예외에 의해 무시되어 디버깅의 악몽을 낳습니다.

블록을 사용하여 돌아 오는 중

using ( var disposable = new DisposableItem() )
{
    return disposable.SomeProperty;
}

try..finally 의 의미 때문에 using 블록이 변환되고 return 문은 예상대로 작동합니다. finally 블록이 실행되고 값이 폐기되기 전에 반환 값이 평가됩니다. 평가 순서는 다음과 같습니다.

  1. try 본문 평가
  2. 반환 값 평가 및 캐시
  3. finally 블록 실행
  4. 캐시 된 반환 값을 반환합니다.

그러나 잘못된 처분 된 참조가 포함되어 있으므로 disposable 변수 자체를 반환 할 수 없습니다 ( 관련 예제 참조).

하나의 블록과 함께 여러 문장 사용하기

다중 중첩 된 중괄호를 추가하지 않고 여러 중첩 된 using 문을 사용할 수 있습니다. 예 :

using (var input = File.OpenRead("input.txt"))
{
    using (var output = File.OpenWrite("output.txt"))
    {
        input.CopyTo(output);
    } // output is disposed here
} // input is disposed here

대안은 다음과 같이 작성하는 것입니다.

using (var input = File.OpenRead("input.txt"))
using (var output = File.OpenWrite("output.txt"))
{
    input.CopyTo(output);
} // output and then input are disposed here

첫 번째 예와 정확히 같습니다.

참고 : 중첩 된 문을 using 하면 Microsoft Code Analysis 규칙 CS2002 가 트리거 될 수 있습니다 (설명을 보려면 이 대답 참조). 링크 된 대답에서 설명한 것처럼 일반적으로 문을 using 중첩하는 것이 안전합니다.

using 문 내의 유형이 동일한 유형 인 경우 쉼표로 구분하고 유형을 한 번만 지정할 수 있습니다 (일반적이지는 않지만).

using (FileStream file = File.Open("MyFile.txt"), file2 = File.Open("MyFile2.txt"))
{
}

유형에 공유 계층 구조가있을 때도 사용할 수 있습니다.

using (Stream file = File.Open("MyFile.txt"), data = new MemoryStream())
{
}

위의 예에서는 var 키워드를 사용할 수 없습니다 . 컴파일 오류가 발생합니다. 선언 된 변수에 다른 계층 구조의 유형이있는 경우 쉼표로 구분 된 선언조차도 작동하지 않습니다.

Gotcha : 처분중인 리소스 반환

다음은 db 변수를 반환하기 전에 처리하기 때문에 나쁜 생각입니다.

public IDBContext GetDBContext()
{
    using (var db = new DBContext())
    {
        return db;
    }
}

이것은 또한 더 미묘한 실수를 만들 수 있습니다 :

public IEnumerable<Person> GetPeople(int age)
{
    using (var db = new DBContext())
    {
        return db.Persons.Where(p => p.Age == age);
    }
}

이것은 괜찮아 보이지만 catch는 LINQ 표현식 평가가 DBContext 나중에 기본 DBContext 가 이미 DBContext 경우에만 나중에 실행됩니다.

그래서 표현식은 using 떠나기 전에 평가되지 않습니다. 여전히 사용한다이 문제에 대한 한 가지 가능한 솔루션을 using 결과를 열거하는 메서드를 호출하여 즉시 평가할 수있는 표현을 야기하는 것입니다. 예를 들어 ToList() , ToArray() 등입니다. Entity Framework의 최신 버전을 사용하는 경우 ToListAsync() 또는 ToArrayAsync() 와 같은 async 대응 항목을 사용할 수 있습니다.

다음은 실제 예를 보여줍니다.

public IEnumerable<Person> GetPeople(int age)
{
    using (var db = new DBContext())
    {
        return db.Persons.Where(p => p.Age == age).ToList();
    }
}

ToList() 또는 ToArray() 를 호출하면 표현식이 열심히 평가됩니다. 즉, 지정된 연령의 모든 사람이 반복하지 않더라도 메모리에로드됩니다.

명령문을 사용하면 null이 없습니다.

IDisposable 객체에서 null 을 확인할 필요가 없습니다. using 하면 예외가 발생하지 않고 Dispose() 가 호출되지 않습니다.

DisposableObject TryOpenFile()
{
    return null;
}

// disposable is null here, but this does not throw an exception 
using (var disposable = TryOpenFile())
{
    // this will throw a NullReferenceException because disposable is null
    disposable.DoSomething(); 

    if(disposable != null)
    {
        // here we are safe because disposable has been checked for null
        disposable.DoSomething();
    }
}

Gotcha : 블록 사용에서 다른 오류를 마스킹하는 Dispose 메서드의 예외

다음 코드 블록을 고려하십시오.

try
{
    using (var disposable = new MyDisposable())
    {
        throw new Exception("Couldn't perform operation.");
    }
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

class MyDisposable : IDisposable
{
    public void Dispose()
    {
        throw new Exception("Couldn't dispose successfully.");
    }
}

"작업을 수행 할 수 없습니다"라는 메시지가 콘솔에 표시되지만 실제로는 "처리 할 수 ​​없습니다"라는 메시지가 나타납니다. Dispose 메서드는 첫 번째 예외가 throw 된 후에도 여전히 호출되므로

이 미묘함을 인식 할 가치가 있습니다. 객체가 처분되지 못하게하고 디버그하기가 더 어려워지는 실제 오류를 가려 낼 수 있기 때문입니다.

문과 데이터베이스 연결 사용

using 키워드는 명령문 내에 정의 된 자원이 명령문 자체의 범위 내에 만 존재하도록합니다. 명령문 내에 정의 된 모든 자원은 IDisposable 인터페이스를 구현해야합니다.

이 연결은 IDisposable 인터페이스를 구현하는 모든 연결을 처리 할 때 매우 중요합니다. 연결이 올바르게 닫히지 만 using 문이 범위 using 벗어난 후에 해당 리소스가 해제되도록 보장하기 때문입니다.

일반적인 IDisposable 데이터 클래스

다음은 IDisposable 인터페이스를 구현하고 using 문을 using 하기에 가장 적합한 데이터 관련 클래스입니다.

  • SqlConnection , SqlCommand , SqlDataReader
  • OleDbConnection , OleDbCommand , OleDbDataReader
  • MySqlConnection , MySqlCommand , MySqlDbDataReader
  • DbContext

이 모든 것은 일반적으로 C #을 통해 데이터에 액세스하는 데 사용되며 일반적으로 데이터 중심 응용 프로그램 구축 과정에서 발생합니다. 동일한 FooConnection , FooCommand , FooDataReader 클래스를 구현하는 언급되지 않은 다른 많은 클래스도 동일한 방식으로 동작해야합니다.

ADO.NET 연결에 대한 일반적인 액세스 패턴

ADO.NET 연결을 통해 데이터에 액세스 할 때 사용할 수있는 일반적인 패턴은 다음과 같습니다.

// This scopes the connection (your specific class may vary)
using(var connection = new SqlConnection("{your-connection-string}")
{
    // Build your query
    var query = "SELECT * FROM YourTable WHERE Property = @property");
    // Scope your command to execute
    using(var command = new SqlCommand(query, connection))
    {
         // Open your connection
         connection.Open();

         // Add your parameters here if necessary

         // Execute your query as a reader (again scoped with a using statement)
         using(var reader = command.ExecuteReader())
         {
               // Iterate through your results here
         }
    }
}

또는 단순한 업데이트를 수행하면서 독자가 필요하지 않은 경우에도 동일한 기본 개념이 적용됩니다.

using(var connection = new SqlConnection("{your-connection-string}"))
{
     var query = "UPDATE YourTable SET Property = Value WHERE Foo = @foo";
     using(var command = new SqlCommand(query,connection))
     {
          connection.Open();
          
          // Add parameters here
          
          // Perform your update
          command.ExecuteNonQuery();
     }
}

DataContext와 함께 문 사용

Entity Framework와 같은 많은 ORM은 기본 데이터베이스와 상호 작용하는 데 사용되는 추상화 클래스를 DbContext 와 같은 클래스 형태로 DbContext 합니다. 이러한 컨텍스트는 일반적으로 IDisposable 인터페이스도 구현하며 가능한 경우 명령문을 using 이를 활용해야합니다.

using(var context = new YourDbContext())
{
      // Access your context and perform your query
      var data = context.Widgets.ToList();
}

Dispose 구문을 사용하여 사용자 지정 범위 정의

사용 사례에 따라 using 구문을 사용하여 사용자 지정 범위를 정의 할 수 있습니다. 예를 들어, 특정 문화권에서 코드를 실행하기 위해 다음 클래스를 정의 할 수 있습니다.

public class CultureContext : IDisposable
{
    private readonly CultureInfo originalCulture;

    public CultureContext(string culture)
    {
        originalCulture = CultureInfo.CurrentCulture;
        Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
    }

    public void Dispose()
    {
        Thread.CurrentThread.CurrentCulture = originalCulture;
    }
}

그런 다음이 클래스를 사용하여 특정 문화권에서 실행되는 코드 블록을 정의 할 수 있습니다.

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

using (new CultureContext("nl-NL"))
{
    // Code in this block uses the "nl-NL" culture
    Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 25-12-2016 00:00:00
}

using (new CultureContext("es-ES"))
{        
    // Code in this block uses the "es-ES" culture
    Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 25/12/2016 0:00:00
}

// Reverted back to the original culture
Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 12/25/2016 12:00:00 AM

참고 : 우리가 만든 CultureContext 인스턴스를 사용하지 않기 때문에 우리는 변수를 할당하지 않습니다.

이 기술은 ASP.NET MVC의 BeginForm 도우미 에 의해 사용됩니다.

제약 컨텍스트에서 코드 실행

특정 (제한 조건) 컨텍스트에서 실행하려는 코드 ( 루틴 )가 있으면 종속성 주입을 사용할 수 있습니다.

다음 예는 열린 SSL 연결에서 실행하는 제한 조건을 보여줍니다. 첫 번째 부분은 라이브러리 또는 프레임 워크에 있으며 클라이언트 코드에 노출되지 않습니다.

public static class SSLContext
{
    // define the delegate to inject
    public delegate void TunnelRoutine(BinaryReader sslReader, BinaryWriter sslWriter);

    // this allows the routine to be executed under SSL
    public static void ClientTunnel(TcpClient tcpClient, TunnelRoutine routine)
    {
        using (SslStream sslStream = new SslStream(tcpClient.GetStream(), true, _validate))
        {
            sslStream.AuthenticateAsClient(HOSTNAME, null, SslProtocols.Tls, false);

            if (!sslStream.IsAuthenticated)
            {
                throw new SecurityException("SSL tunnel not authenticated");
            }

            if (!sslStream.IsEncrypted)
            {
                throw new SecurityException("SSL tunnel not encrypted");
            }

            using (BinaryReader sslReader = new BinaryReader(sslStream))
            using (BinaryWriter sslWriter = new BinaryWriter(sslStream))
            {
                routine(sslReader, sslWriter);
            }
        }
    }
}

이제 SSL 하에서 무언가를하고 싶어하지만 모든 SSL 세부 사항을 다루고 싶지 않은 클라이언트 코드. 이제 SSL 터널 내에서 원하는 모든 작업을 수행 할 수 있습니다 (예 : 대칭 키 교환).

public void ExchangeSymmetricKey(BinaryReader sslReader, BinaryWriter sslWriter)
{
    byte[] bytes = new byte[8];
    (new RNGCryptoServiceProvider()).GetNonZeroBytes(bytes);
    sslWriter.Write(BitConverter.ToUInt64(bytes, 0));
}

다음과 같이이 루틴을 실행합니다.

SSLContext.ClientTunnel(tcpClient, this.ExchangeSymmetricKey);

이렇게하려면 try..finally using() 절이 필요합니다 using() try..finally 블록과 별도로) 유일한 방법이므로 클라이언트 코드 ( ExchangeSymmetricKey )가 일회용 리소스를 적절하게 처리하지 않고 종료되지 않도록 할 수 있습니다. using() 절을 using() 하지 않으면 루틴이 해당 자원을 처리하기 위해 컨텍스트의 제약 조건을 깨뜨릴 수 있는지 여부를 알 수 없습니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow