IDisposable 인터페이스의 올바른 사용


1657

인터페이스 의 "기본"사용은 관리되지 않는 리소스를 정리하는 것이라고 Microsoft 설명서 를 읽은 것으로 알고 IDisposable있습니다.

나에게 "관리되지 않는"은 데이터베이스 연결, 소켓, 창 핸들 등과 같은 것을 의미합니다. 그러나 가비지 수집기가 처리해야하기 때문에 관리 리소스 Dispose()를 해제하기 위해 메서드가 구현되는 코드를 보았습니다. 당신을 위해.

예를 들면 다음과 같습니다.

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

내 질문은, 이것이 가비지 수집기에서 사용하는 메모리를 MyCollection평소보다 빠르게 사용 합니까?

편집 : 지금까지 사람들은 데이터베이스 연결 및 비트 맵과 같은 관리되지 않는 리소스를 정리하기 위해 IDisposable을 사용하는 좋은 예를 게시했습니다. 그러나 _theList위의 코드에 백만 개의 문자열이 포함되어 있고 가비지 수집기를 기다리는 대신 지금 해당 메모리를 비우기를 원한다고 가정하십시오 . 위의 코드가 그것을 달성 할 것입니까?


34
IDisposable을 사용하는 올바른 '패턴'을 알려주기 때문에 받아 들인 대답이 마음에 들지만 OP가 편집에서 말한 것처럼 의도 한 질문에 대답하지 않습니다. IDisposable은 GC를 '호출'하는 것이 아니라 객체를 파괴 가능한 것으로 '표시'합니다. 그러나 GC가 시작되기를 기다리는 대신 '지금'메모리를 확보하는 실제 방법은 무엇입니까? 이 질문에 대해 더 많은 논의가 필요하다고 생각합니다.
Punit Vora

40
IDisposable아무것도 표시하지 않습니다. 이 Dispose방법은 인스턴스에서 사용하는 리소스를 정리하기 위해 수행해야하는 작업을 수행합니다. 이것은 GC와 관련이 없습니다.
John Saunders

4
@남자. 이해 IDisposable합니다. 그렇기 때문에 IDisposable이 <i> 메모리 비우기 </ i>에 도움이 될지 여부에 대한 OP의 의도 된 질문 (및 후속 편집)에 대한 답변이 허용되지 않는다고 말했습니다. 때문에 IDisposable당신이 말한 다음과 같은 메모리 자원 만 확보와는 아무 상관이없는, 영업 이익은 자신의 예에서 무엇을하고 있었는지되는 전혀 null로 관리되는 참조를 설정할 필요가 없습니다. 따라서 그의 질문에 대한 정답은 "아니오, 메모리를 더 빨리 확보하는 데 도움이되지 않습니다. 실제로는 메모리를 확보하는 데 전혀 도움이되지 않으며 리소스 만"입니다. 어쨌든 귀하의 의견에 감사드립니다.
Punit Vora

9
@desigeek : 만약 이것이 사실이라면, "IDisposable이 GC를 '호출'하지 않고, 대상을 파괴 가능한 것으로 표시합니다"
John Saunders

5
@desigeek : 메모리를 결정적으로 해제하는 보장 된 방법은 없습니다. GC.Collect ()를 호출 할 수 있지만 이는 요구가 아니라 정중 한 요청입니다. 가비지 수집을 진행하려면 실행중인 모든 스레드를 일시 중단해야합니다. 자세한 내용을 보려면 .NET Safepoints 개념을 읽으십시오 (예 : msdn.microsoft.com/en-us/library/678ysw69(v=vs.110)). aspx . 관리되지 않는 코드를 호출하는 등의 이유로 스레드를 일시 중지 할 수없는 경우 GC.Collect ()는 아무 것도 수행하지 않을 수 있습니다.
콘크리트 가넷

답변:


2608

폐기의 포인트 입니다 관리되지 않는 리소스를 해제 할 수 있습니다. 특정 시점에 수행해야합니다. 그렇지 않으면 정리되지 않습니다. 가비지 컬렉터가 모르는 방법 전화 DeleteHandle()유형의 변수에 IntPtr, 그것은 모르는 여부 가 호출 할 필요 여부 DeleteHandle().

참고 : 관리되지 않는 리소스 란 무엇입니까 ? Microsoft .NET Framework에서 찾은 경우 관리됩니다. MSDN을 직접 둘러 보았다면 관리되지 않습니다. P / Invoke 호출을 사용하여 .NET Framework에서 사용할 수있는 모든 것의 편안한 세상을 벗어나는 것은 관리되지 않으며 이제는이를 정리해야합니다.

당신의 요구를 만든 것을 목적은 노출하는 일부 외부 세계가 관리되지 않는 리소스를 정리하기 위해 호출 할 수있는, 방법. 이 방법의 이름은 원하는대로 지정할 수 있습니다.

public void Cleanup()

또는

public void Shutdown()

그러나 대신이 방법에 대한 표준화 된 이름이 있습니다.

public void Dispose()

심지어 IDisposable하나의 메소드를 가진 인터페이스가 생성되었습니다 .

public interface IDisposable
{
   void Dispose()
}

따라서 객체가 IDisposable인터페이스를 노출하게 만들고 관리되지 않는 리소스를 정리하는 단일 방법을 작성했다고 약속합니다.

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

그리고 당신은 끝났습니다. 더 잘할 수 있다면 말고


객체가 250MB의 System.Drawing.Bitmap (즉, .NET 관리 비트 맵 클래스)을 일종의 프레임 버퍼로 할당 한 경우 어떻게 됩니까? 물론 이것은 관리되는 .NET 객체이며 가비지 수집기는이를 해제합니다. 그러나 250MB의 메모리를 그대로두고 싶 습니까? 가비지 수집기가 결국 와서 해제 할 때까지 기다리 십니까? 열린 데이터베이스 연결 이 있으면 어떻게 됩니까? 확실히 우리는 GC가 객체를 마무리하기를 기다리면서 연결이 열려있는 것을 원하지 않습니다.

사용자가 Dispose()(더 이상 객체를 사용할 계획이 없음을 의미) 호출 한 경우 낭비되는 비트 맵과 데이터베이스 연결을 제거하지 않는 이유는 무엇입니까?

