IDisposable在方法中创建并返回

时间:2013-01-24 00:39:46

标签: c# design-patterns static-analysis idisposable

我很高兴编写了一个工作正常的项目,并且在运行时没有表现出任何奇怪的东西。所以我决定运行静态代码分析工具(我使用的是Visual Studio 2010)。结果是违反了规则CA2000,消息如下:

警告 - CA2000:Microsoft.Reliability:在方法' Bar.getDefaultFoo()'中,调用System.IDisposable.Dispose on object' new Foo()'在所有引用都超出范围之前。

引用的代码如下:

private static IFoo getDefaultFoo()
{
    return (Baz.canIDoIt()) ? new Foo() : null;
}

我认为自己:也许条件表达会破坏逻辑(我的或验证者)。改为:

private static IFoo getDefaultFoo()
{
    IFoo ret = null;
    if (Baz.canIDoIt())
    {
        retFoo = new Foo();
    }
    return ret;
}

同样的事情再次发生,但现在该对象被称为retFoo。我已经用谷歌搜索了,我已经发布了,我已经知道了堆栈溢出。找到this article。创建对象后,我无需执行任何操作。我只需要返回它的引用。但是,我尝试应用OpenPort2示例中建议的模式。现在代码如下所示:

private static IFoo getDefaultFoo()
{
    Foo tempFoo = null;
    Foo retFoo = null;
    try
    {
        if (Baz.canIDoIt())
        {
            tempFoo = new Foo();
        }
        retFoo= tempFoo;
        tempFoo = null;
    }
    finally
    {
        if (tempFoo != null)
        {
            tempFoo.Dispose();
        }
    }
    return retFoo;
}

再次发出相同的消息,但这次tempFoo变量是规则违规者。所以基本上,代码变得扭曲,更长,更不理性,不必要地复杂并且做得非常相似,但速度更慢。

我还发现了this question,其中同样的规则似乎以类似的方式攻击有效代码。提问者建议忽略警告。我还阅读了this thread和大量类似的问题。

我错过了什么吗?规则是否有问题/无关紧要?我该怎么办?忽视?处理一些魔法方式?也许应用一些设计模式?

编辑:

根据Nicole的要求,我提交了我尝试使用的相关代码。

public class DisposableFooTest
{
    public interface IFoo
    {
        void bar();
    }

    public class Foo : IFoo, IDisposable
    {
        public void bar()
        {
            Console.Out.WriteLine("Foo baring now");
        }

        public void Dispose()
        {
            // actual Dispose implementation is irrelevant, or maybe it is?
            // anyway I followed microsoft dispose pattern
            // with Dispose(bool disposing)
        }
    }

    public static class Baz
    {
        private static bool toggle = false;
        public static bool canIDoIt()
        {
            toggle ^= true;
            return toggle;
        }
    }

    private static IFoo getDefaultFoo()
    {
        IFoo result = null;
        try
        {
            if (Baz.canIDoIt())
            {
                result = new Foo();
            }

            return result;
        }
        catch
        {
            if (result != null)
            {
                (result as IDisposable).Dispose();
                // IFoo does not inherit from IDisposable, hence the cast
            }

            throw;
        }
    }

    public static void Main()
    {
        IFoo bar = getDefaultFoo();
    }
}

分析报告包含以下内容:
`CA2000:Microsoft.Reliability:在方法' DisposableFooTest.getDefaultFoo()'中,调用System.IDisposable.Dispose on object' result'在所有引用都超出范围之前。 %% projectpath %% \ DisposableFooTest.cs 44测试

EDIT2:

以下方法解决了CA2000问题:

private static IFoo getDefaultFoo()
{
    Foo result = null;
    try
    {
        if (Baz.canIDoIt())
        {
            result = new Foo();
        }
        return result;
    }
    finally
    {
        if (result != null)
        {
            result.Dispose();
        }
    }
}

不幸的是,我无法这样做。更重要的是,我更希望遵循面向对象的原则,良好实践和指南来简化代码,使其可读,可维护和可扩展。我怀疑是否有人按预期阅读:如果可能的话给Foo,否则为null。

4 个答案:

答案 0 :(得分:9)

这是一个误报警告。如果IFoo实现IFoo,则无法返回适当的IDisposable实例,而代码分析工具会警告您没有正确处理它。

代码分析不会分析您的意图或逻辑,只是试图警告常见错误。在这种情况下,它“看起来像”您正在使用IDisposable对象而不是调用Dispose()。在这里,您正在按设计进行,因为您希望您的方法返回一个新实例,并将其作为工厂方法的一种形式。

答案 1 :(得分:8)

这里的问题不是你的C#代码明确做的事情,而是生成的IL使用多个指令来完成C#代码中的“步骤”。

例如,在第三个版本中(使用try / finally),return retFoo行实际上涉及分配由编译器生成的另一个变量,并且您无法在原始代码中看到。由于此赋值发生在try / finally块之外,因此确实会引入未处理异常的可能性,使您的一次性Foo实例“孤立”。

这是一个可以避免潜在问题的版本(并通过CA2000筛选):

private static IFoo getDefaultFoo()
{
    IFoo result = null;
    try
    {
        if (Baz.canIDoIt())
        {
            result = new Foo();
        }

        return result;
    }
    catch
    {
        if (result != null)
        {
            result.Dispose();
        }

        throw;
    }
}

显然,与第一个版本相比,这会妨碍可读性和可维护性,因此您必须确定孤立的可能性以及不处置孤儿的后果是否实际上值得代码更改,或者是否抑制在这种特殊情况下,CA2000违规可能更可取。

答案 2 :(得分:2)

静态分析基本上是抱怨的原因如下:

  1. IFoo界面不会继承IDisposable
  2. 但是你要返回一个必须处理的具体实现,并且调用者无法知道这一点。
  3. 换句话说,GetDefaultFoo方法的调用者需要IFoo实现,但不知道您的实现需要明确处理,因此可能不会处理它。如果这是其他库中的常见做法,则必须手动检查项是否实现IDisposable以及任何可能的接口的每个可能实现。

    显然,这有点不对劲。

    恕我直言,解决此问题的最简洁方法是让IFoo继承IDisposable

    public interface IFoo : IDisposable
    {
        void Bar();
    }
    

    这样,来电者就知道他们收到的任何可能的实施都必须处理掉。这也将允许静态分析器检查您使用IFoo对象的所有位置,并在您未正确处理它们时发出警告。

答案 3 :(得分:-1)

返回IDisposable对象时,如果返回null值,则处理该对象并在方法中返回null-这应清除与“私有静态IFoo getDefaultFoo()”关联的“警告/建议”消息方法。当然,您仍然需要在调用例程中将对象作为IDisposable处理(使用或处置)。

private static IFoo getDefaultFoo()
{
    IFoo ret = null;
    if (Baz.canIDoIt())
    {
        retFoo = new Foo();
    }
    return ret;
}

更改为

private static IFoo getDefaultFoo()
{
    IFoo ret = null;
    if (Baz.canIDoIt())
    {
        retFoo = new Foo();
    }
    if (ret == null)
    {
        ret.dispose();
        return null;
    }
    else
        return ret;
}