2010-01-25 4 views
11

가의 악명 높은는 IDisposable 인터페이스를 살펴 보자되는 방식을 설계되었습니다 : 현재 개체가 이미 설치되어있는 경우 MSDN에서 권장하는대로,이유는 IDisposable 구현이

[ComVisible(true)] 
public interface IDisposable 
{ 
    void Dispose(); 
} 

전형적인 구현 (I 체크를 생략) :

public class Base : IDisposable 
{ 
    protected virtual void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      // release managed 
     } 
     // release unmanaged 
     disposed = true; 
    } 

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

    ~Base() 
    { 
     Dispose(false); 
    } 
} 

public class Derived : Base 
{ 
    protected override void Dispose(bool disposing) 
    { 
     base.Dispose(disposing); 
     if (disposing) 
     { 
      // release managed 
     } 
     // release unmanaged 
     disposed = true; 
    } 
} 

문제점 :이 구현은 반 직관적이라고 생각합니다. 또한 기본 클래스와 파생 클래스도 크게 다릅니다. 파생 클래스는 으로 기본 클래스가 IDisposable을 올바르게 구현 한으로 가정하고 원래 인터페이스의 일부조차도 아닌 Dispose (bool)를 재정의합니다.

저는 보통 중학교 프로그래머에게 면접 시험에서 IDisposable을 구현하도록 요청했기 때문에이 질문을 생각해 냈습니다. 이 구현은 더 명확하고 더 일관성, 나에게

public class Base : IDisposable 
{ 
    public virtual void Dispose() 
    { 
     // release managed and unmanaged 
     GC.SuppressFinalize(this); 
    } 

    ~Base() 
    { 
     // release unmanaged 
    } 
} 

public class Derived : Base 
{ 
    public override void Dispose() 
    { 
     // release managed and unmanaged 
     base.Dispose(); 
    } 

    ~Derived() 
    { 
     // release unmanaged 
    } 
} 

: 그들은 정확히 완료해야하는데 방법을 모르는 경우, 그들은이 가까이에 뭔가 들고 오지. 물론 나쁜 점은 관리되지 않는 리소스를 두 곳에서 릴리스해야한다는 것입니다.하지만 중요한 점은 99 % 이상의 사용자 정의 클래스가 처리 할 관리되지 않는 항목이 없으므로 어쨌든 최종자를 필요로하지 않는다는 것입니다. 나는 주니어 프로그래머에게 왜 MSDN 구현이 더 나은지 설명 할 수 없다. 왜냐하면 내가 직접 이해하지 못하기 때문이다.

그래서 이상한 디자인 결정 (파생 클래스가 인터페이스의 메서드와 다른 메서드를 재정의하고 그를 포함하지 않는 관리되지 않는 리소스에 대해 생각하게 만드는)이 궁금합니다. 이 문제에 대한 어떤 생각?

답변

7

, 포함). 이 문제에 대한 어떤 생각?

주된 문제는 프레임 워크가 이미 설계되고 존재할 때 IDisposable이 정의되었다는 것입니다. 이는 관리되는 코드가 피하려고하는 상황을 처리하기위한 것입니다. 따라서 매우 일반적인 사례 인 경우 실제로는 최악의 경우입니다. ;)

이것은 C++/CLI를 보면 btw로 볼 수 있습니다. 그것은 IDisposable 후에 설계되었으며 결과적으로 IDisposable을 훨씬 자연스럽게 구현합니다. 소멸자 [~ClassName]는 자동으로 Dispose가되고 종료 자 [!ClassName]는 종료 자로 처리됩니다.

다른 문제는 IDisposable이 여러 상황을 처리한다는 것입니다.entire blog series을 작성하여 원시 코드를 래핑하고 IDisposable을 구현하는 클래스를 캡슐화하며 인수 분해 된 유형과 함께 사용할 때 사용해야하는 다양한 구현을 살펴 보았습니다.

기술적으로 에만 인터페이스를 직접 구현해야합니다. protected virtual void Dispose(bool disposing) 메서드를 허용하는 디자인 결정은 공용 인터페이스에서 쉽고 안전하게 처리되지 않는 유연성을 제공합니다.

0

내 생각에 IDisposable에 대한 전체 이유는 관리되지 않는 리소스를 해제하는 것이므로 "99 % 사용자 정의 클래스에는 처리 할 관리되지 않는 항목이 없습니다"라는 이유가 혼란 스럽습니다. IDisposable을 구현하는 경우 관리되지 않는 리소스가 있어야합니다.

MSDN IDisposable