이제 우리는 :

  • 관리되지 않는 리소스를 제거해야합니다 (필수로 인해).
  • 도움이되기를 원하기 때문에 관리 자원 제거

Dispose()관리 객체를 제거하기 위해 메소드를 업데이트 해 봅시다 :

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

그리고 당신이 더 잘할 수 있다는 것을 제외하고 는 모든 것이 좋습니다 !


사람이 물건 을 부르는 것을 잊어 버린 경우 어떻게해야 Dispose()합니까? 그러면 관리되지 않는 리소스 가 누출 될 것입니다 !

참고 : 결국 가비지 수집기가 백그라운드 스레드에서 실행되고 사용되지 않는 개체와 관련된 메모리를 해제하기 때문에 관리되는 리소스가 누출되지 않습니다 . 여기에는 개체 및 사용하는 모든 관리되는 개체 (예 : BitmapDbConnection)가 포함됩니다.

사람이 전화를 잊어 버린 경우 에도 베이컨을 저장할 Dispose()있습니다 ! 가비지 수집기가 마침내 객체를 해제 (즉, 마무리) 할 때까지이를 호출 할 있습니다.

참고 : 가비지 수집기는 결국 모든 관리 대상 개체를 해제합니다. 그렇게되면 Finalize 객체 에서 메소드를 호출 합니다. GC의 알다시피, 나에 대한 배려하지 않는 당신의 폐기 방법. 그것은 우리가 관리되지 않는 것들을 제거하고 싶을 때 호출하는 방법으로 선택한 이름입니다.

가비지 콜렉터가 오브젝트를 파괴 하는 것은 성가신 관리되지 않는 자원을 자유롭게하기 위한 완벽한 시간입니다. 우리는 방법을 재정 의하여이 작업을 수행합니다 Finalize().

참고 : C #에서는 Finalize()메서드를 명시 적으로 재정의하지 않습니다 . 당신이하는 방법을 쓰기 같은 외모 C ++ 소멸자 및 컴파일러의 구현으로 그 소요 Finalize()방법 :

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

그러나 해당 코드에 버그가 있습니다. 가비지 수집기는 백그라운드 스레드 에서 실행됩니다 . 당신은 두 물체가 파괴되는 순서를 모른다. 당신에 전적으로 가능하다 Dispose()코드의 관리 당신이하려는 개체가 (당신이 도움이 될 싶었 기 때문에) 더 이상 존재하지 없애 :

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

따라서 관리 대상 리소스를 더 이상 사용 하지 않을 수 있으므로 관리 리소스를 건드리지 말고 관리되지 않는 리소스를 계속 확보 해야한다는 것을 Finalize()수 있습니다 .Dispose()

이 작업을 수행하는 표준 패턴을 가지고있다 Finalize()그리고 Dispose()모두 전화 발신 제 3 (!) 방법; 여기서 불리언 말을 전달하는 경우 Dispose()(와 반대로 Finalize()), 관리되는 리소스를 해제하는 것이 안전하다는 의미입니다.

내부 메소드에는 "CoreDispose"또는 "MyInternalDispose"와 같은 임의의 이름이 지정 수 있지만이를 호출하는 것이 일반적입니다 Dispose(Boolean).

protected void Dispose(Boolean disposing)

그러나 더 유용한 매개 변수 이름은 다음과 같습니다.

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

그리고 IDisposable.Dispose()메소드 구현을 다음과 같이 변경하십시오 .

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

그리고 당신의 파이널 라이저는 :

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

참고 : 객체가을 구현하는 객체의 자손 인 경우 Dispose를 재정의 할 때 기본 Dispose 메서드 Dispose를 호출하는 것을 잊지 마십시오.

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

그리고 당신이 더 잘할 수 있다는 것을 제외하고 는 모든 것이 좋습니다 !


사용자가 Dispose()개체를 호출하면 모든 것이 정리 된 것입니다. 나중에 가비지 수집기가 와서 Finalize를 호출하면 Dispose다시 호출 됩니다.

이 방법은 낭비 일뿐만 아니라 객체에 마지막 호출 에서 이미 폐기 한 객체에 대한 정크 참조가있는 경우 해당 객체를 Dispose()다시 처리하려고합니다.

내 코드에서 내가 처리 한 객체에 대한 참조를 제거하는 데주의를 기울 였으므로 Dispose정크 객체 참조 를 호출하지 않습니다 . 그러나 그것은 미묘한 버그가 들어 오는 것을 막지 못했습니다.

사용자가 호출하면 Dispose(): 핸들 CursorFileBitmapIconServiceHandle 이 삭제됩니다. 나중에 가비지 수집기가 실행되면 동일한 핸들을 다시 파괴하려고 시도합니다.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

이 문제를 해결하는 방법은 가비지 수집기에 개체를 마무리 할 필요가 없음을 알리는 것입니다. 리소스가 이미 정리되었으며 더 이상 작업이 필요하지 않습니다. 메소드 를 호출 GC.SuppressFinalize()하여 이를 수행하십시오 Dispose().

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

사용자가을 호출 Dispose()했으므로

  • 관리되지 않는 리소스 해제
  • 해제 된 관리 자원

GC에는 파이널 라이저를 실행하는 것이 중요하지 않습니다. 모든 것이 처리됩니다.

관리되지 않는 리소스를 정리하기 위해 Finalize를 사용할 수 없습니까?

에 대한 설명서 Object.Finalize는 다음과 같습니다.

Finalize 메서드는 개체가 삭제되기 전에 현재 개체가 보유한 관리되지 않는 리소스에 대해 정리 작업을 수행하는 데 사용됩니다.

그러나 MSDN 설명서에서도 다음과 같이 말합니다 IDisposable.Dispose.

관리되지 않는 리소스 해제, 해제 또는 재설정과 관련된 응용 프로그램 정의 작업을 수행합니다.

그래서 어느 것입니까? 관리되지 않는 리소스를 정리할 수있는 곳은 어디입니까? 정답은:

당신의 선택입니다! 그러나를 선택하십시오 Dispose.

마무리되지 않은 정리는 마무리 도구에 넣을 수 있습니다.

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

