当了IDisposable被传递到父IDisposable的

问题描述:

昨天有什么结果的行为,在我们的代码库运行Visual Studio代码分析后,下面的代码被强调为一个问题:是返回当了IDisposable被传递到父IDisposable的

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()
  • 这是一个古老的代码,并在警告消息中描述的异常从未被报道(据我所知)。如果假设上述观点,为什么双重using块不会导致两次调用stringReader.dispose()并因此抛出异常?
+0

你应该期望的是,封闭类型的实现者已经*记录了它对你处理的Disposables的行为。 –

是正确的假设,对象X将Y和从该点起的所有权,要求X.dispose()总是会导致调用Y.dispose()

不,这绝不是保存。我们来看看这个特定的案例:XmlReader.Create(Stream)

去参考源中的相当一些代码后,我发现Dispose方法调用Close方法。这很明显。然后注意到this piece of code

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

所以无论后盾流将被关闭,并设置取决于设定closeInput的价值,你可以通过XmlReaderSettings.CloseInput设置中设置。

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

在创建过程中,对象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); 
    } 
} 

  • 不,你不能假定另一个对象在处置自己时会调用Dispose()。具有一次性对象作为参考的对象可能甚至不使用一次性资源。
  • 此警告已知有点奇怪。看看here看到一些关于警告的投诉。你应该设计你的班级,以便它不止一次致电Dispose()

顺便提一下,MSDN表示:

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

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