检测“泄漏”的IDisposable对象。

14

有很多问题在SO上问如何检测IDisposable对象泄漏。答案似乎是"你不能"

我刚刚用最简单的测试用例进行了检查,FxCop 10.0没有做到这一点,ReSharper 4与MSVS2010也没有做到这一点。

对我来说,这似乎是错误的,比C中的内存泄漏更糟糕(至少我们已经建立了检测工具)。

我在想:是否可能使用反射和其他模糊的高级技术,在终结器中注入一个检查,以查看是否已调用Dispose

WinDBG+SOS的魔术技巧怎么样?

即使没有现有的工具可以做到这一点,我也想知道这在理论上是否可能(我的C#不是很好)。

有什么想法吗?

注意:这个问题的标题可能会误导。真正的问题应该是一个IDisposable对象是否已经正确地Disposed()。被GC处理掉不算,因为我认为那是一个错误。

编辑:解决方案:.NET Memory Profiler可以完成这项工作。我们只需要在程序末尾垃圾回收几次GC.Collect(),以使我们的分析工具能够正确地获取统计数据。


C#中可能没有工具的原因在于,C#中的资源基本上与对象生命周期不再耦合。 在C#和C ++中可以追踪对象生存期以及对象是否已被正确处理。 但是,C#中的可处置资源在任何情况下都不与对象生命周期绑定,这使得跟踪它们变得更加困难。 为了比较,请尝试在C ++中跟踪未通过RAII与对象生命周期绑定的泄漏的GDI资源。 这也不容易。 - Konrad Rudolph
我有点在思考这个问题。我养成了一个习惯,在编写代码时快速检查类型,看它们是否继承自IDisposable。如果是的话,我会在它们需要存活的范围内用 using 包装它们。这对现有代码没有任何作用,但我只是想提一下。 - Nick Strupat
1
请查看此帖子,您可以使用Visual Studio代码分析在编译时检测iDisposable问题: https://dev59.com/dVfUa4cB1Zd3GeqPLtWL#6213977 - John Dyer
3个回答

15

这是否检测到Dispose还是只检测内存?如果我的IDisposable对象只有Console.WriteLine("baz");(我知道这个例子很差,但你明白我的意思),我想确保它不是由GC调用,怎么办? - kizzx2
@Uwe Keim:RedGate的ANTS Memory Profiler是否跟踪问题实际上所问的内容?(这是列表中唯一需要发送电子邮件进行评估的问题 - 现在太懒了) - kizzx2
3
.NET Memory Profiler具有"Dispose Tracker"功能,正好符合我想要的需求。遗憾的是,目前这个答案对任何人都不是真正有用的,因为它只是列出了谷歌搜索结果(并非无用,因为它迫使我尝试每一个结果以证明至少有一个相关)。你能否编辑一下,至少包括.NET Memory Profiler的"Dispose Tracker"功能,以便信息更加详实? - kizzx2
@kizzx2 - 我会进行编辑,但就价值而言,.NET内存分析器是在搜索结果中唯一找到的。其余的来自实际使用。 - Justin Niessner
@Justin Neissner:以CLRProfiler为例,它似乎很简单,但我无法弄清楚它如何用于跟踪未处理(不一定是泄漏内存的)对象? - kizzx2
显示剩余2条评论

3

您可以通过为IDisposable对象添加Finalizer来实现。在Finalizer中,您可以检查对象是否已被处理。如果未被处理,您可以断言它,或将其写入日志,或执行其他操作。

 ~Disposable()
 {
#if DEBUG
            // In debug-builds, make sure that a warning is displayed when the Disposable object hasn't been
            // disposed by the programmer.

            if( _disposed == false )
            {
                System.Diagnostics.Debug.Fail ("There is a disposable object which hasn't been disposed before the finalizer call: {0}".FormatString (this.GetType ().Name));
            }
#endif
            Dispose (false);
 }

你可以将这个功能封装到一个基类Disposable中,以此为模板来实现可释放模式。

例如:

    /// <summary>
    /// Abstract base class for Disposable types.    
    /// </summary>
    /// <remarks>This class makes it easy to correctly implement the Disposable pattern, so if you have a class which should
    /// be IDisposable, you can inherit from this class and implement the DisposeManagedResources and the
    /// DisposeUnmanagedResources (if necessary).
    /// </remarks>
    public abstract class Disposable : IDisposable
    {
        private bool                    _disposed = false;

        /// <summary>
        /// Releases the managed and unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose (true);
            GC.SuppressFinalize (this);
        }

        /// <summary>
        /// Releases the unmanaged and managed resources.
        /// </summary>
        /// <param name="disposing">When disposing is true, the managed and unmanaged resources are
        /// released.
        /// When disposing is false, only the unmanaged resources are released.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
        protected void Dispose( bool disposing )
        {
            // We can suppress the CA1063 Message on this method, since we do not want that this method is 
            // virtual.  
            // Users of this class should override DisposeManagedResources and DisposeUnmanagedResources.
            // By doing so, the Disposable pattern is also implemented correctly.

            if( _disposed == false )
            {
                if( disposing )
                {
                    DisposeManagedResources ();
                }
                DisposeUnmanagedResources ();

                _disposed = true;
            }
        }

        /// <summary>
        /// Override this method and implement functionality to dispose the 
        /// managed resources.
        /// </summary>
        protected abstract void DisposeManagedResources();

        /// <summary>
        /// Override this method if you have to dispose Unmanaged resources.
        /// </summary>
        protected virtual void DisposeUnmanagedResources()
        {
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="Disposable"/> is reclaimed by garbage collection.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
        ~Disposable()
        {
#if DEBUG
            // In debug-builds, make sure that a warning is displayed when the Disposable object hasn't been
            // disposed by the programmer.

            if( _disposed == false )
            {
                System.Diagnostics.Debug.Fail ("There is a disposable object which hasn't been disposed before the finalizer call: {0}".FormatString (this.GetType ().Name));
            }
#endif
            Dispose (false);
        }
    }

2
我不认为这是一种糟糕的方法。事实上,我认为这比使用分析器要好得多,因为它非常简单易行。唯一的缺点是它无法检查其他人的类,因此仍然需要使用分析器。但是,既然您可以控制在终结器中添加“Debug.Assert”,为什么不选择更简单的方法呢?您真的喜欢一直在分析器中运行程序以捕捉一些粗心的错误吗?我想知道什么样的软件开发可以使这种琐碎的方法与“过于混乱和复杂”相比:P - kizzx2
3
Finalizers会带来性能损失,这可能与问题的隐含目标不符。Jeffrey Richter的《CLR via C# 2nd Edition》(第477页)指出,具有finalizers的对象分配时间更长,被晋升到较老的代(因此稍后回收),并且在收集时需要额外的处理。 - Neil
3
@Neil Whitaker是正确的。请参考Framework Design Guidelines 2nd Ed.第329-330页。“避免使类型可终结”。引用Joe Duffy的话,“在一个负载很重的服务器上,你可能会发现一个处理器花费了100%的时间只运行终结器。” - TrueWill
2
@kizzx2 - finalizers不是必然的。大多数IDisposable类不需要finalizer。对于其余的许多类,SafeHandle的后代就足够了。阅读Microsoft在此主题上的指南。 - TrueWill
2
@kizzx2 - 但是示例代码将 #if DEBUG 放在了终结器内部,而答案建议使用此代码“作为实现可处理模式的模板”。 - TrueWill
显示剩余5条评论

3

@Justin Niessner的建议虽然可行,但我觉得使用完整的分析工具太过繁重。

我创建了自己的解决方案:EyeDisposable。它会检测程序集中未调用Dispose方法的情况。


谢谢,看起来很酷!不过我希望这里有静态分析(也许可以作为 R# 插件)。有一个小建议,在自述文件的 克隆 部分,需要在运行 git submodule update 之前运行 git submodule init - Ohad Schneider
1
@OhadSchneider 感谢提醒 - 静态分析很酷,但对于非平凡情况可能会有很多误报 - 而且比这个“小”实用程序的原始范围要复杂得多 :P - kizzx2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接