문제는 가비지 수집기가 언제 객체를 마무리할지 알 수 없다는 것입니다. 가비지 수집기 실행될 때까지 관리되지 않고 필요하지 않은 사용하지 않은 기본 리소스가 계속 사용 됩니다. 그런 다음 finalizer 메서드를 호출합니다. 관리되지 않는 리소스 정리 Object.Finalize 의 문서는 이것을 지적합니다.

종료자가 실행되는 정확한 시간은 정의되어 있지 않습니다. 클래스 인스턴스에 대한 결정적인 자원 릴리스를 보장하려면 Close 메소드를 IDisposable.Dispose구현 하거나 구현을 제공하십시오 .

이것은 Dispose관리되지 않는 리소스를 정리 하는 데 사용하는 장점입니다 . 관리되지 않는 리소스가 정리되면이를 파악하고 제어 할 수 있습니다. 그들의 파괴는 "결정 론적" 이다.


원래의 질문에 대답하기 위해 : GC가 메모리를 결정할 때가 아니라 지금 메모리를 해제하지 않는 이유는 무엇입니까? 나는 그 얼굴 인식 소프트웨어가없는 요구가 내부 이미지의 530메가바이트 제거하는 지금이 더 이상 필요 것 때문에. 그렇지 않은 경우 : 기계가 교환 정지로 분쇄됩니다.

보너스 독서

이 답변의 스타일 (설명하는이 좋아하는 사람들을위한 이유 (가) 그래서 방법을 분명하게), 당신이 돈 상자의 필수 COM의 장 하나를 읽어 제안을 :

35 페이지에서 그는 이진 객체 사용의 문제점을 설명하고 COM을 당신의 눈앞에서 발명합니다. COM 의 이유 를 알고 나면 나머지 300 페이지는 분명하며 Microsoft의 구현에 대해 자세히 설명합니다.

객체 나 COM을 다루는 모든 프로그래머는 최소한 첫 번째 장을 읽어야한다고 생각합니다. 그것은 무엇이든에 대한 최고의 설명입니다.

추가 보너스 독서

Eric Lippert 가 아는 모든 것이 잘못되었을 때

따라서 올바른 종료자를 작성하는 것은 실제로 매우 어렵습니다. 제가 드릴 수있는 최선의 조언은 시도하지 않는 것 입니다.


12
더 잘 할 수 있습니다-Dispose에서 GC.SuppressFinalize ()에 대한 호출을 추가해야합니다.
plinth

55
@Daniel Earwicker : 사실입니다. Microsoft는 Win32 사용을 중단하고 추상화 가능하고 휴대 가능한 장치 독립적 .NET Framework 호출을 고수합니다. 아래의 운영 체제를 파고 들고 싶다면; OS가 무엇을 실행하는지 알고 있다고 생각 하기 때문에 자신의 손에 인생을 보내고 있습니다. 모든 .NET 앱이 Windows 또는 데스크톱에서 실행되는 것은 아닙니다.
Ian Boyd

34
이것은 훌륭한 답변이지만 표준 사례와 클래스가 이미 Dispose를 구현하는 기본 클래스에서 파생되는 경우 최종 코드 목록의 이점을 얻을 수 있다고 생각합니다. 예를 들어 여기 ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ) 를 읽었을 때 이미 Dispose를 구현하는 클래스에서 파생 할 때해야 할 일에 대해 혼란스러워했습니다. 야, 나는 이것에 익숙하지 않다).
integra753 2019

5
@GregS 및 기타 : 일반적으로 참조 설정을 귀찮게하지 않습니다 null. 우선, 그것은 당신이 그것들을 만들 수 없다는 것을 의미하며 readonly, 둘째로 !=null(예제 코드에서와 같이) 매우 못생긴 검사 를해야 합니다. flag를 가질 수 disposed있지만 신경 쓰지 않는 것이 더 쉽습니다. .NET GC는 필드 x를 참조 할 때 더 이상 필드에 대한 참조가 '사용'되지 않도록 충분히 공격적 x.Dispose()입니다.
porges

7
당신이 언급 한 Don Box의 두 번째 페이지에서, 그는 검색 알고리즘의 O (1) 구현 예를 사용합니다. 나는 웃었다.
wip

65

IDisposableusing문 을 활용하고 관리 대상 개체를 결정적으로 정리하는 쉬운 방법 을 활용하는 데 종종 사용됩니다 .

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

6
나는 개인적으로 그것을 좋아하지만 실제로 프레임 워크 디자인 지침에 따라 움직이지 않습니다.
mqp

4
특히 예외 처리, 잠금 및 관리되지 않는 리소스 사용 블록을 복잡한 방식으로 혼합 할 때 결정적인 범위 및 범위 구성 / 정리를 쉽게 할 수 있기 때문에 적절한 디자인이라고 생각합니다. 언어는이를 일급 기능으로 제공합니다.
yfeldblum

FDG에 지정된 규칙을 정확하게 따르지는 않지만 "using statement"에서 사용하기 위해 패턴을 반드시 올바르게 사용해야합니다.
Scott Dorman

2
Log.Outdent가 발생하지 않는 한 이것에는 아무런 문제가 없습니다.
Daniel Earwicker

1
에 대한 다양한 답변 입니다 그것은 예외 안전을 위해 "범위의 행동을"가져 오기위한 수단으로는 IDisposable와 "사용"을 사용하는 학대? 다른 사람들이이 기술을 좋아하거나 싫어하는 이유에 대해 좀 더 자세히 설명하겠습니다. 다소 논란의 여지가 있습니다.
Brian

44

Dispose 패턴의 목적은 관리되는 리소스와 관리되지 않는 리소스를 모두 정리하는 메커니즘을 제공하는 것이며, 발생하는시기는 Dispose 메서드의 호출 방식에 따라 다릅니다. 예를 들어, 목록을 지우면 폐기되는 컬렉션에 영향을 미치지 않으므로 Dispose를 사용하면 실제로 Dispose와 관련된 작업을 수행하지 않습니다. 마찬가지로 변수를 null로 설정하는 호출도 GC에 영향을 미치지 않습니다.

Dispose 패턴을 구현하는 방법에 대한 자세한 내용 은이 기사 를 참조하십시오. 그러나 기본적으로 다음과 같습니다.

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

