2010-01-25 21 views
11

让我们来看看臭名昭著的IDisposable接口:为什么IDisposable的实现设计方式是

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

和典型实现的建议,MSDN(我省略了检查,如果当前对象已被释放):

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

所以我想知道,是什么导致了这种不寻常的设计决定(使派生类重写比在接口不同的方法,让他想想它最有可能没有按”非托管资源t包含)。有关这件事的任何想法?

主要问题是IDisposable是在框架已经设计并存在之后定义的。它意味着处理托管代码试图避免的情况 - 所以它确实是一种边缘情况,如果是非常常见的情况。 ;)

这可以看出,顺便说一句,如果你看看C++/CLI。它是在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 2010-01-25 19:04:46

+0

并非总是如此。您可能正在使用本地资源封装类型,或者实施了分解类型。 – 2010-01-25 19:04:58

+0

@SLaks:如果资源是完全管理的,那么重要性就不那么重要了(因为完全管理的类型将由GC正确处理...) – 2010-01-25 19:05:29

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 2010-01-25 19:25:54

+0

您没有调用GC.SuppressFinalize(this); – 2010-09-08 07:54:04

+0

@Sergej你是对的;这只是整个事情的一部分。我用我的.snippet文件的当前内容更新了我的答案,该文件是与个人触摸不同的片段的合并。 – Will 2010-09-08 14:25:08

0

一个问题,我与你实现看到的是,有potention派生类不调用基类Dispose方法。在这种情况下,GC.SuppressFinalize可能不会被调用,并且您最终还会调用Finalizer。我喜欢Will的解决方案以确保调用GC.SuppressFinalize。 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

模式这是字面上的一套规则微软员工使用构建.NET的API。规则是免费的(见下文),但该书有解释规则的评论。这对于.NET开发人员来说确实是必须的。

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

+0

也在有效的C#书中提到 – 2010-01-26 05:44:35

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以外的任何类派生的类都有清理终结器(类可能有终结器,用于记录未能正确处置的错误)。如果一个类持有对许多托管对象的引用,并且还拥有一个非托管资源,则应该将非托管资源移出到它自己的包装类中,将其变为托管资源。该包装类可以从类似SafeHandle的类派生,也可以从Object派生并定义一个终结器/析构函数;无论采取哪种行动方案,都无需在主课堂中使用终结器/析构器。

相关问题