C#은 현대적인 언어이므로 가비지 콜렉터가 사용하지 않는 메모리를 자동으로 해제해 줍니다. 따라서 개발자는 메모리 관리에 신경 쓰지 않고 로직 작성에만 집중할 수 있습니다. 설명 끝.
이면 좋겠지만 현실은 그렇게 간단하지 않는 법입니다. C#이 메모리 관리를 자동으로 해주는 것이 맞지만 우리가 사용하는 리소스가 메모리만 있는 것은 아닙니다. 예를 들어, 운영 체제는 메모리와 별개로 최대로 열 수 있는 파일 개수에 제한이 존재합니다. 리눅스의 경우, /proc/sys/fs/file-max 파일 출력해 보면 최대로 열 수 있는 파일 개수를 알 수 있습니다.
$ cat /proc/sys/fs/file-max
75000
바꿔 말하면 메모리가 많이 남아 있더라도 파일을 75000개 이상 열면 더 이상 파일을 열지 못하는 상황이 발생합니다. 이 문제를 해결하려면 C, C++ 시절 메모리 관리와 마찬가지로 더 이상 사용하지 않는 파일을 수동으로 닫아줘야 합니다.
예를 들어, 우리가 텍스트 파일을 읽을 때 사용하는 StreamReader 클래스는 파일을 열기 때문에 사용 후에는 반드시 Close 메소드를 호출해서 닫아줘야 합니다. 여기서 Close
메소드가 하는 일이 열려 있는 파일을 수동으로 닫아주는 것입니다. 만약, Close
를 실수로 안 불러줬다면 파일이 닫기지 않아 메모리는 남아 돌지만 더 이상 파일을 열지 못하는 상황이 올 수도 있습니다.
var reader = new StreamReader("file.txt");
var content = reader.ReadToEnd();
// Do something with content
reader.Close();
이 코드는 버그가 있습니다. 만약 ReadToEnd
메소드나 content
를 사용하는 코드에서 예외가 발생했다면reader.Close()
메소드가 불리지 않고 빠져나갈 수 있습니다. 정상 종료든 예외 상황이든 파일을 꼭 닫기 위해서는 다음과 같이 finally 블록을 사용해야 합니다.
var reader = new StreamReader("file.txt");
try {
var content = reader.ReadToEnd();
// Do something with content
} finally {
reader.Close();
}
이런 식으로 블록을 빠져 나갈 때 리소스를 수동으로 해제해야 하는 경우가 많기 때문에 C#은 using이라는 키워드를 제공합니다. 위 코드를 using을 이용해 표현해 보면 다음과 같습니다. using 블록을 빠져나갈 때 자동으로 reader
를 해제하기 때문에 더 이상 finally 블록에서 Close
메소드를 호출해 줄 필요가 없게 되었습니다.
using (var reader = new StreamReader("file.txt"))
{
var content = reader.ReadToEnd();
// Do something with content
}
그런데 C#은 각 리소스를 해제하는 방법을 어떻게 아는 걸까요? StreamReader
의 경우는 Close
메소드를 부르면 되지만, 리소스 종료에 따라 해제하는 방법이 다를 수밖에 없습니다. 이런 문제를 해결하려면 각 리소스 클래스가 통일된 인터페이스를 구현해줘야 할 것입니다.
.NET에 이런 목적으로 존재하는 인터페이스가 IDisposable입니다. IDisposable
은 Dispose
메소드를 정의하고 있는데, IDisposable
을 구현한 클래스가 using 블록에 사용되면 using 블록을 빠져나갈 때 C#이 자동으로 Dispose
메소드를 불러 줍니다.
public interface IDisposable
{
void Dispose()
}
앞서 살펴본 StreamReader
클래스도 베이스 클래스인 TextReader
를 통해 IDisposable
인터페이스를 구현하고 있고, Dispose
메소드에서 Close
를 호출하기 때문에 using 블록을 빠져나갈 때 자동으로 리소스가 해제되는 것입니다.
.NET에서는 명시적인 리소스 해제가 필요한 클래스가 모두 IDisposable
인터페이스를 구현하고 있기 때문에 이런 클래스를 사용할 때는 될 수 있으면 using 블록을 사용하는 것이 좋습니다. 또한 우리가 만드는 클래스도 명시적인 리소스 해제가 필요할 경우 IDisposable
를 구현하는 것이 좋은 습관입니다.
Pingback: Using 키워드와 IDisposable | 서광열의 C# 스쿨