여기서 가장 중요한 방법은 Dispose (bool)이며 실제로 두 가지 상황에서 실행됩니다.

  • disposing == true : 메소드가 사용자 코드에 의해 직접 또는 간접적으로 호출되었습니다. 관리 및 관리되지 않는 리소스를 폐기 할 수 있습니다.
  • disposing == false : 종료자가 내부에서 런타임에 의해 메소드를 호출했으며 다른 오브젝트를 참조하지 않아야합니다. 관리되지 않는 리소스 만 폐기 할 수 있습니다.

GC가 정리를 처리하게하는 문제는 GC가 수집주기를 실행할시기를 실제로 제어 할 수 없다는 것입니다 (GC.Collect ()를 호출 할 수는 있지만 실제로는 그렇게하지 않아야합니다). 필요 이상으로 길다. Dispose ()를 호출한다고해서 실제로 수집주기가 발생하거나 GC가 개체를 수집 / 해제하지는 않습니다. 사용 된 리소스를보다 결정적으로 정리하고 GC에이 정리가 이미 수행되었음을 알리는 수단을 제공합니다.

IDisposable 및 dispose 패턴의 요점은 즉시 메모리를 해제하지 않습니다. Dispose를 호출하면 실제로 메모리를 즉시 해제 할 수있는 유일한 시간은 == false 시나리오를 처리하고 관리되지 않는 리소스를 조작 할 때뿐입니다. 관리 코드의 경우 GC가 수집주기를 실행할 때까지 실제로 메모리를 회수하지 않습니다. 수집주기는 실제로 제어 할 수 없습니다 (이미 언급 한 GC.Collect () 호출은 좋지 않습니다).

.NET의 문자열은 정렬되지 않은 리소스를 사용하지 않고 IDisposable을 구현하지 않으므로 시나리오를 실제로 유효하지 않습니다. 강제로 "정리"할 수있는 방법이 없습니다.


4
종료자를 구현하는 것을 잊지 않았습니까?
Budda

@ 부다 : 아니요, 그는 SafeHandle을 사용하고 있습니다. 소멸자가 필요하지 않습니다.
Henk Holterman

9
Dispose ()를 여러 번 호출 할 때 안전망을 추가하려면 +1하십시오. 사양에 따르면 여러 통화가 안전해야합니다. 너무 많은 Microsoft 클래스가이를 구현하지 못하면 ObjectDisposedException이 발생합니다.
Jesse Chisholm

5
그러나 Dispose (bool disposing)는 SimpleCleanup 클래스의 고유 한 메서드이며 프레임 워크에서 호출하지 않습니다. 매개 변수로 "true"를 사용하여 호출하기 때문에 'disposing'은 절대 false가 아닙니다. 코드는 IDisposable에 대한 MSDN 예제와 매우 유사하지만 @Budda가 지적한 것처럼 finalizer가 부족합니다.
yoyo

19

Dispose가 호출 된 후에는 개체의 메서드에 대한 추가 호출이 없어야합니다 (물론 Dispose에 대한 추가 호출을 허용해야하지만). 따라서 문제의 예는 바보입니다. Dispose가 호출되면 객체 자체를 버릴 수 있습니다. 따라서 사용자는 해당 전체 객체에 대한 모든 참조를 버리고 (null로 설정) 내부의 모든 관련 객체가 자동으로 정리됩니다.

관리 / 관리되지 않는 일반적인 질문과 다른 답변의 토론에 관해서는이 질문에 대한 답은 관리되지 않는 리소스의 정의로 시작해야한다고 생각합니다.

요약하자면 시스템을 상태로 만들기 위해 호출 할 수있는 함수가 있고 해당 상태에서 시스템을 다시 가져 오기 위해 호출 할 수있는 다른 기능이 있다는 것입니다. 이제 일반적인 예에서 첫 번째 것은 파일 핸들을 반환하는 함수이고 두 번째는에 대한 호출 일 수 있습니다 CloseHandle.

그러나 이것이 핵심입니다. 일치하는 함수 쌍이 될 수 있습니다. 하나는 상태를 만들고 다른 하나는 상태를 찢습니다. 상태가 구축되었지만 아직 해제되지 않은 경우 리소스 인스턴스가 존재합니다. 적절한 시간에 분류가 발생하도록 준비해야합니다. 리소스는 CLR에서 관리하지 않습니다. 자동으로 관리되는 유일한 자원 유형은 메모리입니다. GC와 스택의 두 가지 종류가 있습니다. 값 유형은 스택에 의해 관리되거나 참조 유형 내에서 승차감을 치거나 GC가 참조 유형을 관리합니다.

이러한 함수는 자유롭게 인터리브 될 수있는 상태 변경을 야기하거나 완벽하게 중첩되어야 할 수도 있습니다. 상태 변경은 스레드로부터 안전하거나 그렇지 않을 수 있습니다.

법무부의 질문에 나오는 예를보십시오. 로그 파일 들여 쓰기에 대한 변경 사항은 완벽하게 중첩되어야합니다. 그렇지 않으면 모두 잘못됩니다. 또한 스레드 안전하지 않을 것입니다.

가비지 수집기를 사용하여 관리되지 않는 리소스를 정리할 수 있습니다. 그러나 상태 변경 기능이 스레드 안전하고 두 상태가 수명이 겹치는 경우에만 가능합니다. 따라서 법무부의 자원에 대한 예에는 종결자가 없어야합니다! 그것은 아무도 도움이되지 않습니다.

이러한 종류의 리소스의 경우 종료 자없이을 구현할 수 있습니다 IDisposable. 파이널 라이저는 절대적으로 선택 사항입니다. 이것은 많은 책에서 언급되거나 언급되지 않은 것입니다.

그런 다음 using명령문 을 사용하여 Dispose호출 될 가능성을 확인해야 합니다. 이것은 본질적으로 스택으로 라이딩을 맞추는 것과 같습니다 (파이널 라이저는 GC using에 있고 스택에 있습니다).

누락 된 부분은 Dispose를 수동으로 작성하여 필드와 기본 클래스를 호출해야한다는 것입니다. C ++ / CLI 프로그래머는 그렇게 할 필요가 없습니다. 컴파일러는 대부분의 경우이를 위해이를 작성합니다.

