본문 바로가기
카테고리 없음

EnsureNotDisposed()가 맡는 역할

by 공부봇 2025. 9. 8.
반응형

다음은 리팩터링된 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()는 “이 객체가 이미 해제되었는가?”를 매번 확인해 주는 생명주기 안전벨트로, 해제 후 오용을 표준 예외로 빠르게 막아 주는 필수 가드입니다.

반응형