您如何看待我的IDisposable模式实现?

时间:2009-04-01 17:06:34

标签: c# .net design-patterns idisposable

您如何看待以下IDisposable模式实现?

public class Connection : IDisposable 
{
    private Socket _socket;

    public bool IsConnected()
    {
        if (_socket.Poll(1, SelectMode.SelectRead) && _socket.Available == 0)
            return false;
        return true;
    }

    public void Disconnect()
    {
        if (m_socket != null && IsConnected())
        {
            try
            {
                _socket.Shutdown(SocketShutdown.Both);
                _socket.Disconnect(false);
            }
            catch (SocketException se)
            {
                System.Console.WriteLine(se.Message);
            }
        }
    }

    ~Connection()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!IsConnected())
        {
            if (disposing)
            {
                Disconnect();
            }
            else
            {
                AppDomain currentDomain = AppDomain.CurrentDomain;
                if (currentDomain.IsFinalizingForUnload() && !Environment.HasShutdownStarted)
                {
                     System.Console.WriteLine("Client failed to call Destroy");
                }
            }
        }
    }
}

使用上面的代码我得到了这个错误:

  

{“尝试对非套接字的操作进行操作”}   System.Net.Sockets.Socket.Poll(Int32 microSeconds,SelectMode mode)

7 个答案:

答案 0 :(得分:11)

实施存在严重缺陷。你没有真正实现IDisposable,你最终依靠垃圾收集器来清理你的资源,这是一件坏事。

此外,当GC确实出现时,你甚至都没有正确地清理这些资源(它确实正确地执行了,但错误地发生了它)。

当您持有实施IDisposable的参考时,您的班级有责任实施IDisposable。然后,在Dispose的实施中,如果您没有进行GC操作(这是对Dispose的明确调用),那么您应该在任何Dispose实施中调用IDisposable坚持下去。

您检查Socket的连接状态,但这与在其上调用Dispose不同,并且您因此泄漏了资源(GC最终将其取出)。

有关如何正确实现IDisposable的指南,请参阅位于此处的标题为“实现最终化和处理以清理非托管资源”的MSDN文档部分:

http://msdn.microsoft.com/en-us/library/b1yfkh5e(VS.71).aspx

我应该注意到,我并不完全同意这些指导原则,但它们是最常用的。对于我的立场,请看这里:

http://www.caspershouse.com/post/A-Better-Implementation-Pattern-for-IDisposable.aspx

答案 1 :(得分:3)

由于一些原因,这种实施存在缺陷。

首先,您的Dispose()方法应该只有一个目的 - 调用socket.Dispose();。现在,你在那里放了太多的逻辑,而不是实际上“处置”你拥有的单一托管的IDisposable资源。

其次,您根本不需要终结器,因为您不直接拥有或分配任何本机非托管资源。您要处理的唯一资源是Socket,它是受管理的,并将根据需要实现自己的终结器。如果你想陷阱并找到没有正确处理Connection的情况,我会设置一个仅调试的终结器来警告这种情况。 IDisposable中的终结器用于处理GC必须进行清理的情况,因为调用者忘记调用Dispose() - 在您的情况下,套接字的终结器将为您处理。

第三,Microsoft设计指南中建议的IDisposable模式的一部分表明客户端应该能够多次调用Dispose()而不会产生任何后果。在第一次调用Dispose()之后,应该没有任何东西直接使用套接字 - 事实上,我建议Dispose()应该调用socket.Close();(socket as IDisposable).Dispose();并立即设置socket = null;以防止这是一种可能性。使用当前逻辑,很可能让IsConnected()中的调用导致套接字在后续调用Dispose()时抛出异常,这应该避免。

第四,强烈建议在使用文件,套接字或其他“可关闭”资源的所有资源上使用Close()方法。 Close()应该调用Dispose()。

最后,应该在处理后使用Connection进行检查。处理后使用的连接上的任何方法都应抛出ObjectDisposedException。

答案 2 :(得分:3)

这似乎没有在其他答案中明确说明,所以如果你对具体出错的地方感兴趣,请点击这里。

当两个对象具有终结器并由GC回收时,没有特定顺序执行终结器。在你的代码中,Socket类有一个终结器,你的类有一个终结器。如果终结器首先在Socket实例上执行,那么当你的终结器执行时,你将尝试在已经完成的对象上调用方法,因此是异常。

终结者基本上很糟糕。你几乎不需要写一个(即使你正在处理原始的Win32句柄 - 改为使用SafeHandle。)

相反,只需实现IDisposable,不要编写终结器。

答案 3 :(得分:2)

对于初学者,为什么不实施IDisposable

这将向您的对象的用户明确指示他们在完成后实际需要处理它。然后他们也可以将它包装在using块等中。

答案 4 :(得分:1)

这是我过去用过的一次性模式(我建议从这开始):

public class Connection : IDisposable
{
    #region Dispose pattern

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    /// <filterpriority>2</filterpriority>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        ReleaseUnmanagedResources();
        if (disposing)
            ReleaseManagedResources();
    }

    /// <summary>
    /// Derived classes from this class should override this method
    /// to clean up managed resources when Dispose is called.
    /// </summary>
    protected virtual void ReleaseManagedResources()
    {
        // Enter managed resource cleanup code here.
    }

    /// <summary>
    /// Derived classes should override this method to clean up
    /// unmanaged resources when Dispose is called.
    /// </summary>
    protected virtual void ReleaseUnmanagedResources()
    {
        // Enter unmanaged resource cleanup code here.
    }

    #endregion

答案 5 :(得分:1)

我只想将此添加到Richardson指出的模式

private bool disposed;

private void Dispose(bool disposing)
{
    if(!this.disposed)
    {
        ReleaseUnmanagedResources();
        if (disposing)
            ReleaseManagedResources();
        this.disposed = true;
    }
}

答案 6 :(得分:1)

终结器是一种痛苦,如果可能的话,你应该避免将它们称为 然而如果你实现了IDisposable,你几乎总是想写一个(与Dispose(bool)方法一起使用)。它调用Dispose(false)和GC.SuppressFinalize()。

如果您没有实现终结器,那么除非您持有的任何IDisposable实例正确实现终结器,否则您将泄漏。比如说SafeHandle没有终结器,但是依赖有人调用Dispose?如果您持有一个,并且没有实现终结器,那么您将允许您的消费者通过不调用Dispose来永久地泄漏手柄!

假设您消费的消费者和物品都玩得不好并不是不能自己玩的好理由。

此外,即使您使用SafeHandle,也应该像处理对象一样处理它。否则,如果你根本不处理它,那么它将不得不等到GC被释放,如果你在Dispose中处理它但没有终结器,没有人处理你,那么它将不得不等待直到那个收集你的人之后的GC。

上面的b_richardson有正确的模式,并不难,并且通过添加GC.SuppressFinalize()可以避免双通GC,除非没有正确处理你。

至于添加一个布尔值来跟踪我们是否已经被Disposed还是可以工作,我更喜欢将我的Dispose方法编写为可重新运行(检查null等)也有助于这样做当一个对象在使用过程中获取资源时,你只想处置它所拥有的资源。