将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); } }