【问题标题】:IDisposable and CA2000 warning during VS2010 Code AnalysisVS2010代码分析期间的IDisposable和CA2000警告
【发布时间】:2011-05-24 14:16:07
【问题描述】:

我在这里需要一些建议,希望有人可以帮助我。我有以下类结构(简化):

public class Bar: IDisposable {...}

public abstract class FooBase: IDisposable
{
    Bar bar;
    bool disposed;

    internal FooBase(Bar bar)
    {
        this.bar=bar;
    }

    public void Dispose()
    {
         Dispose(true);
         GC.SupressFinalize(this);

    }

    protected void Dispose(bool disposing)
    {
         if (!this.disposed)
         {
             if (disposing)
             {
                 this.bar.Dispose();
             }

             this.disposed = true;
         }
    }
}

public FooA: Foo {...}
public FooB: Foo {...}

public static class FooProvider
{
    public static FooA GetFooA()
    {
       Bar bar = new Bar();
       ...
       return new FooA(bar);
    }

    public static FooB GetFooB()
    {
        Bar bar = new Bar();
        ...
        return new FooB(bar);
    }

    ...
}

当我对此运行代码分析时,我在 FooProvider 类的所有“CreateFooX()”方法上收到警告 CA2000。此警告提供以下消息:

“Microsoft. 可靠性:在方法 'FooProvider.GetFooX()' 中,在对象 'bar' 的所有引用超出范围之前调用 System.IDisposable.Dispose。”

Microsoft 建议不要禁止显示此警告,但我不确定它是否会警告代码中的实际问题。确实,在我们考虑的任何 'CreateFooX()' 方法中,在超出范围之前不会释放 'bar',但对它的引用仍然存在于 'FooX' 对象中,该对象最终将被释放,并反过来处理释放 '吧”。

我是否对 Dispose 模式的工作方式理解有误,并且我的代码存在一些根本缺陷,还是应该直接取消此警告?

编辑

由于某些 cmets,我尝试将工厂方法修改为以下内容:

public static class FooProvider
{
    public static FooA GetFooA()
    {
       Bar bar = null;

       try
       {
           bar =  new Bar();
           ...
           return new FooA(bar);
       }
       catch
       {
           if (bar != null) bar.Dispose();
           throw;
       }
    }

    ...
}

但我仍然收到同样的警告。我想这只是一个误报,我可以放心使用它。

感谢您的建议。

【问题讨论】:

  • 不知道你是否为了简洁而省略了它,但不要忘记在 FooBase 中包含一个终结器,以便在代码中不显式调用 Dispose 时实际调用它。
  • 是的,我省略了它。不过还是谢谢你的提醒。
  • @Massif, @InBetween:如果FooBase 只处理托管资源,而不直接 处理任何非托管资源,那么终结器应该是完全没有必要的。
  • @LukeH:你是绝对正确的。我应该指出它确实处理非托管资源,但它与问题没有直接关系,所以我没有提到它。
  • 我也讨厌这种误报,并在这里开票:connect.microsoft.com/VisualStudio/feedback/details/779134。如果您愿意,请投票。

标签: c# .net code-analysis


【解决方案1】:

这是代码分析部分的典型误报。它真的无法理解你的代码的内在情况,所以它给出了一个普遍的答案。请谨慎行事,但只要您确认您有误报,您就可以放心地忽略它。