+0

틀린. 리소스를 관리하는 경우 IDisposable을 구현하고 처리해야합니다. – SLaks

+0

항상 그렇지는 않습니다. 네이티브 리소스가있는 형식을 캡슐화하거나 인수 분해 된 형식을 구현할 수 있습니다. –

+0

@SLaks : 리소스가 완전히 관리되는 경우 덜 중요합니다 (완전히 관리되는 유형이 GC에 의해 올바르게 처리되므로 ...) –

1

MSDN 매거진은 article about this pattern있다.

이 질문에 대한 대답은 아니지만 다음 코드 조각을 사용하여 패턴을 구현할 수 있습니다. 같은 특이한 디자인 결정 (인터페이스에서보다 다른 방법을 재정의하는 파생 클래스를 만들고 그 그것은 아마도 '아무튼 관리되지 않는 리소스에 대해 생각하게되었다 내가 궁금하네요 그래서

<?xml version="1.0" encoding="utf-8" ?> 
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> 
    <CodeSnippet Format="1.0.0"> 
     <Header> 
      <Title>Dispose pattern</Title> 
      <Shortcut>dispose</Shortcut> 
      <Description>Code snippet for virtual dispose pattern</Description> 
      <Author>SLaks</Author> 
      <SnippetTypes> 
       <SnippetType>Expansion</SnippetType> 
       <SnippetType>SurroundsWith</SnippetType> 
      </SnippetTypes> 
     </Header> 
     <Snippet> 
      <Declarations> 
       <Literal Editable="false"> 
        <ID>classname</ID> 
        <ToolTip>Class name</ToolTip> 
        <Default>ClassNamePlaceholder</Default> 
        <Function>ClassName()</Function> 
       </Literal> 
      </Declarations> 
      <Code Language="csharp"> 
       <![CDATA[ 
     ///<summary>Releases unmanaged resources and performs other cleanup operations before the $classname$ is reclaimed by garbage collection.</summary> 
     ~$classname$() { Dispose(false); } 
     ///<summary>Releases all resources used by the $classname$.</summary> 
     public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } 
     ///<summary>Releases the unmanaged resources used by the $classname$ and optionally releases the managed resources.</summary> 
     ///<param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> 
     protected virtual void Dispose(bool disposing) { 
      if (disposing) { 
       $end$$selected$ 
      } 
     }]]> 
      </Code> 
     </Snippet> 
    </CodeSnippet> 
</CodeSnippets> 
12

저는 일반적으로 파생 클래스에 대한 추측을합니다. 여기 내 .snippet 파일이다 : 나는 당신의 구현을 참조

#region IDisposable pattern 
/// <summary> 
/// Dispose of (clean up and deallocate) resources used by this class. 
/// </summary> 
/// <param name="fromUser"> 
/// True if called directly or indirectly from user code. 
/// False if called from the finalizer (i.e. from the class' destructor). 
/// </param> 
/// <remarks> 
/// When called from user code, it is safe to clean up both managed and unmanaged objects. 
/// When called from the finalizer, it is only safe to dispose of unmanaged objects. 
/// This method should expect to be called multiple times without causing an exception. 
/// </remarks> 
protected virtual void Dispose(bool fromUser) 
    { 
    if (fromUser) // Called from user code rather than the garbage collector 
     { 
     // Dispose of managed resources (only safe if called directly or indirectly from user code). 
     try 
      { 
     DisposeManagedResources(); 
      GC.SuppressFinalize(this); // No need for the Finalizer to do all this again. 
      } 
     catch (Exception ex) 
      { 
      //ToDo: Handle any exceptions, for example produce diagnostic trace output. 
      //Diagnostics.TraceError("Error when disposing."); 
      //Diagnostics.TraceError(ex); 
      } 
     finally 
      { 
      //ToDo: Call the base class' Dispose() method if one exists. 
      //base.Dispose(); 
      } 
     } 
    DisposeUnmanagedResources(); 
    } 
/// <summary> 
/// Called when it is time to dispose of all managed resources 
/// </summary> 
    protected virtual void DisposeManagedResources(){} 
/// <summary> 
/// Called when it is time to dispose of all unmanaged resources 
/// </summary> 
    protected virtual void DisposeUnmanagedResources(){} 
/// <summary> 
/// Dispose of all resources (both managed and unmanaged) used by this class. 
/// </summary> 
public void Dispose() 
    { 
    // Call our private Dispose method, indicating that the call originated from user code. 
    // Diagnostics.TraceInfo("Disposed by user code."); 
    this.Dispose(true); 
    } 
