将 IDisposable 传递给父 IDisposable 时产生的行为是什么
What is the resulting behavior when an IDisposable is passed into a parent IDisposable
昨天,在对我们的代码库进行 运行 Visual Studio 代码分析后,以下代码被突出显示为一个问题:
using (var stringReader = new StringReader(someString))
{
using (var reader = XmlReader.Create(stringReader)) {
// Code
}
}
返回的警告是
Warning CA2202 Object 'stringReader' can be disposed more than once in
method '(method name)'. To avoid generating a
System.ObjectDisposedException you should not call Dispose more than
one time on an object.
搜索堆栈溢出后,我得到了一个普遍的理解,如果我要创建一个包含 IDisposable 成员的自定义 class,它应该实现 IDisposable 本身,并调用 dispose()
方法成员的。
我的两个问题是
- 在对象 X 在创建过程中将对
IDisposable
对象 Y 的引用作为参数的所有情况下,假设对象 X 将取得 Y 的所有权并从那时起调用X.dispose()
将 总是 导致调用 Y.dispose()
- 这是一段旧代码,警告消息中描述的异常从未被报告过(据我所知)。如果假设以上几点,为什么双
using
块不会导致调用 stringReader.dispose()
两次并因此抛出异常?
is it correct to assume that the object X will take ownership of Y and from that point onwards, calling X.dispose() will always result in calling Y.dispose()
不,永远不能这样假设。让我们检查这个具体案例:XmlReader.Create(Stream)
.
在查阅参考源中的一些代码后,我发现 Dispose
方法调用了 Close
方法。这是很明显的。然后注意this piece of code:
public override void Close() {
Close( closeInput );
}
所以backing stream是否会被关闭和释放取决于设置closeInput
的值,你可以通过XmlReaderSettings.CloseInput
设置
所以这里的答案肯定是否定的:您不能确定它已被处置。你应该始终确保自己是。
In all cases where object X takes a reference to an IDisposable object Y as a parameter during creation, is it correct to assume that the object X will take ownership of Y and from that point onwards, calling X.dispose() will always result in calling 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.
}
}
请注意,当您的 class 使用像这样的非托管资源时,这可以扩展到新的水平:
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 以查看有关警告的一些投诉。您应该设计 class 以便多次调用
Dispose()
必须是安全的。
顺便说一下,MSDN 说:
A method implementation contains code paths that could cause multiple calls to IDisposable.Dispose or a Dispose equivalent, such as a Close() method on some types, on the same object.
因此 Close()
方法调用的路径也可以生成此警告,这就是您在您的案例中看到此警告的原因。
昨天,在对我们的代码库进行 运行 Visual Studio 代码分析后,以下代码被突出显示为一个问题:
using (var stringReader = new StringReader(someString))
{
using (var reader = XmlReader.Create(stringReader)) {
// Code
}
}
返回的警告是
Warning CA2202 Object 'stringReader' can be disposed more than once in method '(method name)'. To avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object.
搜索堆栈溢出后,我得到了一个普遍的理解,如果我要创建一个包含 IDisposable 成员的自定义 class,它应该实现 IDisposable 本身,并调用 dispose()
方法成员的。
我的两个问题是
- 在对象 X 在创建过程中将对
IDisposable
对象 Y 的引用作为参数的所有情况下,假设对象 X 将取得 Y 的所有权并从那时起调用X.dispose()
将 总是 导致调用Y.dispose()
- 这是一段旧代码,警告消息中描述的异常从未被报告过(据我所知)。如果假设以上几点,为什么双
using
块不会导致调用stringReader.dispose()
两次并因此抛出异常?
is it correct to assume that the object X will take ownership of Y and from that point onwards, calling X.dispose() will always result in calling Y.dispose()
不,永远不能这样假设。让我们检查这个具体案例:XmlReader.Create(Stream)
.
在查阅参考源中的一些代码后,我发现 Dispose
方法调用了 Close
方法。这是很明显的。然后注意this piece of code:
public override void Close() {
Close( closeInput );
}
所以backing stream是否会被关闭和释放取决于设置closeInput
的值,你可以通过XmlReaderSettings.CloseInput
设置
所以这里的答案肯定是否定的:您不能确定它已被处置。你应该始终确保自己是。
In all cases where object X takes a reference to an IDisposable object Y as a parameter during creation, is it correct to assume that the object X will take ownership of Y and from that point onwards, calling X.dispose() will always result in calling 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.
}
}
请注意,当您的 class 使用像这样的非托管资源时,这可以扩展到新的水平:
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 以查看有关警告的一些投诉。您应该设计 class 以便多次调用
Dispose()
必须是安全的。
顺便说一下,MSDN 说:
A method implementation contains code paths that could cause multiple calls to IDisposable.Dispose or a Dispose equivalent, such as a Close() method on some types, on the same object.
因此 Close()
方法调用的路径也可以生成此警告,这就是您在您的案例中看到此警告的原因。