C# Language
명령문 사용
수색…
소개
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
블록이 실행되고 값이 폐기되기 전에 반환 값이 평가됩니다. 평가 순서는 다음과 같습니다.
-
try
본문 평가 - 반환 값 평가 및 캐시
- finally 블록 실행
- 캐시 된 반환 값을 반환합니다.
그러나 잘못된 처분 된 참조가 포함되어 있으므로 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()
하지 않으면 루틴이 해당 자원을 처리하기 위해 컨텍스트의 제약 조건을 깨뜨릴 수 있는지 여부를 알 수 없습니다.