완벽하게 중첩되고 스레드로부터 안전하지 않은 상태에 대해 선호하는 대안이 있습니다 (IDisposable을 피하면 IDisposable을 구현하는 모든 클래스에 종료자를 추가하는 것을 거부 할 수없는 사람과의 논쟁이 필요하지 않습니다) .

클래스를 작성하는 대신 함수를 작성합니다. 이 함수는 델리게이트가 다음을 호출하도록 허용합니다.

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

그리고 간단한 예는 다음과 같습니다.

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

전달되는 람다는 코드 블록의 역할을하므로 using더 이상 발신자가 악용 할 위험이 없다는 점을 제외하고는와 동일한 목적을 위해 자체 제어 구조를 만드는 것과 같습니다 . 리소스 정리에 실패 할 수있는 방법은 없습니다.

자원이 수명이 겹칠 수있는 종류 인 경우이 기술은 유용하지 않습니다. 자원 A, 자원 B, 자원 A를 종료 한 다음 나중에 자원 B를 종료 할 수 있기를 원하기 때문입니다. 사용자가 이처럼 완벽하게 중첩되도록 강요 한 경우 그러나 그런 다음 IDisposable스레드 안전을 구현하지 않은 한 아직 종료자가 없어도 사용해야합니다 (무료는 아닙니다).


re : "Dispose가 호출 된 후에는 더 이상 개체의 메서드를 호출하지 않아야합니다." "해야한다"는 실 용어이다. 보류중인 비동기 작업이있는 경우 개체가 폐기 된 후에 발생할 수 있습니다. ObjectDisposedException 발생.
Jesse Chisholm

관리되지 않는 리소스가 GC가 이해하지 못하는 상태를 캡슐화한다는 아이디어를 다루는 것은 내 것 이외의 유일한 대답 인 것 같습니다. 그러나 관리되지 않는 리소스의 주요 측면은 상태를 정리해야하는 하나 이상의 엔터티가 리소스를 "소유하는"개체가 없어도 계속 존재할 수 있다는 것입니다. 내 정의는 어떻습니까? 꽤 비슷하지만 "자원"을 좀 더
명쾌하게 만든다고 생각

@supercat-관심이 있으시면
Daniel Earwicker

1
@DanielEarwicker : 흥미있는 기사이지만 실제로 다루지 않는 관리되지 않는 리소스 중 적어도 하나는 오래 지속되는 객체의 이벤트에 대한 구독을 생각할 수 있습니다. 이벤트 구독은 재미 있지만, 메모리가 무제한으로 처리 되더라도 처리에 많은 비용이들 수 있습니다. 예를 들어, 열거 중 수정을 허용하는 콜렉션에 대한 열거자는 콜렉션에서 업데이트 알림을 구독해야 할 수 있으며 콜렉션은 수명주기 동안 여러 번 갱신 될 수 있습니다.
구독자

1
한 쌍의 작업 enter이며 exit리소스를 생각하는 방식의 핵심입니다. 이벤트 구독 / 가입 취소는 어려움없이 적용 할 수 있습니다. 직교 / 연산 특성과 관련하여 메모리 누수와 실질적으로 구분할 수 없습니다. 구독이 목록에 개체를 추가하는 것만 큼 놀랍지 않습니다.
Daniel Earwicker

17

IDisposable을 사용하는 시나리오 : 관리되지 않는 리소스 정리, 이벤트 구독 취소, 연결 끊기

IDisposable 구현에 사용하는 관용구 ( threadsafe 아님) :

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}


3
관리되지 않는 리소스가 없으면 파이널 라이저가 포함되어서는 안됩니다. 그럼에도 불구하고 선호되는 구현은 관리되지 않는 리소스를 SafeHandle에 래핑하는 것입니다.
Dave Black

11

그러나이 코드는 완전히 중복되고 불필요하며 가비지 수집기가 다른 방법으로 수행하지 않는 작업을 수행하지 않습니다 (MyCollection의 인스턴스가 범위를 벗어난 경우) .Clear().

편집에 대한 답변 : 정렬 내가 이렇게하면 :

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

메모리 관리를 위해 기능적으로 동일합니다.

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

이 순간에 메모리를 정말로 확보해야하는 경우,을 호출하십시오 GC.Collect(). 그러나 여기서 할 이유가 없습니다. 필요할 때 메모리가 해제됩니다.


2
re : "필요할 때 메모리가 해제됩니다." 오히려 "GC가 필요할 때 결정합니다." GC가 메모리가 실제로 필요하다고 결정하기 전에 시스템 성능 문제가 나타날 수 있습니다 . 지금 해제하는 것이 필수는 아니지만 유용 할 수 있습니다.
Jesse Chisholm

1
컬렉션 내에서 참조를 무효화하면 참조 된 항목의 가비지 수집이 촉진 될 수있는 몇 가지 경우가 있습니다. 예를 들어, 대규모 배열을 만들어 새로 만든 작은 항목에 대한 참조로 채워지지만 그 이후로 오랫동안 필요하지 않은 경우 배열을 포기하면 다음 레벨 2 GC까지 해당 항목이 유지 될 수 있습니다. 처음에 0을 지우면 다음 레벨 0 또는 레벨 1 GC에 적합합니다. 큰 객체 힙에 큰 수명이 짧은 객체를 갖는 것은 어쨌든 icky (나는 디자인을 싫어함)이지만 ...
supercat

1
... 그러한 배열을 포기하기 전에 GC 영향을 줄입니다.
supercat

11

MyCollection어쨌든 가비지 수집 될 예정 이라면 폐기 할 필요가 없습니다. 그렇게하면 CPU가 필요 이상으로 떨 어지고 가비지 수집기가 이미 수행 한 미리 계산 된 일부 분석이 무효화 될 수도 있습니다.

IDisposable관리되지 않는 리소스와 함께 스레드가 올바르게 배치되었는지 확인 하는 데 사용 합니다.

편집 Scott의 의견에 대한 답변 :

GC 성능 측정 항목이 영향을받는 유일한 시간은 [sic] GC.Collect ()를 호출 할 때입니다. "

