为啥 FxCop 不报告 CA2000 对于这种未处置的类实例的琐碎情况?

Posted

技术标签:

【中文标题】为啥 FxCop 不报告 CA2000 对于这种未处置的类实例的琐碎情况?【英文标题】:Why does FxCop not report CA2000 for this trivial case of not-disposed class instance?为什么 FxCop 不报告 CA2000 对于这种未处置的类实例的琐碎情况? 【发布时间】:2015-04-12 20:14:11 【问题描述】:

以下代码对 Main 的第一行产生了 CA2000 ("Dispose objects before lost scope") 违规,但不是第二行。我真的很喜欢第二行的 CA2000 违规,因为这是我工作的大型代码库中经常发现的(明显简化的)模式。

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

public static void Main()

    new Disposable();
    MakeDisposable();


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

private sealed class Disposable : IDisposable

    public void Dispose()
    
    

【问题讨论】:

尝试将您的 Make 方法重命名为 Create,看看是否会有所不同。 还有...您使用的是哪个版本的 Visual Studio 和/或 FxCop? 我试着即兴创作一些对你有用的东西,但下次请尝试制作一个更具描述性的标题。 对不起,我应该提一下,这是 Visual Studio 2013 的。 【参考方案1】:

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

尤其是警告 CA2000 是一条存在已知问题的规则,我们无法以当前形式对其进行修复。


更长的答案是让 CA2000 正确很难。在过去的 Visual Studio 版本中,尤其是 2010 版,错误的 CA2000 警告会在各处触发,您无法采取任何措施让它们消失。在 Stack Overflow 上搜索有关它的数十个问题中的任何一个。

即使在可以消除警告的情况下,解决方法也几乎比将其留在原处更糟糕。问题是,在您在这里遇到的情况下,您希望在对象离开工厂方法的范围之前将其处理掉。除非,你这样做 - 如果它引发异常。在这种情况下,方法的返回值会丢失,调用者无法为自己处置对象。

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

微软通过做两件事来回应这一点:

降低 CA2000 的灵敏度,使其仅在最明显的情况下触发。这似乎是合理的,因为像第一行这样的明显案例比更晦涩的案例更有可能是错误,并且更容易修复。 将 CA2000 排除在他们推荐的代码分析规则之外;请注意,他们的“扩展正确性”规则集不再包括 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(); 
    
 

【讨论】:

我想这就是我所期望的。然而,我的抱怨并不是因为工厂没有被标记(我们有很好的模式来确保工厂不泄漏),而是 Main 没有被标记的事实。确保工厂的所有调用者都在泄漏要困难得多。我想我太天真了,但我希望如果这条规则捕捉到任何东西,它将能够捕捉到 Main 方法中的两条线。【参考方案2】:

我有一种预感,这种情况的发生是多种因素造成的。


1. MakeDisposable 本身就很好

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

using ( var disposable = MakeDisposable() )

    // elided

代码中的任何位置。


2。在MakeDisposable 中对IDisposable引用未在Main 上考虑

在您的Main 方法中调用MakeDisposable() 不会导致错误,因为编译器不知道MakeDisposable 的返回值是对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() 中,您正在实例化IDisposable,而new Disposable(); 编译器知道您没有将它正确地传递出该范围会给出错误。 p>

【讨论】:

以上是关于为啥 FxCop 不报告 CA2000 对于这种未处置的类实例的琐碎情况?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 FxCop 会在此 C# 代码中发出有关溢出 (CA2233) 的警告?

基于不适用于 System.Web.UI.Control 对象的 CA2000“在失去范围之前处理对象”创建自定义 FXCop 规则

.NET Framework FxCop 规则 CA1401 PInvokesShouldNotBeVisible 规则 - 为啥存在此规则?

代码分析 (FXCop) 命令行差异

FxCop 自定义规则文档。为啥找不到我的网址?

Global.asax - FxCop 警告冲突 CA1811 与 CA2109