using
C#에서 using 구문은 IDisposable 인터페이스를 구현하는 객체의 자원을 자동으로 해제하기 위한 필수적인 패턴입니다. 특히 스트림Stream과 같은 시스템 리소스를 다룰 때 중요합니다.
IDisposable, Dispose()
IDisposable인터페이스를 구현한 객체에서Dispose()를 호출하면 해당 객체가 소유하고 있던 unmanaged resources에 대한 메모리를 해제합니다. 따라서 가비지 컬렉터가 수거하기를 기다리지 않고 즉시 해제할 수 있어요.
using (var resource = new SomeDisposableResource())
{
// 리소스 사용
} // 블록이 끝나면 자동으로 resource.Dispose()가 호출됨사실 다음 코드와 동일합니다:
var resource = new SomeDisposableResource();
try
{
// 리소스 사용
}
finally
{
if (resource != null)
((IDisposable)resource).Dispose();
}MemoryStream
MemoryStream은 파일이나 네트워크가 아닌 메모리에 데이터를 읽고 쓰기 위한 스트림 클래스입니다. 디스크 I/O 없이 바이트 데이터를 효율적으로 처리할 수 있어요. 특징으로는:
- 메모리 내 데이터 처리로 I/O 병목 현상 제거
- 임시 데이터 저장 및 조작을 위한 유연한 API
- 바이트 배열과 스트림 API 간의 가교 역할 ⇒ MemoryStream은 Stream 추상 클래스의 하위 클래스이므로, 파일 스트림, 네트워크 스트림 등과 같은 다른 스트림과 동일한 API로 데이터를 읽고 쓸 수 있습니다.
이미지 처리에서의 활용
다음은 이미지 데이터를 처리하기 위해 MemoryStream을 사용하는 예시입니다:
using (var ms = new MemoryStream(imageData))
{
// MemoryStream을 사용하여 바이트 배열에서 이미지 디코더 생성
decoder = BitmapDecoder.Create(
ms,
BitmapCreateOptions.None,
BitmapCacheOption.OnLoad); // 이미지를 모두 메모리에 로드
// 이미지 크기 정보 추출
pixelWidth = decoder.Frames[0].PixelWidth;
pixelHeight = decoder.Frames[0].PixelHeight;
// 표준 형식으로 변환하여 처리 단순화
FormatConvertedBitmap convertedBitmap = new FormatConvertedBitmap(
decoder.Frames[0],
pixelFormat,
null,
0);
// 픽셀 데이터 추출
int stride = (pixelWidth * pixelFormat.BitsPerPixel + 7) / 8;
pixelData = new byte[stride * pixelHeight];
convertedBitmap.CopyPixels(pixelData, stride, 0);
}이 코드에서 using 블록을 사용함으로써 MemoryStream이 사용한 리소스가 블록이 종료될 때 자동으로 해제됩니다.
다양한 데이터 형식 처리하기
MemoryStream은 이미지뿐만 아니라 다양한 데이터 형식을 처리하는 데 활용할 수 있습니다.
정수 배열 직렬화 예시
public byte[] SerializeIntArray(int[] data)
{
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(ms))
{
// 배열 길이 저장
writer.Write(data.Length);
// 각 요소 저장
foreach (int value in data)
{
writer.Write(value);
}
}
// 직렬화된 바이트 배열 반환
return ms.ToArray();
}
}
public int[] DeserializeIntArray(byte[] bytes)
{
using (MemoryStream ms = new MemoryStream(bytes))
{
using (BinaryReader reader = new BinaryReader(ms))
{
// 배열 길이 읽기
int length = reader.ReadInt32();
int[] result = new int[length];
// 각 요소 읽기
for (int i = 0; i < length; i++)
{
result[i] = reader.ReadInt32();
}
return result;
}
}
}스트림 결합 및 파이프라인 처리
MemoryStream의 강력한 기능 중 하나는 다른 스트림과 결합하여 데이터 처리 파이프라인을 구성할 수 있다는 점입니다:
public byte[] CompressData(byte[] originalData)
{
using (MemoryStream output = new MemoryStream())
{
using (GZipStream compressionStream = new GZipStream(output, CompressionMode.Compress))
{
using (MemoryStream input = new MemoryStream(originalData))
{
// 데이터 스트림을 압축 스트림으로 복사
input.CopyTo(compressionStream);
} // input MemoryStream 해제
} // compressionStream 해제 (중요: 이때 내부 버퍼가 출력에 기록됨)
return output.ToArray();
} // output MemoryStream 해제
}성능 고려사항
-
초기 용량 설정: 데이터 크기를 미리 알고 있다면 초기 용량을 지정하여 재할당을 줄일 수 있습니다.
// 초기 용량 지정 using (var ms = new MemoryStream(capacity: expectedSize)) -
ToArray() 사용 주의:
ToArray()는 새로운 배열을 생성하므로 불필요한 메모리 할당을 피해야 합니다. -
대용량 데이터: 매우 큰 데이터(수백 MB 이상)를 처리할 때는 메모리 사용량에 주의해야 합니다.
using + MemoryStream
using 구문과 MemoryStream의 조합은 리소스 관리와 메모리 기반 데이터 처리를 효율적으로 수행할 수 있게 해줍니다. 특히 이미지 처리, 직렬화/역직렬화, 네트워크 통신 등에서 디스크 I/O 없이 데이터를 빠르게 처리할 수 있어요.