개념적으로 GC는 객체 참조 그래프의보기를 유지하고 스레드의 스택 프레임에서 이에 대한 모든 참조를 유지합니다. 이 힙은 상당히 클 수 있으며 많은 메모리 페이지에 걸쳐 있습니다. 최적화로서 GC는 불필요하게 페이지를 다시 스캔하지 않도록 자주 변경되지 않는 페이지 분석을 캐시합니다. GC는 페이지의 데이터가 변경 될 때 커널로부터 알림을 수신하므로 페이지가 더럽고 재검색이 필요하다는 것을 알고 있습니다. 컬렉션이 Gen0에 있으면 페이지의 다른 것들도 변경 될 가능성이 있지만 Gen1 및 Gen2에서는 그 가능성이 적습니다. 이 플랫폼에서 Silverlight 플러그인을 사용하기 위해 GC를 Mac으로 이식 한 팀의 경우 Mac OS X에서는 이러한 후크를 사용할 수 없었습니다.

불필요한 자원 폐기에 대한 또 다른 요점 : 프로세스가 언로드되는 상황을 상상해보십시오. 프로세스가 얼마 동안 실행되었다고 상상해보십시오. 해당 프로세스의 많은 메모리 페이지가 디스크로 스왑되었을 수 있습니다. 최소한 L1 또는 L2 캐시에는 없습니다. 이러한 상황에서는 프로세스가 종료 될 때 운영 체제에서 해제 될 '릴리스'리소스로 모든 데이터 및 코드 페이지를 메모리로 다시 스왑하기 위해 언로드하는 응용 프로그램이 필요하지 않습니다. 이는 관리 대상 및 특정 관리되지 않는 리소스에도 적용됩니다. 백그라운드가 아닌 스레드를 활성 상태로 유지하는 리소스 만 폐기해야합니다. 그렇지 않으면 프로세스가 활성 상태로 유지됩니다.

이제 정상적인 실행 중에 관리되지 않는 메모리 누수를 피하기 위해 @fezmonkey가 데이터베이스 연결, 소켓, 창 핸들을 가리 키기 때문에 올바르게 정리해야하는 임시 리소스가 있습니다 . 이들은 폐기해야 할 종류입니다. 스레드를 소유하는 클래스를 작성하면 (그리고 소유주가 스레드를 작성 했으므로 적어도 코딩 스타일에 따라 중지되도록 할 책임이 있음을 의미 함) 해당 클래스는 아마도 IDisposable스레드를 구현 하고 해제 해야합니다 Dispose.

.NET 프레임 워크는 IDisposable이 클래스 폐기 해야 한다는 신호를 개발자에게 경고하기 위해 인터페이스로 사용합니다 . IDisposable처리가 선택적 인 프레임 워크의 유형 (명시 적 인터페이스 구현 제외)을 생각할 수 없습니다 .


Dispose 호출은 완벽하게 유효하고 합법적이며 권장됩니다. IDisposable을 구현하는 객체는 일반적으로 그 이유 때문에 그렇게합니다. GC 성능 지표가 영향을받는 유일한 시간은 GC.Collect () 호출시입니다.
Scott Dorman

많은 .net 클래스의 경우, 처분은 "어느 정도"선택 사항입니다. 즉, 인스턴스를 "일반적으로"포기하면 새 인스턴스를 만들어 포기하지 않는 한 문제가 발생하지 않습니다. 예를 들어, 컨트롤에 대한 컴파일러 생성 코드는 컨트롤이 인스턴스화 될 때 글꼴을 만들고 양식이 배치 될 때 글꼴을 버리는 것 같습니다. 수천 개의 컨트롤을 만들어서 폐기하면 수천 개의 GDI 핸들을 묶을 수 있지만 대부분의 경우 컨트롤이 만들어지고 파괴되지 않습니다. 그럼에도 불구하고 여전히 그러한 포기를 피하려고 노력해야한다.
supercat

1
글꼴의 경우 문제는 Microsoft가 컨트롤에 할당 된 "글꼴"개체를 처리 할 개체를 실제로 정의하지 않았기 때문입니다. 경우에 따라 컨트롤이 오래 지속되는 개체와 글꼴을 공유 할 수 있으므로 컨트롤을 배치하면 글꼴을 배치하는 것이 좋지 않습니다. 다른 경우에는 글꼴이 컨트롤과 다른 곳에 할당되므로 컨트롤이 처리하지 않으면 아무도 처리하지 않습니다. 또한, 컨트롤이 글꼴의 GDI 핸들을 사용하지 않는 것이므로 별도의 비 일회용 FontTemplate 클래스가 있으면 글꼴에 대한 이러한 어려움을 피할 수있었습니다.
supercat

선택적 Dispose()호출 주제에 대해서는 stackoverflow.com/questions/913228/…
RJ Cuthbertson

7

게시 한 예에서 여전히 "지금 메모리를 비우지"않습니다. 모든 메모리는 가비지 수집되지만 이전 세대 에서 메모리를 수집 할 수 있습니다 . 확실하게 테스트를 수행해야합니다.


프레임 워크 디자인 지침은 규칙이 아니라 지침입니다. 인터페이스의 기본 용도, 사용시기, 사용 방법 및 사용하지 않을시기를 알려줍니다.

IDisposable을 사용하여 실패했을 때 간단한 RollBack () 코드를 읽었습니다. 아래의 MiniTx 클래스는 Dispose ()에서 플래그를 확인하고 Commit호출이 발생하지 않으면 Rollback자체 호출 됩니다. 호출 코드를 훨씬 쉽게 이해하고 유지 관리 할 수 ​​있도록 간접 계층을 추가했습니다. 결과는 다음과 같습니다.

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

또한 타이밍 / 로깅 코드가 동일한 작업을 수행하는 것을 보았습니다. 이 경우 Dispose () 메서드는 타이머를 중지하고 블록이 종료되었음을 기록했습니다.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

관리되지 않는 리소스 정리를 수행하지 않지만 IDisposable을 사용하여보다 깨끗한 코드를 작성하는 구체적인 예는 다음과 같습니다.


고차 함수를 사용하여 @Daniel Earwicker의 예를 살펴보십시오. 벤치마킹, 타이밍, 로깅 등의 경우 훨씬 간단합니다.
Aluan Haddad


6

관리되지 않는 리소스 사용 또는 해제에 대한 일반적인 내용은 반복하지 않겠습니다. 그러나 나는 일반적인 오해가 무엇인지 지적하고 싶습니다.
다음 코드가 주어지면

