简体   繁体   English

为什么FxCop没有报告CA2000这个非处理类实例的琐碎案例?

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

The following code produces a CA2000 (" Dispose objects before losing scope ") violation for the first line of Main, but not the second. 以下代码为Main的第一行生成CA2000 (“ 丢失范围之前的Dispose对象 ”)违规,但不生成第二行。 I would really like the CA2000 violation for the second line, because this is a (obviously simplified) pattern often found in a large code base I work on. 我真的很喜欢第二行的CA2000违规,因为这是我工作的大型代码库中常见的(显然简化的)模式。

Does anyone know why the violation is not produced for the second line? 有谁知道为什么第二行没有产生违规?

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

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

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

The short answer is, CA2000 is broken and not likely to get fixed anytime soon. 简短的回答是,CA2000已经破裂,不太可能很快得到修复。 See this bug report which is almost exactly your question: 请参阅此错误报告 ,这几乎是您的问题:

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是一个已知问题的规则,我们将无法以当前形式修复它。


The longer answer is that getting CA2000 right is hard . 更长的答案是让CA2000正确是很难的 In past versions of Visual Studio, particularly 2010, false CA2000 warnings fired all over the place, and there was nothing you could do to make them go away. 在过去的Visual Studio版本中,特别是2010年,在整个地方都发出了错误的CA2000警告,并且没有任何办法可以让它们消失。 Search Stack Overflow for any of the dozens of questions about it. 搜索Stack Overflow以查找有关它的任何问题。

Even in cases where you could eliminate the warning, the workaround was almost worse than just leaving it in place. 即使在您可以消除警告的情况下,解决方法也几乎比将其留在原地更糟糕。 The problem is, in cases like the one you have here, is that you don't want the object disposed before it leaves scope of the factory method. 问题是,在您遇到的情况下,您希望在它离开工厂方法范围之前处置对象。 Except, you do -- if it throws an exception . 除此之外,你会这样做 - 如果它抛出异常 In that case, the return value of the method is lost, and the caller has no way to dispose the object for itself. 在这种情况下,方法的返回值将丢失,并且调用者无法为自己处置该对象。

Unfortunately, trying to figure that out means doing program flow analysis of the compiled IL, to determine if any code path (including exceptional ones) allow the object to leak. 不幸的是,试图弄清楚这意味着对编译的IL进行程序流分析,以确定是否有任何代码路径(包括特殊路径)允许对象泄漏。 The end result was, almost any case where you tried to return an IDisposable from a method would produce the error. 最终的结果是,几乎任何你试图从方法返回IDisposable的情况都会产生错误。

Microsoft responded to this by doing two things: 微软通过做两件事做出了回应:

  • Cranking down on the sensitivity of CA2000, so that it only fires on the most obvious of cases. 降低CA2000的灵敏度,使其仅在最明显的情况下触发。 This seems reasonable, since the obvious cases like your first line are more likely to be bugs than more obscure ones, and easier to fix. 这似乎是合理的,因为像你的第一行这样的明显案例比更模糊的案例更容易出错,而且更容易修复。
  • Taking CA2000 out of their recommended code analysis rules; 使CA2000超出其推荐的代码分析规则; note that their "Extended Correctness" ruleset no longer includes CA2000. 请注意,他们的“扩展正确性”规则集不再包含CA2000。

Along with the Roslyn compiler rewrite comes the ability for tools like FxCop to do some source-level analysis, which may be easier to get correct in this case. 与Roslyn编译器重写相比,FxCop等工具可以进行一些源级分析,在这种情况下可能更容易正确。 In the mean time, the general consensus is, just turn CA2000 off. 同时,普遍的共识是,关闭CA2000。


In case you're curious, a bit of testing seems to confirm that the current (2013) CA rule only fires if a locally contained, locally constructed instance of IDisposable drops out of scope. 如果您感到好奇,一些测试似乎确认当前(2013)CA规则在本地包含的本地构造的IDisposable实例超出范围时触发。 IOW, the object can't leave the method in which you new it up, or CA ignores it. IOW,该对象不能离开您new它的方法,或CA忽略它。 This leads me to believe that CA is simply not digging into method calls for it's analysis. 这让我相信CA根本就没有深入研究它的分析方法调用。 Besides trying to silence false positives, it may also have been part of the overall attempt to speed up the CA process by cutting out some expensive checks, which I believe happened between 2010 and 2012? 除了试图消除误报之外,它还可能是通过削减一些昂贵的支票来加速CA流程的整体尝试的一部分,我相信这些支票发生在2010年和2012年之间?

If I add bit to your example, you can see the obvious pattern of which ones get the warning: 如果我在你的例子中添加一点,你可以看到哪些明显的模式会得到警告:

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

I have a hunch that this happens due to a combination of factors. 我预感到这种情况会因多种因素共同作用而发生。


1. MakeDisposable is fine by itself : 1. MakeDisposable本身很好

There is no error due to MakeDisposable because, well why would it? 由于MakeDisposable没有错误,因为它为什么会这样? You could have 你可以有

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

anywhere in your code. 代码中的任何位置。


2. The references to the IDisposable in MakeDisposable is not taken into account on Main 2.在该引用 IDisposableMakeDisposable不考虑对Main

The call to MakeDisposable() in your Main method does not cause an error because the compiler does not know that the return value of MakeDisposable is the only reference to the IDisposable . Main方法中调用MakeDisposable()不会导致错误,因为编译器不知道MakeDisposable的返回值是对IDisposable唯一引用。

In other words, in the eyes of the compiler, the following forms of MakeDisposable() are equivalent: 换句话说,在编译器的眼中,以下形式的MakeDisposable()是等效的:

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

(the original), or exposing a backing field, creating it if necessary: (原始),或暴露支持字段,必要时创建它:

private static Disposable disposable;

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

    return disposable;
}

private static void DisposeDisposable()
{
    // elided
}

or even exposing a backing field already created: 甚至暴露已经创建的支持字段:

private static Disposable disposable = new Disposable();

private static Disposable MakeDisposable()
{
    return disposable;
}

private static void DisposeDisposable()
{
    // elided
}

so the call in Main is valid. 所以Main的呼叫是有效的。

In Main() , where you are instantiating the IDisposable , with new Disposable(); Main()中,使用new Disposable();实例化IDisposable new Disposable(); the compiler knows that you're not passing it out of that scope so correctly gives the error. 编译器知道你没有将它传递出该范围,因此正确地给出了错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM