빠른 흐름
// using var — 현재 스코프 끝에서 자동 Dispose
using var reader = new StreamReader(path);
string content = reader.ReadToEnd();
// 메서드 끝 or 예외 발생 시 reader.Dispose() 자동 호출
// using 블록 — 블록 종료 시 Dispose
using (var conn = new SqlConnection(connString))
{
conn.Open();
// 블록 끝에 conn.Dispose()
}
// async 자원
await using var stream = new FileStream(path, FileMode.Open);기본 흐름
어떤 자원 해제 형태가 있나
자원 카드는 아래 네 형태를 먼저 구분하면 됩니다.
using var reader = new StreamReader(path); // 스코프 끝
using (var conn = new SqlConnection(connString)) { } // 블록 끝
await using var stream = new FileStream(path, FileMode.Open); // 비동기 해제
reader.Dispose(); // 수동 해제using var: 현재 스코프 끝using (...) {}: 블록 끝await using:IAsyncDisposableDispose(): 직접 해제
IDisposable이 필요한 이유 — GC로는 충분하지 않다
GC(가비지 컬렉터) 는 메모리를 관리하지만 운영체제 자원은 관리하지 않습니다. 파일 핸들, 소켓, DB 연결, 비관리 메모리는 GC가 수거하기 전까지 계속 점유됩니다.
// GC가 언제 수거할지 알 수 없음
var reader = new StreamReader("big.txt");
// reader = null; // GC 대상이 되어도 파일 잠금은 유지됨
// 다른 프로세스가 이 파일을 열려고 하면 실패
// 명시적 Dispose — 즉시 파일 핸들 해제
reader.Dispose(); // OS에 파일 핸들 즉시 반납IDisposable.Dispose() 는 "지금 당장 자원을 반납하라"는 명시적 계약입니다.
using 문의 두 형태
using 블록 은 블록이 끝나는 정확한 지점에서 Dispose합니다. using var 는 선언된 스코프(메서드, if 블록 등)가 끝날 때 Dispose합니다.
// using 블록 — 정확한 Dispose 시점 제어
using (var writer = new StreamWriter("out.txt"))
{
writer.Write("data");
} // 여기서 Dispose
DoMoreWork(); // writer는 이미 Dispose됨
// using var — 스코프 끝까지 살아있음
using var writer2 = new StreamWriter("out2.txt");
writer2.Write("data");
DoMoreWork(); // writer2는 여기서도 살아있음
// 메서드 끝에서 Disposeusing 문은 컴파일러가 try/finally 형태로 변환합니다. finally에서 정리 메서드를 호출하므로 예외가 발생해도 자원 정리 경로를 놓치기 어렵습니다.
// using var reader = ... 는 컴파일러가 이렇게 변환
StreamReader reader = new StreamReader(path);
try
{
string content = reader.ReadToEnd();
}
finally
{
reader?.Dispose(); // 항상 실행
}IAsyncDisposable — 비동기 정리
비동기 자원(비동기 DB 연결, HTTP 클라이언트 등)은 IAsyncDisposable 을 구현합니다. await using으로 사용합니다.
await using var connection = new SqlConnection(connString);
await connection.OpenAsync();
// await connection.DisposeAsync() 자동 호출IDisposable 직접 구현
비관리 자원을 보유하는 클래스를 만들 때 IDisposable을 구현합니다.
public class ResourceHolder : IDisposable
{
private bool _disposed = false;
private IntPtr _handle = /* 비관리 핸들 */;
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this); // Finalizer 호출 방지
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// 관리 자원 정리 (다른 IDisposable 객체)
}
// 비관리 자원 정리
if (_handle != IntPtr.Zero) { /* 해제 */ }
_disposed = true;
}
}// ❌ Dispose 이후 재사용
using var stream = new FileStream(path, FileMode.Open);
stream.Dispose();
// stream.ReadByte(); // ObjectDisposedException체크포인트
| 항목 | 설명 |
|---|---|
IDisposable | 자원을 즉시 해제하는 계약 |
using (...) {} | 블록 종료 시 정확한 Dispose |
using var | 현재 스코프 끝에서 Dispose |
await using | 비동기 Dispose (IAsyncDisposable) |
GC.SuppressFinalize | Finalizer 중복 호출 방지 |
| finally와 관계 | using은 try/finally로 컴파일됨 |
주의할 점
Dispose된 객체를 이후에 다시 사용하면 ObjectDisposedException 이 발생합니다. using var를 메서드 상단에 선언하면 스코프가 길어져 실수하기 쉽습니다. 자원의 사용 범위를 가능한 한 좁게 유지하고, 사용 후 즉시 Dispose할 수 있는 위치에 선언하세요.
using을 빼면 예외 경로에서 정리를 놓치기 쉬워집니다. 다만 IDisposable이라고 해서 모두 짧은 수명 using이 정답은 아닙니다. 예를 들어 HttpClient처럼 재사용 전략을 따로 가져가야 하는 타입도 있으므로, "자원 해제가 필요한가"와 "권장 수명주기가 무엇인가"를 같이 봐야 합니다.
참고 링크
3 sources