퍼블릭 클래스
  IDisposable 구현
  개인 _Large as string ()

  '_Large를 의미하는 이상한 코드에는 이제 수백만 개의 긴 문자열이 포함됩니다.

  Public Sub Dispose ()는 IDisposable.Dispose를 구현합니다.
    _Large = 아무것도
  엔드 서브

Disposable 구현은 현재 지침을 따르지 않지만 모든 아이디어를 얻길 바랍니다.
이제 Dispose가 호출되면 사용 가능한 메모리 양이 얼마입니까?

답 : 없음. 위에 표시된 Dispose 메서드를 호출하기 위해 finaliser를 추가 할 필요는 없습니다. 종료자가 실행될 수 있도록 메모리 재생이 지연됩니다.
Dispose를 호출하면 관리되지 않는 리소스를 해제 할 수 있으며 관리되는 메모리를 회수 할 수 없으며 GC 만 가능합니다. 위의 내용이 좋은 생각이 아니라고 말할 수는 없지만 위의 패턴을 따르는 것이 실제로 좋은 생각입니다. Dispose가 실행되면 LargeStuff 인스턴스가 여전히 범위에 있더라도 GC가 _Large에서 사용중인 메모리를 회수하는 것을 막을 수있는 것은 없습니다. _Large의 문자열도 0 세대에있을 수 있지만 LargeStuff 인스턴스는 2 세대 일 수 있으므로 다시 메모리를 확보 할 수 있습니다.


1
의 인스턴스 경우 LargeStuff주변에 긴 2 세대로 만들기에 충분하고, 경우에있다가 _Large세대 0에있는 새로 만들어진 문자열에 대한 참조를 보유하고, 다음의 인스턴스 경우 LargeStuff밖으로 널링을하지 않고 포기는 _Large, 다음 문자열에 의해 참조 _Large다음 Gen2 컬렉션까지 유지됩니다. 제로 아웃 _Large하면 다음 Gen0 컬렉션에서 문자열이 제거 될 수 있습니다. 대부분의 경우 참조를 무효화하는 것은 도움이되지 않지만 이점을 제공 할 수있는 경우가 있습니다.
supercat

5

별도로 제어 할 수있는 방법으로는 주 사용으로 수명시스템 리소스를 (완전히의 멋진 대답이 적용 이안 , 명성을!)에 는 IDisposable / 사용 콤보 것은도에 사용할 수있는 범위 (중요) 글로벌 자원의 상태 변화 : 콘솔스레드처리 , 어떤 전역 객체 같은 응용 프로그램 인스턴스 .

이 패턴에 대한 기사를 작성했습니다 : http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

그것은 당신이 몇 가지 자주 사용 글로벌 상태를 보호 할 수있는 방법을 보여줍니다 재사용읽기 방법 : 콘솔 색상 , 현재 스레드 문화 , 엑셀 응용 프로그램 개체의 속성을 ...


4

어쨌든 코드를 떠날 때보 다 코드의 효율성이 떨어질 것으로 기대합니다 .

Clear () 메소드를 호출하는 것은 불필요하며 Dispose가 그렇게하지 않으면 GC는 아마도 그렇게하지 않을 것입니다 ...


2

Dispose()예제 코드 에서 개체 의 정상적인 GC로 인해 발생하지 않는 영향을 줄 있는 작업이 있습니다 MyCollection.

가 참조하는 객체 경우 _theList또는이 _theDict후, 다른 객체에 의해 참조되는 List<>또는 Dictionary<>개체가 컬렉션에 적용 대상이되지 않는다 갑자기 아무런 내용이 없습니다. 예제와 같이 Dispose () 작업이없는 경우 해당 컬렉션에는 여전히 해당 내용이 포함됩니다.

이 상황이라면 물론, 나는 그것을 깨진 설계를 부를 것이다 - 난 그냥 지적하고 있습니다 (pedantically, 내 생각)을하는 것이 Dispose()작업이 완전히 중복되지 않을 수도의 다른 용도가 있는지 여부에 따라 List<>또는 Dictionary<>그 아니다 조각에 표시됩니다.


그들은 개인 분야이기 때문에 OP가 그들에 대한 언급을하지 않는다고 가정하는 것이 공정하다고 생각합니다.
mqp

1) 코드 조각은 예제 코드 일 뿐이므로 간과하기 쉬운 부작용이있을 수 있습니다. 2) 개인 필드는 종종 getter 속성 / 메소드의 대상입니다. 어쩌면 너무 많을 수도 있습니다 (일부 사람들은 getter / setter를 반 패턴으로 간주합니다).
Michael Burr

2

"관리되지 않는 리소스"에 대한 대부분의 토론에서 한 가지 문제는 용어를 실제로 정의하지는 않지만 관리되지 않는 코드와 관련이있는 것 같습니다. 많은 유형의 관리되지 않는 리소스가 관리되지 않는 코드와 인터페이스하는 것은 사실이지만 이러한 용어로 관리되지 않는 리소스를 생각하는 것은 도움이되지 않습니다.

대신, 모든 관리 자원의 공통점을 인식해야합니다. 즉, 외부의 무언가를 대신하여 무언가를하도록 요구하는 다른 물체를 요구하고, 다른 어떤 것은 '사물'을 해치거나 추가 통지. 물체가 흔적없이 버려지고 사라진다면, 더 이상 존재하지 않는 물체를 대신하여 그 행동을 바꿀 필요가 없다는 것을 '무엇'외부에 말하지 않을 것입니다. 결과적으로 '일의 유용성이 영구적으로 줄어 듭니다.

관리되지 않는 리소스는 개체를 대신하여 동작을 변경하는 일부 외부 '사물'에 의한 동의를 나타내며, 이는 개체가 버리고 존재하지 않으면 외부 '사물'의 유용성을 손상시킬 수 있습니다. 관리 자원은 그러한 계약의 수혜자이지만, 포기 된 경우 통지를 수신하도록 등록한 오브젝트이며, 그러한 통지를 사용하여 파기 전에 순서를 정합니다.


관리되지 않는 개체에 대한 IMO의 정의는 분명합니다. 비 GC 개체 .
Eonil