/// <summary> 
/// Destructor, called by the finalizer during garbage collection. 
/// There is no guarantee that this method will be called. For example, if <see cref="Dispose"/> has already 
/// been called in user code for this object, then finalization may have been suppressed. 
/// </summary> 
~$MyName$() 
    { 
    // Call our private Dispose method, indicating that the call originated from the finalizer. 
    // Diagnostics.TraceInfo("Finalizer is disposing $MyName$ instance"); 
    this.Dispose(false); 
    } 
#endregion 
+1

+1 내가 일반적으로하는 방식입니다. – SwDevMan81

+0

GC.SuppressFinalize (this)를 호출하지 않았습니다. –

+0

@Sergej 네 말이 맞아. 이것은 전체적인 것의 일부일뿐입니다. 내 대답을 업데이트했습니다. 내 .snippet 파일의 현재 내용 (개인 접촉으로 다른 코드가 합쳐진 것입니다) – Will

0

하나의 문제는 파생 클래스는 기본 클래스의 Dispose 메서드를 호출하지 않는 potention 있다는 것입니다. 이 경우 GC.SuppressFinalize가 필요할 때 호출되지 않을 수 있으며 결국에는 Finalizer를 호출하게됩니다. 나는 GC.SuppressFinalize가 호출되도록 Will의 솔루션을 좋아한다. MSDN에서 권장하는 방법은 비슷한 느낌을 가지며 개체가 개발자에 의해 처분되는 경우 GC.SuppressFinalize가 호출되도록합니다.

2

이 책 및 기타 대부분의 API 디자인 질문에 대한 답변은이 책에서 찾을 수 있습니다.

프레임 워크 디자인 지침 : 규칙, 숙어, 및 재사용 .NET 라이브러리 http://www.amazon.com/gp/product/0321545613?ie=UTF8&tag=bradabramsblo-20&link_code=wql&camp=212361&creative=380601

에 대한 패턴이 그대로 Microsoft 직원이 .NET API를 구축하는 데 사용할 규칙의 집합입니다. 규칙은 무료이지만 (아래 참조) 책에는 규칙을 설명하는 설명이 있습니다. .NET 개발자에게는 꼭 필요한 기능입니다.

http://msdn.microsoft.com/en-us/library/b1yfkh5e.aspx

+0

효과적인 C# 책에서도 언급 됨 –

2

나는이 better이다라고 말하고 싶지만 :

public class DisposableClass : IDisposable { 
    void IDisposable.Dispose() { 
    CleanUpManagedResources(); 
    CleanUpNativeResources(); 
    GC.SuppressFinalize(this); 
    } 

    protected virtual void CleanUpManagedResources() { 
    // ... 
    } 
    protected virtual void CleanUpNativeResources() { 
    // ... 
    } 

    ~DisposableClass() { 
    CleanUpNativeResources(); 
    } 
} 
0

추천으로 IDisposable 패턴의 유용한 기능은, 독립적에게는 IDisposable을 구현하는 유형을 확장 파생 된 형식에 대한 일관된 패턴을 수 있다는 것입니다 기본 형식이 Dispose이라는 공개 매개 변수없는 메서드를 노출하는지 여부 보호 된 메소드에 매개 변수없는 Dispose()와 다른 서명을주는 데 사용되는 더미로 매개 변수를 간주하는 경우 실제로 패턴이 그렇게 나쁘지는 않습니다. 가장 큰 약점은 여분의 Dispose에 대한 보호가 스레드 안전 방식으로 수행되지 않는다는 것입니다.

권장 IDisposable 패턴의 유용하지 않은 기능은 적합하지 않은 경우 대부분에서 종료 자/소멸자의 사용을 권장한다는 것입니다. 아주 드물게 System.Object 이외의 클래스에서 파생 된 클래스는 정리를위한 finalizer를가집니다 (클래스는 제대로 처리하지 못하는 오류를 기록하기 위해 finalizer를 가질 수 있습니다). 클래스가 여러 관리 객체에 대한 참조를 보유하고 관리되지 않는 리소스를 보유하는 경우 관리되지 않는 리소스를 자체 래퍼 클래스로 이동하여 관리되는 리소스로 바꿔야합니다. 래퍼 클래스는 SafeHandle과 같은 것으로부터 파생되거나, Object에서 파생되고 finalizer/destructor를 정의 할 수 있습니다. 두 가지 방법 중 하나를 사용하면 주 수업에서 최종 자/소멸자가 필요하지 않습니다.

관련 문제