【问题标题】:What is the resulting behavior when an IDisposable is passed into a parent IDisposable将 IDisposable 传递给父 IDisposable 时的结果行为是什么
【发布时间】:2017-11-26 16:02:48
【问题描述】:

昨天,在我们的代码库上运行 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()
  • 这是一段旧代码,从未报告过警告消息中描述的异常(据我所知)。如果假设以上几点,为什么双 using 块不会导致调用 stringReader.dispose() 两次并因此引发异常?

【问题讨论】:

  • 您应该期待的是,封闭类型的实现者已记录它与您交给它的一次性用品有关的行为。

标签: c# idisposable


【解决方案1】:
  • 不,您不能假设另一个对象会在处置自己时调用Dispose()。以一次性对象为参考的对象甚至可能没有使用一次性资源。
  • 已知此警告有些奇怪。查看here 以查看有关警告的一些投诉。您应该设计您的课程,以便多次调用Dispose() 必须是安全的。

顺便说一句,MSDN 说:

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

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

【讨论】:

    【解决方案2】:

    在对象 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);
        }
    }
    

    【讨论】:

      【解决方案3】:

      假设对象 X 将获得 Y 的所有权是否正确,从那时起,调用 X.dispose() 将始终导致调用 Y.dispose()

      不,假设这一点是永远不会保存的。让我们来看看这个具体案例:XmlReader.Create(Stream)

      在参考源中查看了相当多的代码后,我发现Dispose 方法调用了Close 方法。这是很明显的。然后注意this piece of code

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

      所以后端流是否会被关闭和释放取决于设置closeInput的值,你可以通过XmlReaderSettings.CloseInput设置来设置。

      所以这里的答案是肯定的:你不能确定它已经被处理掉了。您应该始终确保自己确实如此。

      【讨论】:

        猜你喜欢
        • 2015-09-24
        • 2019-03-26
        • 1970-01-01
        • 1970-01-01
        • 2020-11-09
        • 2012-07-04
        • 2012-01-19
        • 2016-02-13
        相关资源
        最近更新 更多