告诉 FxCop 另外一个方法在调用 dispose。

14

通常当你销毁一个私有成员时,你可能会这样做:

public void Dispose() {
    var localInst = this.privateMember;
    if (localInst != null) {
        localInst.Dispose();
    }
}

局部变量被赋值的目的是为了避免竞态条件,即其他线程在空值检查后将私有成员赋值为null。在这种情况下,如果对实例调用Dispose两次,我并不在意。

我经常使用这种模式,所以我编写了一个扩展方法来实现:

public static void SafeDispose(this IDisposable disposable)
{
    if (disposable != null)
    {
        // We also know disposable cannot be null here, 
        // even if the original reference is null.
        disposable.Dispose();
    }
}

现在在我的类中,我可以这样做:

public void Dispose() {
    this.privateMember.SafeDispose();
}

问题在于,FxCop并不知道我正在这样做,因此它会在每种情况下都给出CA2000: Dispose objects before losing scope警告。

我不想关闭此规则,也不想抑制每个情况。 有没有一种方法可以向 FxCop 暗示此方法就像Dispose一样重要呢?


3
你的样本中写着 this.privateMember.Dispose()。你的代码实际上是写成了 this.privateMember.SafeDispose() 吗? - Ed Chapel
3个回答

9
简短的回答是:没有办法提示对象在其他地方被处理。
通过一些反编译工具(如Reflector、dotPeek等)可以解释原因。
FxCop位于C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop中(根据您的操作系统/VS版本组合进行调整)。规则在Rules子目录中。
在主FxCop文件夹中,打开:
- Microsoft.VisualStudio.CodeAnalysis.dll - Microsoft.VisualStudio.CodeAnalysis.Phoenix.dll - phx.dll
在Rules文件夹中,打开DataflowRules.dll。
在DataflowRules.dll中找到Phoenix.CodeAnalysis.DataflowRules.DisposeObjectsBeforeLosingScope。这就是实际执行评估的类。
查看其中的代码,您会发现两个与您的问题相关的有趣事项:
- 它使用一个名为SharedNeedsDisposedAnalysis的共享服务。 - 它派生自FunctionBodyRule。
第一个项目很有趣,因为SharedNeedsDisposedAnalysis决定哪些符号需要调用Dispose()。它非常彻底,通过对代码进行“遍历”来确定需要处理的内容以及实际处理的内容。然后它保存这些内容的表格以供以后使用。
第二个项目很有趣,因为FunctionBodyRule规则评估单个函数的主体。还有其他类型的规则,比如FunctionCallRule,它们评估函数调用成员(例如,ProvideCorrectArgumentsToFormattingMethods)。
重点是,在SharedNeedsDisposedAnalysis服务中可能会出现“遗漏”,因为它可能没有递归到您的方法中以查看实际上是否已处理,而FunctionBodyRule规则不能超越函数主体,所以它无法捕获您的扩展。这就是Guard.Against(arg)等“防护函数”从未被视为在使用参数之前验证其有效性的原因——即使这正是“防护函数”所做的事情,FxCop仍然会告诉您检查参数是否为空。
您基本上有两个选择:
- 排除问题或关闭该规则。它不会按照您的意愿执行。 - 创建一个理解扩展方法的自定义/派生规则。使用您的自定义规则替换默认规则。

在编写自定义FxCop规则后,我想告诉您我发现这是一项相当复杂的任务。如果您要开始这项工作,虽然建议使用新的Phoenix引擎规则样式(当前DisposeObjectsBeforeLosingScope使用的就是该样式),但我发现理解旧版/标准的FxCop SDK规则更容易些(请参阅主FxCop文件夹中的FxCopSdk.dll)。由于几乎没有文档可供参考,因此Reflector将是您研究如何进行此操作的巨大助力。请查看Rules文件夹中的其他程序集,以了解其中的示例。


1
实际上,CA1062有一个识别守卫函数的机制:使用名为ValidatedNotNullAttribute的属性进行装饰。不幸的是,对于处置规则没有类似的机制。 - Nicole Calinoiu
谢谢Travis!你知道写自定义规则的好指南吗?我已经将fxcop作为构建的一部分运行,并且希望确保它可以从源代码(而不是从Program Files)加载规则。 - Haacked
我发现这篇白皮书非常有用。 - riezebosch
@NicoleCalinoiu - 非常好的提示;我不知道那个存在。 - Travis Illig
我没有一个好的地方来编写自定义的FxCop规则。我是通过大量使用Reflector和Google学习的。@riezebosch链接的那篇白皮书看起来相当不错。 - Travis Illig
哦,如果要从不同的位置加载规则,请使用“/r”命令行选项来指定包含自定义规则的文件夹。 - Travis Illig

1

我绝不是FxCop专家,但这个问题关于使用SuppressMessage是否有答案?我不知道用SuppressMessage属性装饰您的SafeDispose方法是否会导致FxCop在分析调用它的方法时抑制该消息,但似乎值得一试。

不要相信下面的语法,但类似于:

[SuppressMessage("Microsoft.Design", "CA2000:Dispose objects before losing scope", Justification = "We just log the exception and return an HTTP code")]
public static void SafeDispose(this IDisposable disposable)

0

这个代码分析规则有问题,正如Travis所概述的那样。它似乎会排队任何“新”的操作,并且除非dispose调用接近,否则会触发CA2000。

不要使用new,而是在方法体中调用一个带有this的方法:

MyDisposableClass result;
MyDisposableClass temp = null;
try
{
  temp = new MyDisposableClass();
  //do any initialization here
  result = temp;
  temp = null;
}
finally
{
  if (temp != null) temp.Dispose();
}
return result;

这样做可以消除任何初始化导致对象无法被处理的可能性。在您的情况下,当您“new up” privatemember 时,您会在类似上述方法的方法中执行此操作。使用此模式后,您当然仍然需要正确处理,而您的扩展方法是概括该空值检查的好方法。

我发现您可以避免CA2000同时传递IDisposable并对它们进行所需的操作 - 只要您在类似上述的方法中正确使用它们。试一试,看看是否适用于您。祝您好运,问得好!

此规则的其他修复方法(包括此方法)在此处列出:CA2000:Dispose objects before losing scope (Microsoft)


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