1
@Eonil : 관리되지 않는 개체! = 관리되지 않는 리소스. 이벤트와 같은 것은 관리 객체를 사용하여 완전히 구현할 수 있지만 적어도 수명이 긴 객체의 이벤트를 구독하는 수명이 짧은 객체의 경우 GC는 이벤트를 정리하는 방법에 대해 아무것도 모르기 때문에 여전히 관리되지 않는 리소스를 구성합니다. .
supercat


2

정의의 첫 번째. 나를 위해 관리되지 않는 리소스는 IDisposable 인터페이스를 구현하는 클래스 또는 dll 호출을 사용하여 만든 클래스를 의미합니다. GC는 그러한 객체를 다루는 방법을 모른다. 클래스에 예를 들어 값 유형 만있는 경우이 클래스를 관리되지 않는 리소스가있는 클래스로 간주하지 않습니다. 내 코드의 경우 다음 관행을 따릅니다.

  1. 내가 만든 클래스에서 관리되지 않는 리소스를 사용하면 메모리를 정리하기 위해 IDisposable 인터페이스도 구현해야합니다.
  2. 사용을 마치 자마자 개체를 청소하십시오.
  3. 내 dispose 메서드에서 모든 IDisposable 클래스 멤버를 반복하고 Dispose를 호출합니다.
  4. 내 Dispose 메서드에서 GC.SuppressFinalize (this)를 호출하여 가비지 수집기에 내 개체가 이미 정리되었음을 알립니다. 나는 GC 호출이 비싼 작업이기 때문에 그렇게한다.
  5. 추가 예방 조치로 Dispose ()를 여러 번 호출하려고합니다.
  6. 때때로 개인 멤버 _disposed를 추가하고 메소드 호출에서 오브젝트가 정리되었습니다. 그리고 정리 된 경우 ObjectDisposedException 을 생성하십시오.
    다음 템플릿은 코드 샘플로 단어로 설명한 것을 보여줍니다.

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

1
"저에게 관리되지 않는 리소스 란 IDisposable 인터페이스를 구현하는 클래스 또는 dll 호출을 사용하여 만든 클래스를 의미합니다." 따라서 is IDisposable관리되지 않는 리소스로 간주되어야 하는 모든 유형이 있습니까? 맞지 않는 것 같습니다. 또한 함침 유형이 순수한 값 유형 인 경우 폐기 할 필요가없는 것으로 보입니다. 또한 잘못된 것 같습니다.
Aluan Haddad

모두 스스로 판단합니다. 나는 단지 코드를 추가하기 위해 코드를 추가하고 싶지 않습니다. IDisposable을 추가하면 GC가 관리 할 수없는 기능을 만들었거나 수명을 제대로 관리 할 수 ​​없다고 생각합니다.
Yuriy Zaletskyy

2

주어진 코드 샘플은 IDisposable사용 하기에 좋은 예가 아닙니다 . 일반적으로 사전 지우기 는 Dispose방법으로 이동하지 않아야합니다 . 범위를 벗어나면 사전 항목이 지워지고 처리됩니다. IDisposable범위를 벗어난 후에도 해제 / 해제되지 않는 일부 메모리 / 핸들러를 해제하려면 구현이 필요합니다.

다음 예제는 코드와 주석이있는 IDisposable 패턴에 대한 좋은 예를 보여줍니다.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

1

관리 자원을 폐기하는 데 가장 적합한 유스 케이스는 GC가 수집하지 않은 자원을 회수 할 준비를하는 것입니다.

대표적인 예는 순환 참조입니다.

순환 참조를 피하는 패턴을 사용하는 것이 가장 좋은 방법이지만, 예를 들어 '부모'에 대한 참조가있는 '자식'개체로 끝나는 경우 방금 포기하면 부모의 GC 수집이 중지 될 수 있습니다. 참조를 사용하고 GC에 의존합니다-플러스를 구현 한 경우 호출되지 않습니다.

이것의 유일한 방법은 자식에서 부모 참조를 null로 설정하여 순환 참조를 수동으로 나누는 것입니다.

부모와 자녀에게 IDisposable을 구현하는 것이 가장 좋은 방법입니다. 부모에서 Dispose가 호출되면 모든 자식에서 Dispose를 호출하고 자식 Dispose 메서드에서 부모 참조를 null로 설정합니다.


4
대부분의 경우 GC는 죽은 개체를 식별하는 것이 아니라 실제 개체를 식별하는 방식으로 작동합니다. 각 gc주기 후, 종료를 위해 등록 된 각 오브젝트에 대해 큰 오브젝트 힙에 저장되거나 live의 대상인 WeakReference경우 시스템은 마지막 GC주기에서 라이브 루팅 된 참조가 발견되었음을 나타내는 플래그를 검사합니다. 즉시 종료가 필요한 개체 대기열에 개체를 추가하거나 큰 개체 힙에서 개체를 해제하거나 약한 참조를 무효화합니다. 원형 참조는 다른 참조가없는 경우 객체를 활성 상태로 유지하지 않습니다.
supercat

1

관리되는 리소스와 관리되지 않는 리소스 모두에 IDisposable을 사용하는 방법에 대해 많은 답변이 바뀌 었습니다. 나는이 기사를 IDisposable이 실제로 어떻게 사용되어야하는지에 관해 찾은 최고의 설명 중 하나로 제안 할 것이다.

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

실제 질문에 대해; IDisposable을 사용하여 많은 메모리를 차지하는 관리 대상 개체를 정리하면 짧은 대답은 아니요 입니다. 그 이유는 IDisposable을 폐기 한 후에는 범위를 벗어나도록해야하기 때문입니다. 이 시점에서 참조 된 모든 하위 개체도 범위를 벗어나 수집됩니다.

이에 대한 유일한 예외는 관리 대상 개체에 많은 메모리가 묶여 있고 일부 작업이 완료되기를 기다리는 스레드를 차단 한 경우입니다. 해당 호출이 완료된 후 필요하지 않은 객체를 null로 설정하면 가비지 수집기가 더 빨리 수집 할 수 있습니다. 그러나이 시나리오는 IDisposable의 사용 사례가 아닌 리팩토링해야하는 잘못된 코드를 나타냅니다.


1
나는 왜 누군가가 당신의 대답에 -1을 넣었는지 이해하지 못했습니다
Sebastian Oscar Lopez
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.