为什么 FxCop 不为这种微不足道的未处置 class 实例报告 CA2000?

Why does FxCop not report CA2000 for this trivial case of not-disposed class instance?

以下代码对 Main 的第一行产生了 CA2000(“在丢失范围之前处置对象”)违规,但第二行没有。我真的很喜欢第二行的 CA2000 违规,因为这是一个(明显简化的)模式,经常在我处理的大型代码库中找到。

有谁知道为什么第二行不产生违规?

public static void Main()
{
    new Disposable();
    MakeDisposable();
}

private static Disposable MakeDisposable() { return new Disposable(); }

private sealed class Disposable : IDisposable
{
    public void Dispose()
    {
    }
}

我预感这是多种因素共同作用的结果。


1. MakeDisposable 本身就可以:

由于 MakeDisposable 没有错误,因为,为什么会这样?你可以

using ( var disposable = MakeDisposable() )
{
    // elided
}

代码中的任意位置。


2。 MakeDisposableIDisposablereferencesMain

中未被考虑

在您的 Main 方法中调用 MakeDisposable() 不会导致错误,因为编译器不知道 MakeDisposable 的 return 值是 引用IDisposable.

也就是说,在编译器看来,MakeDisposable()的以下几种形式是等价的:

private static Disosable MakeDisposable()
{
     return new Disosable();
}

(原始),或公开一个支持字段,必要时创建它:

private static Disposable disposable;

private static Disposable MakeDisposable()
{
    if ( disposable == null )
        disposable = new Disposable();

    return disposable;
}

private static void DisposeDisposable()
{
    // elided
}

甚至公开已经创建的支持字段:

private static Disposable disposable = new Disposable();

private static Disposable MakeDisposable()
{
    return disposable;
}

private static void DisposeDisposable()
{
    // elided
}

因此 Main 中的调用有效。

Main() 中,您使用 new Disposable(); 实例化 IDisposable,编译器 知道 您没有传递它该范围如此正确地给出了错误。

简短的回答是,CA2000 坏了,不太可能很快修好。请参阅 this bug report 这几乎正是您的问题:

The warning CA2000 in particular is a rule that has known issues and that we won’t be able to fix it up in its current form.


较长的答案是正确获得 CA2000 困难。在 Visual Studio 的过去版本中,尤其是 2010 年,错误的 CA2000 警告随处可见,而且您无法消除它们。在 Stack Overflow 中搜索有关它的数十个问题中的任何一个。

即使在您可以消除警告的情况下,解决方法也比只保留它更糟糕。问题是,在您遇到的这种情况下,您 不希望 在对象离开工厂方法的范围之前将其处理掉。除非,你这样做——如果它抛出异常。在这种情况下,方法的 return 值将丢失,调用者无法自行处置该对象。

不幸的是,试图弄清楚这一点意味着对已编译的 IL 进行程序流分析,以确定是否有任何代码路径(包括异常代码路径)允许对象泄漏。最终结果是,几乎任何您尝试从方法中 return 一个 IDisposable 的情况都会产生错误。

Microsoft 通过做两件事对此作出回应:

  • 调低 CA2000 的灵敏度,使其仅在最明显的情况下触发。这似乎是合理的,因为像您的第一行这样明显的情况比更隐蔽的情况更可能是错误,并且更容易修复。
  • 从他们推荐的代码分析规则中剔除 CA2000;请注意,他们的 "Extended Correctness" 规则集不再包括 CA2000。

随着 Roslyn 编译器的重写,FxCop 等工具能够进行一些源代码级分析,在这种情况下可能更容易得到正确的分析。同时,普遍的共识是,只需关闭 CA2000。


如果您好奇的话,一些测试似乎可以确认当前 (2013) CA 规则 只有 在本地包含的、本地构建的实例 IDisposable 超出范围。 IOW,该对象不能离开您 new 所在的方法,否则 CA 会忽略它。这让我相信 CA 根本没有深入研究方法调用来进行分析。除了试图消除误报之外,它还可能是通过取消一些昂贵的检查来加快 CA 流程的总体尝试的一部分,我认为这发生在 2010 年到 2012 年之间?

如果我在你的例子中添加一点,你可以看到明显的警告模式:

class Program
{
    public static void Main()
    {
        new Disposable(); // CA2000

        IDisposable x;
        MakeDisposable(out x);

        Test();
    }

    public static Disposable Test()
    {
        IDisposable z;
        var x = MakeDisposable(out z);
        var y = new Disposable(); // CA2000
        return x;
    }

    private static Disposable MakeDisposable(out IDisposable result)
    {
        result = new Disposable();

        new Disposable(); // CA2000
        return new Disposable(); 
    }
}