将IDisposable传递到父IDisposable时的结果是什么?

昨天,在我们的代码库上运行Visual Studio代码分析后,以下代码突出显示为一个问题:

using (var stringReader = new StringReader(someString)) { using (var reader = XmlReader.Create(stringReader)) { // Code } } 

返回的警告是

警告CA2202对象’stringReader’可以在方法’(方法名称)’中多次处理。 为避免生成System.ObjectDisposedException,不应在对象上多次调用Dispose。

在搜索堆栈溢出之后,我得出一般的理解,如果我要创建一个包含IDisposable成员的自定义类,它应该实现IDisposable本身,并调用该成员的dispose()方法。

我的两个问题是

  • 在对象X在创建过程中引用IDisposable对象Y作为参数的所有情况下,假设对象X将获得Y的所有权并且从该点开始是正确的,调用X.dispose()始终导致调用Y.dispose()
  • 这是一段旧代码,并且从未报告过警告消息中描述的exception(据我所知)。 如果假设上述点,为什么double using block不会导致调用stringReader.dispose()两次因此抛出exception?

假设对象X将取Y的所有权并且从那时开始,调用X.dispose()将始终导致调用Y.dispose()是正确的

不,假设它永远不会保存。 让我们检查一下这个特例: XmlReader.Create(Stream)

在参考源中找到相当多的代码后,我发现Dispose方法调用Close方法。 这很明显。 然后注意这段代码 :

 public override void Close() { Close( closeInput ); } 

因此,后备流是否将被关闭和处置取决于设置closeInput的值,您可以通过XmlReaderSettings.CloseInput设置来设置该值。

所以这里的答案是肯定的:你不能确定它是被处置的。 你应该总是确保自己。

  • 不,你不能假设另一个对象在处理自己时会调用Dispose() 。 具有一次性对象作为参考的对象甚至可能不使用一次性资源。
  • 众所周知,这种警告有些奇怪。 看这里看一些有关警告的投诉。 您应该设计您的类,以便多次调用Dispose()必须是安全的。

顺便说一句, MSDN说:

方法实现包含可能导致在同一对象上多次调用IDisposable.Dispose或Dispose等效项(如某些类型的Close()方法)的代码路径。

因此, Close()方法调用的路径也可以生成此警告,这就是您在案例中看到此警告的原因。

在对象X在创建过程中引用IDisposable对象Y作为参数的所有情况下,假设对象X将获得Y的所有权并且从该点开始是正确的,调用X.dispose()将始终导致调用Y.dispose()

我想不是,我会试着解释原因。

有一种名为IDisposablePattern的模式,它看起来像这样:

 public class SimpleClass : IDisposable { // managed resources SqlConnection implements IDisposable as well. private SqlConnection _connection; private bool _disposed; // implementing IDisposable public void Dispose() { // Here in original Dispose method we call protected method with parameter true, // saying that this object is being disposed. this.Dispose(true); // Then we "tell" garbage collector to suppress finalizer for this object because we are releasing // its memory and doesnt need to be finalized. Calling finalizer(destructor) of a given type is expensive // and tweaks like this help us improve performance of the application. GC.SuppressFinalize(this); } // Following the best practices we should create another method in the class // with parameter saying whether or not the object is being disposed. // Its really important that this method DOES NOT throw exceptions thus allowing to be called multiple times protected virtual void Dispose(bool disposing) { // another thing we may add is flag that tells us if object is disposed already // and use it here if (_disposed) { return; } if (_connection != null) { _connection.Dispose(); _connection = null; } _disposed = true; // call base Dispose(flag) method if we are using hierarchy. } } 

请注意,当您的类使用非托管资源时,可以将其扩展到新级别:

  public class SimpleClass2: IDisposable { // managed resources private SqlConnection _connection; private bool _disposed; // unmanaged resources private IntPtr _unmanagedResources; // simple method for the demo public string GetDate() { // One good practice that .NET Framework implies is that when object is being disposed // trying to work with its resources should throw ObjectDisposedException so.. if(_disposed) { throw new ObjectDisposedException(this.GetType().Name);} if (_connection == null) { _connection = new SqlConnection("Server=.\\SQLEXPRESS;Database=master;Integrated Security=SSPI;App=IDisposablePattern"); _connection.Open(); } // allocation of unmanaged resources for the sake of demo. if (_unmanagedResources == IntPtr.Zero) { _unmanagedResources = Marshal.AllocHGlobal(100 * 1024 * 1024); } using (var command = _connection.CreateCommand()) { command.CommandText = "SELECT getdate()"; return command.ExecuteScalar().ToString(); } } public void Dispose() { // Here in original Dispose method we call protected method with parameter true, // saying that this object is being disposed. this.Dispose(true); // Then we "tell" garbage collector to suppress finalizer for this object because we are releasing // its memory and doesnt need to be finalized. Calling finalizer(destructor) of a given type is expensive // and tweaks like this help us improve performance of the application. // This is only when your class doesnt have unmanaged resources!!! // Since this is just made to be a demo I will leave it there, but this contradicts with our defined finalizer. GC.SuppressFinalize(this); } // Following the best practices we should create another method in the class // with parameter saying wether or not the object is being disposed. // Its really important that this method DOES NOT throw exceptions thus allowing to be called multiple times protected virtual void Dispose(bool disposing) { // another thing we may add is flag that tells us if object is disposed already // and use it here if (_disposed) { return; } // Thus Dispose method CAN NOT release UNMANAGED resources such as IntPtr structure, // flag is also helping us know whether we are disposing managed or unmanaged resources if (disposing) { if (_connection != null) { _connection.Dispose(); _connection = null; } _disposed = true; } // Why do we need to do that? // If consumer of this class forgets to call its Dispose method ( simply by not using the object in "using" statement // Nevertheless garbage collector will fire eventually and it will invoke Dispose method whats the problem with that is if we didn't // have the following code unmanaged resources wouldnt be disposed , because as we know GC cant release unmanaged code. // So thats why we need destructor(finalizer). if (_unmanagedResources != IntPtr.Zero) { Marshal.FreeHGlobal(_unmanagedResources); _unmanagedResources = IntPtr.Zero;; } // call base Dispose(flag) method if we are using hierarchy. } ~DatabaseStateImpr() { // At this point GC called our finalizer method , meaning // that we don't know what state our managed resources are (collected or not) because // our consumer may not used our object properly(not in using statement) so thats why // we skip unmanaged resources as they may have been finalized themselves and we cant guarantee that we can // access them - Remember? No exceptions in Dispose methods. Dispose(false); } }