반응형
다음은 리팩터링된 EmguImage 클래스 안에서 EnsureNotDisposed()가 맡는 역할을 정확하고 자세히 정리한 설명입니다.
EnsureNotDisposed()의 역할
1) 무엇을 하는 함수인가?
- 목적: 이미 Dispose()가 호출되어 더 이상 유효하지 않은 객체에 대해, 추가 사용을 즉시 차단합니다.
- 방법: 내부 플래그(예: m_disposed)를 확인해 true면 **ObjectDisposedException**을 던집니다.
- 효과: 후속 코드가 네이티브 리소스(Mat, Bitmap)에 접근하기 전에 빠르게 실패(fail-fast) 하여, 메모리 접근 위반/핸들 오류/프로세스 크래시 같은 치명적 문제를 예방합니다.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureNotDisposed()
{
if (m_disposed)
throw new ObjectDisposedException(GetType().FullName);
}
2) 왜 꼭 필요한가?
- 이 클래스는 OpenCV/ GDI 같은 네이티브 리소스의 소유자입니다.
Dispose() 이후에는 포인터/핸들이 무효하므로, 사용자가 Draw(), Save(), GetPixel() 같은 메서드를 다시 호출하면 Use-after-free가 발생할 수 있습니다. - EnsureNotDisposed()는 이 상황을 .NET 표준 예외로 명확하게 표현해 주어,
- 버그를 조기 발견(디버깅 용이),
- 정의되지 않은 동작(프로세스 크래시 등)을 방지,
- 클래스의 수명 계약(lifecycle contract) 을 강제합니다.
3) 어디에서 호출해야 하나?
- 모든 public(또는 protected) 멤버의 입구에서 호출하는 것이 원칙입니다.
예: Load, SetImage*, Draw, Save, SaveJpeg, GetPixel, 그리고 BitmapImage, IntPtrImage, Width/Height/Stride 같은 속성 getter도 포함됩니다. - 즉, 객체 상태나 네이티브 리소스에 접근하는 코드 앞에 항상 배치합니다.
4) 어디에서는 호출하면 안 되나?
- Dispose(bool) 내부에서는 호출하지 않습니다.
이미 해제 중인 경로에서 예외를 던지면 정상적인 종료 흐름을 방해할 수 있기 때문입니다. - 같은 이유로, Dispose()가 내부적으로 사용하는 리소스 정리 전용 메서드(예: ReleaseImageCore)에서도 호출하지 않습니다.
→ 해결 패턴:- 외부용 ReleaseImage()는 EnsureNotDisposed()를 호출(정상 사용 가드)
- 내부용 ReleaseImageCore()는 가드 없이 순수 해제만 수행(Dispose 경로에서 사용)
public void ReleaseImage() // 외부에서 호출: 가드 O
{
EnsureNotDisposed();
ReleaseImageCore();
}
private void ReleaseImageCore() // 내부 해제용: 가드 X
{
try { m_Bitmap?.Dispose(); } catch {}
m_Bitmap = null;
try { m_Mat?.Dispose(); } catch {}
m_Mat = null;
}
5) 예시 시나리오
- 사용자가 using (var img = new EmguImage()) { img.Load(...); } 블록을 벗어나면 Dispose()가 호출됩니다.
이후 img.Draw(g)를 호출하면:- EnsureNotDisposed()가 m_disposed == true를 감지 → ObjectDisposedException 발생
- 문제를 즉시, 명확하게 알리고 네이티브 접근을 차단
6) 성능/구현 팁
- 함수 내용이 매우 짧으므로 인라이닝(AggressiveInlining)을 사용해 호출 오버헤드를 사실상 제거할 수 있습니다.
- 멀티스레드 접근이 가능하다면 m_disposed를 volatile로 두거나 Volatile.Read/Write를 사용해 가시성을 확보하는 것이 안전합니다.
(단일 스레드 사용이라면 과하지 않아도 됩니다.)
7) 이점 요약
- 안전성: 네이티브 리소스에 대한 잘못된 접근 차단
- 일관성: .NET 관례에 맞는 ObjectDisposedException 사용
- 디버깅 용이성: 문제 지점을 즉시 파악 가능
- 계약 강제: Dispose() 이후에는 사용 불가라는 규칙을 코드로 보장
한 줄 정리:
EnsureNotDisposed()는 “이 객체가 이미 해제되었는가?”를 매번 확인해 주는 생명주기 안전벨트로, 해제 후 오용을 표준 예외로 빠르게 막아 주는 필수 가드입니다.
반응형