【讨论】:

    【解决方案2】:

    这不是误报。如果在创建Bar 之后但在将其传递给Foo 构造函数之前抛出异常怎么办?我看到了几个代码路径,其中一个或多个对象可能不会被释放。

    【讨论】:

    • 好点。然而,即使 GetFooX() 仅由 'Bar bar=new Bar()' 后跟 'return new FooX(bar)' 组成,你也会得到同样的警告,这没有意义,因为 new FooX(bar) 不能抛出任何异常(它只在构造函数中设置对象的状态:this.bar=bar)。现在我仍然明白警告来自哪里,因为 FooX 初始化程序在一般情况下可能会抛出异常。
    • 可以显示GetFooX的来电者吗?他们使用using 吗?如果是这样,那么我同意这是一个误报,并且不会责怪 VS,因为它很难确定没有不会处理的代码路径。
    • GetFooX 的所有调用者都在另一个程序集中,因此 VS 分析器无法确定不会 Dispose 的可能代码路径。
    • 好吧,我完全不怪他们。 :-) 确保当您忽略警告时,您在源代码中执行此操作。我还建议使用 Justification 属性来记录在特定情况下忽略警告的原因。
    • 好的,会的。感谢您的建议。
    【解决方案3】:

    我觉得你的一次性模式有点不对劲。我认为您不应该在 FooBase 类中调用 bar.Dispose 。为了您正在处理的对象的安全,并且能够多次安全地调用 Dispose,我会推荐这种方法。

      private bool _disposed;
      public void Dispose()
      {
         Dispose( true );
         GC.SuppressFinalize( this );
      }
    
      protected virtual void Dispose( bool disposing )
      {
         if ( disposing )
         {
            if ( !_disposed )
            {
               if ( Bar != null )
               {
                  Bar.Dispose();
               }
    
               _disposed = true;
            }
         }
      }
    

    至于错误,我认为这应该处理静态分析警告。我在一个测试项目中按如下方式实现了您的代码,启用了所有静态分析警告而没有出现警告问题。

    public class Bar : IDisposable
    {
      private bool _disposed;
      public void Dispose()
      {
         Dispose( true );
         GC.SuppressFinalize( this );
      }
    
      protected virtual void Dispose( bool disposing )
      {
         if ( disposing )
         {
            if ( !_disposed )
            {
               _disposed = true;
            }
         }
      }
    }
    
    public abstract class FooBase : IDisposable
    {
      public Bar Bar
      {
         get;
         set;
      }
    
      internal FooBase( Bar bar )
      {
         Bar = bar;
      }
    
      private bool _disposed;
      public void Dispose()
      {
         Dispose( true );
         GC.SuppressFinalize( this );
      }
    
      protected virtual void Dispose( bool disposing )
      {
         if ( disposing )
         {
            if ( !_disposed )
            {
               if ( Bar != null )
               {
                  Bar.Dispose();
               }
    
               _disposed = true;
            }
         }
      }
    }
    
    public class FooA : FooBase
    {
      public FooA( Bar bar )
         : base( bar )
      {
      }
    }
    
    public static class FooProvider
    {
      public static FooA GetFooA()
      {
         Bar bar;
         using ( bar = new Bar() )
         {
            return new FooA( bar );
         }
      }
    }
    
    [TestClass]
    public class UnitTest1
    {
      [TestMethod]
      public void StaticAnalysisTest()
      {
         Assert.IsNotNull( FooProvider.GetFooA().Bar );
      }
    }
    

    我希望这会有所帮助。

    【讨论】:

    • 糟糕,我在发布代码时并没有考虑。是的,Dispose 模式已经过时了。我已经编辑了,谢谢!代码真的和你写的一样。
    • 请注意 FooProvider 中 FooA GetFooA() 的实现。一次性模式更新后,将处理静态分析警告。单元测试代码显示 Bar 在方法退出后仍可访问且未清空。
    • 是的,您的代码应该摆脱警告,因为您正在向 bar 添加公共 get 属性。但这有两个主要问题:您正在“打破”黑匣子并将酒吧暴露给我们根本不想要的 FooX 对象的消费者。其次,我们根本不希望 FooX 消费者知道 Bar 类,这就是 Provider 类的全部意义所在。
    • 关于你的第二条评论, using 语句也不是一个有效的选项。如果我这样做,我实际上是在向 FooProvider 消费者传递一个带有已处置栏的 FooX 对象。
    • 我不确定将 Bar 公开为公共财产是否能解决问题。我这样做只是为了在单元测试中表明它不会被破坏,直到 FooA 被破坏。如果您将其更改为私有字段并有一个方法返回它,它应该以相同的方式运行。祝你好运!
    【解决方案4】:

    这个问题至少有一部分不是真正的误报,即使它不一定是一个非常有用的问题检测。要解决您编辑版本的剩余问题,您需要在 bar 分配之后立即打开 try 块,而不是在它之前。 例如

    Bar bar = new Bar();
    try
    {
        ///...            
        return new FooA(bar);
    }
    catch
    {
        bar.Dispose();
        throw;
    }
    

    很遗憾,在您进行此更改后,您仍会收到 CA2000 违规,这可能是误报。这是因为该规则不会检查您是否将bar 置于新创建的FooA 的状态中。如果它进入FooA 中的状态,您可以安全地为违规创建抑制。但是,如果它没有进入FooA 中的状态,您应该将它放在finally 子句中而不是catch 子句中。

    【讨论】:

    • barFooA 的状态成员,所以是的,我可以取消警告。我不太确定的是,正如您所说,bar 的初始化是否应该在“try-catch”块内。如果 'Bar' 构造函数抛出,我真的没有 'Bar' 对象 'bar' 所以我真的不能处理它吗?我不知道 bar 处于什么状态,如果 bar 代表任何东西。
    • 如果 Bar 构造函数抛出,您将无法使用部分构造的实例。您的 bar 变量值将保持为空。
    猜你喜欢
    • 1970-01-01
    • 2011-06-22
    • 1970-01-01
    • 1970-01-01
    • 2011-03-31
    • 2012-04-25
    • 2016-04-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多