处理对象的代码分析友好方式

时间:2011-11-23 15:51:45

标签: c# .net code-analysis idisposable static-code-analysis

作为Visual Studio 2010(主要是C#4.0)开发标准的一部分,我们启用了代码分析。当我正在审查最近为新项目提交的代码时,我看到了很多

  

CA2000:Microsoft.Reliability:在方法'XYZ'中,对象'ABC'不是   沿着所有异常路径放置。调用System.IDisposable.Dispose on   对所有引用的对象'ABC'超出范围。

警告。问题在于,我所做的一切似乎都没有消除这些警告 - 而且我花了几个小时搜索网络并尽我所能。

首先,让我明确一点,我不是在谈论将一个简单的使用块放入正确处理局部变量 - 这不是问题。在我的例子中,当对象由方法返回或分配给方法中的另一个对象时,会出现这些警告。

以下是包含四个此类警告的代码示例:

public void MainMethod()
{
    var object1 = CreateFirstObject();    // Warning here
    var object2 = CreateSecondObject();   // Warning here

    SomeCollectionProperty.Add(object1);
    SomeCollectionProperty.Add(object2);
}

private SomeObject CreateFirstObject()
{
    var theObject = new SomeObject()      // Warning here
    {
        FirstProperty = "some value",
        // ...
    };

    return theObject;
}

private SomeOtherObject CreateSecondObject()
{
    var theObject = new SomeOtherObject() // Warning here
    {
        FirstProperty = "a different value",
        // ...
    };

    return theObject;
}

我评论了发生警告的行。

我已经尝试重构两个Create方法,如MSDN文章(here)中所述,但警告仍然出现。

更新 我应该注意SomeObject和SomeOtherObject都实现了IDisposable。

此外,虽然对象初始化程序可能是问题的一个组成部分,但请记住,初始化程序与两个私有方法隔离,与MainMethod中的警告无关。

有谁能告诉我如何正确实施这些方法来消除CA2000警告?

5 个答案:

答案 0 :(得分:10)

在这种情况下,CA2000正在检测的问题是,如果在方法传出之前发生异常,则一次性实例可能会“孤立”。例如,CreateFirstObject的“正确”实现类似于以下内容:

private SomeObject CreateFirstObject()
{
    var theObject = new SomeObject();
    try
    {
        theObject.FirstProperty = "some value";
    }
    catch
    {
        theObject.Dispose();
        throw;
    }

    return theObject;
}

鉴于您所描述的有关MainMethod所需行为的内容,其“正确”实现可能如下所示:

public void MainMethod()
{
    var object1 = CreateFirstObject();
    try
    {
        SomeCollectionProperty.Add(object1);

        var object2 = CreateSecondObject();
        try
        {
            SomeCollectionProperty.Add(object2);
        }
        catch
        {
            object2.Dispose();
            throw;
        }
    }
    catch
    {
        object1.Dispose();
        SomeCollectionProperty.Remove(object1); // Not supposed to throw if item does not exist in collection.

        throw;
    }
}

答案 1 :(得分:1)

摆脱警告的一种方法是在代码中禁止它:

[SuppressMessage(
    "Microsoft.Reliability",
    "CA2000:DisposeObjectsBeforeLosingScope",
    Justification = "Factory method")]

但这不是问题的真正解决方案

此处描述了一个解决方案:How to get rid of CA2000 warning when ownership is transferred?

在上述链接中,基本上声明将对象添加到实现ICollection<T>的集合中,但我没有测试过。

答案 2 :(得分:0)

如果将返回的对象包装在using块中的main中,或者实现finally以处理对象,会发生什么?

SomeOtherObjects是否需要实现IDisposable?

答案 3 :(得分:0)

需要的是实现类似于“using”块的模式,但是在成功返回它的场景中禁用对象的处理。 Nicole Calinoiu提供的方法是合理的,但我更愿意避免捕获那些只会冒泡的异常。考虑到C#语言的限制,我首选的代码表达式是使用InitializedSuccessfully标志,然后有一个“finally”块,如果没有调用InitializedSuccessfully,它将负责处理。

如果一个类要包含许多IDIsposable对象,并且一旦构造完成就会修复这些对象的集合,那么定义一个IDisposable管理器类可能会很有用,它将保存一个IDisposable对象列表。让您的类的构造函数接受DisposableManager对象作为参数,并将它构造的所有对象放入由此生成的列表中(对您的类有一个实例方法可能会有所帮助:

T regDisposable<T>RegDispose(T newThing) where T:IDisposable
{
  myDisposableManager.Add(newThing);
  return newThing;
}

要使用它,在myDisposableManager初始化之后,只需说出:var someDisposableField = RegDispose(new someDisposableType());。这种模式将带来两大好处:

  1. 所有主要类必须在其Dispose实现中执行`if(myDisposableManager!= null)myDisposableManager.Dispose();`在构造函数中设置IDisposable对象的行为(使用RegDispose)也将提供它的清理。
  2. 如果构造函数抛出异常,调用主对象的构造函数的代码可以在它创建并传入的DisposableManager对象上调用Dispose方法。这将确保及时清除部分创建的对象,一些本来是不可能的东西。

在vb中,基类构造函数可以将构造函数参数公开为字段初始值设定项可用的字段。因此,可以很好地在字段初始化器和显式构造函数中使用RegDispose模式。在C#中是不可能的。为此目的可以使用[threadstatic]字段,但需要注意确保设置的任何此类字段也未设置。从线程池线程之类的内容调用构造函数可能会产生内存泄漏。此外,无法像普通的那样有效地访问threadstatic字段,我不知道C#中有什么方法可以避免多次重新获取线程静态字段 - 对于每个注册的IDisposable对象一次。

答案 4 :(得分:0)

我们使用的可清除警告大部分时间的模式是

DisposableObject retVal;
DisposableObject tempVal;

try{
  tempVal = new DisposableObject();
  tempVal.DoStuff();
  retVal = tempVal;
  tempVal = null;
} finally{
  if (tempVal != null) { tempVal.Dispose();} //could also be writtent tempVal?.Dispose();
}

return retVal;

不幸的是,在某些情况下,警告并不会消失,因此我们在本地删除警告,理由是一次性用品被图案覆盖。

这在Microsoft Documentation上已经很简短地提到过。