7

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

CA2000:Microsoft.Reliability:在方法“XYZ”中,对象“ABC”并未沿所有异常路径进行处理。在对对象“ABC”的所有引用超出范围之前调用 System.IDisposable.Dispose。

警告。问题是我所做的任何事情似乎都无法消除警告——而且我已经花了几个小时在网上搜索并尽我所能。

首先,让我明确一点,我不是在谈论放置一个简单的 using 块来正确处理局部变量——这不是问题。就我而言,当对象由方法返回或分配给方法中的另一个对象时,就会出现这些警告。

这是一个包含四个此类警告的代码示例:

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

我已经评论了出现警告的行。

我已尝试按照 MSDN 文章(此处)中的描述重构这两种 Create 方法,但仍然出现警告。

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

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

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

4

5 回答 5

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;
    }
}
于 2011-11-23T16:30:27.803 回答
1

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

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

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

这里描述了一个解决方案:所有权转移时如何摆脱 CA2000 警告?

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

于 2011-11-23T16:23:59.407 回答
0

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

SomeOtherObjects 是否需要实现 IDisposable?

于 2011-11-23T16:02:29.843 回答
0

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

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

T regDisposable<T>RegDispose(T newThing) 其中 T:IDisposable
{
  myDisposableManager.Add(newThing);
  返回新事物;
}

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

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

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

于 2011-11-23T18:13:52.767 回答
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 文档中有非常简短的提及。

于 2019-07-03T20:52:50.617 回答