C#은 가비지 컬렉터(Garbage Collector, GC)를 통해 대부분의 메모리 관리를 자동으로 처리합니다만, 관리되지 않는 리소스(Unmanaged Resources)를 사용할 때는 개발자가 직접 메모리를 정리하지 않으면 메모리 누수(Memory Leak)가 발생할 수 있습니다. 이러한 문제점을 방지 또는 보완하는 방법으로 `IDisposable` 인터페이스와 디스포저(Dispose) 패턴을 활용합니다. 오늘은 디스포저 패턴에 대해 살펴보고 메모리 누수를 방지에 대해 정리해 보고자 합니다.
IDisposable 인터페이스를 활용한 메모리 누수 방지와 디스포저 패턴
관리되지 않는 리소스와 메모리 누수
관리되지 않는 리소스는 .NET의 가비지 컬렉터가 자동으로 정리하지 않는 리소스를 의미합니다. 대표적인 예로는 다음과 같습니다.
- 파일 핸들
- 데이터베이스 연결
- 소켓
- 시스템 메모리 버퍼
이러한 리소스는 수동으로 해제하지 않으면 계속 메모리를 점유하며, 결국 시스템 성능 저하나 프로그램 크래시로 이어질 수 있습니다.
메모리 누수의 예시
using System;
using System.IO;
class MemoryLeakExample
{
public void WriteToFile(string filePath)
{
FileStream fs = new FileStream(filePath, FileMode.Create);
StreamWriter writer = new StreamWriter(fs);
writer.WriteLine("Hello, World!");
// FileStream과 StreamWriter를 닫지 않음
}
}
// 위 코드는 FileStream과 StreamWriter를 닫지 않아 리소스 누수가 발생합니다.
위 코드는 파일 핸들을 닫지 않기 때문에 운영체제에서 파일 리소스가 해제되지 않습니다. 이를 방지하려면 디스포저 패턴을 적용해야 합니다.
IDisposable 인터페이스와 Dispose 메서드
IDisposable
인터페이스는 관리되지 않는 리소스를 정리하기 위한 표준 인터페이스입니다. 이 인터페이스를 구현하면 Dispose()
메서드가 호출될 때 리소스를 명시적으로 정리할 수 있습니다.
IDisposable 인터페이스 구현
using System;
using System.IO;
class ResourceHandler : IDisposable
{
private FileStream _fileStream;
public ResourceHandler(string filePath)
{
_fileStream = new FileStream(filePath, FileMode.Create);
}
public void WriteToFile(string content)
{
using (StreamWriter writer = new StreamWriter(_fileStream))
{
writer.WriteLine(content);
}
}
public void Dispose()
{
// FileStream 정리
if (_fileStream != null)
{
_fileStream.Close();
_fileStream.Dispose();
_fileStream = null;
}
}
}
- 생성자: FileStream 객체를 초기화
- Dispose 메서드: FileStream 객체를 닫고 메모리를 해제
if (_fileStream != null)
: 중복 해제를 방지
사용 예
class Program
{
static void Main(string[] args)
{
using (ResourceHandler handler = new ResourceHandler("example.txt"))
{
handler.WriteToFile("Hello, Dispose Pattern!");
}
// using 블록이 끝나면 Dispose가 자동 호출됩니다.
}
}
디스포저 패턴 SafeHandle
과 함께 사용
C#에서는 관리되지 않는 리소스를 보다 안전하게 처리하기 위해 SafeHandle
클래스를 제공합니다. SafeHandle
은 리소스를 안전하게 해제하는 데 사용할 수 있습니다.
SafeHandle
을 활용한 디스포저 패턴
using System;
using System.IO;
using Microsoft.Win32.SafeHandles;
class AdvancedResourceHandler : IDisposable
{
private FileStream _fileStream;
private SafeFileHandle _handle;
private bool _disposed = false;
public AdvancedResourceHandler(string filePath)
{
_fileStream = new FileStream(filePath, FileMode.Create);
_handle = _fileStream.SafeFileHandle;
}
public void WriteToFile(string content)
{
if (_disposed)
throw new ObjectDisposedException("AdvancedResourceHandler");
using (StreamWriter writer = new StreamWriter(_fileStream))
{
writer.WriteLine(content);
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// 관리되는 리소스 해제
if (_fileStream != null)
{
_fileStream.Close();
_fileStream.Dispose();
}
}
// 관리되지 않는 리소스 해제
if (_handle != null && !_handle.IsInvalid)
{
_handle.Dispose();
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~AdvancedResourceHandler()
{
Dispose(false);
}
}
SafeFileHandle
사용: 관리되지 않는 파일 핸들을 안전하게 관리합니다.
Dispose(bool disposing)
: 관리되는 리소스와 관리되지 않는 리소스를 구분하여 정리합니다.
~AdvancedResourceHandler()
: 소멸자(Finalizer)에서 관리되지 않는 리소스를 정리합니다.
GC.SuppressFinalize(this)
: 가비지 컬렉터가 불필요하게 소멸자를 호출하지 않도록 방지합니다.
사용 예
class Program
{
static void Main(string[] args)
{
using (AdvancedResourceHandler handler = new AdvancedResourceHandler("example_advanced.txt"))
{
handler.WriteToFile("Using SafeHandle in Dispose Pattern!");
}
}
}
'프로그래밍 언어 > C#' 카테고리의 다른 글
C# Record 타입과 Init-Only 프로퍼티 불변 객체 활용법 - C# 25 (0) | 2024.11.20 |
---|---|
C# 가비지 컬렉션 메모리 관리와 최적화 - C# 23 (0) | 2024.11.15 |
C# Task와 Task<T>를 활용한 비동기 프로그래밍 - C# 22 (0) | 2024.11.14 |
C# 비동기 프로그래밍 async와 await로 네트워크와 파일 작업 처리하기 - C# 21 (0) | 2024.11.13 |
C# 람다 표현식과 LINQ 활용 그리고 컬렉션 필터링과 코드 최적화 이해하기 - C# 20 (0) | 2024.11.12 |