2

您如何看待以下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 模式)

4

7 回答 7

11

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

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

IDisposable当您持有实施的引用时,您的班级有责任实施IDisposable。然后,在您的实现中Dispose,如果您没有被 GCed(这是对 的显式调用Dispose),您将调用您所持Dispose有的任何IDisposable实现。

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

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

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

我应该指出,我并不完全同意这些指导方针,但它们是最被采用的。对于我的立场,请参见此处:

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

于 2009-04-01T17:10:24.740 回答
3

这个实现是有缺陷的,有几个原因。

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

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

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

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

最后,应该在处置后检查连接的使用。处置后使用的连接上的任何方法都应引发 ObjectDisposedException。

于 2009-04-01T18:17:29.337 回答
3

这似乎没有在其他答案中明确说明,所以如果您对具体出了什么问题感兴趣,就在这里。

当两个对象具有终结器并被 GC 回收时,它们的终结器执行的顺序没有特定的顺序。在您的代码中,Socket 类有一个终结器,而您的类有一个终结器。如果终结器首先在 Socket 实例上执行,那么当终结器执行时,您将尝试调用已终结对象上的方法,因此会出现异常。

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

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

于 2009-04-01T18:25:22.953 回答
2

首先,你为什么不实施IDisposable

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

于 2009-04-01T17:10:43.897 回答
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
于 2009-04-01T17:29:57.157 回答
1

我只想将此添加到理查森指出的模式中

private bool disposed;

private void Dispose(bool disposing)
{
    if(!this.disposed)
    {
        ReleaseUnmanagedResources();
        if (disposing)
            ReleaseManagedResources();
        this.disposed = true;
    }
}
于 2009-04-01T17:58:31.973 回答
1

终结器很痛苦,如果可能的话,你应该避免调用它们。但是,如果您实现了 IDisposable,您几乎总是希望编写一个(结合 Dispose(bool) 方法)。这调用了 Dispose(false) 和 GC.SuppressFinalize()。

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

假设你的消费者和你消费的对象都会玩得很好,这并不是你自己不玩得好的理由。

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

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

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

于 2009-04-02T00:33:19.980 回答