您如何看待以下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)
答案 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等)也有助于这样做当一个对象在使用过程中获取资源时,你只想处置它所拥有的资源。