【问题标题】:Object initializer and Dispose when property can throw exception属性可以抛出异常时的对象初始化器和 Dispose
【发布时间】:2011-06-20 10:37:15
【问题描述】:

我有以下设置:

public class SomeClass
{
  private DirectoryEntry _root;
  private DirectorySearcher _searcher;

  public SomeClass()
  {
    _root = new DirectoryEntry("ldap://bla");
    _searcher = new DirectorySearcher(_root)
      {
        PageSize = int.MaxValue,
        SizeLimit = int.MaxValue
      }
  }
}

我使用 int.MaxValue 的原因是因为在这种情况下我知道我会超过默认值,但这个数字永远不会大得离谱,所以我对这一点很好。

但如果我打开 代码分析Microsoft 基本正确性规则,它会抱怨:

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

问题是 PageSize 和 SizeLimit 可以抛出异常,如果发生这种情况,G__initLocal0 对象不会被释放(即使 _searcher 确实被释放)。他们可以抛出的异常是如果您将它们分配给负数,这在这里不会发生,但它仍然会报错。

接下来,我使用常规赋值语句在对象初始化器之外设置属性,但是 ReSharper 抱怨告诉我应该使用初始化器。我可以抑制 ReSharper,但我喜欢想办法在不添加抑制的情况下让事情正常工作。

所以我认为我必须捕获错误,如果可能的话,我不喜欢在构造函数中捕获错误,所以我创建了一个名为 Searcher 的属性,如下所示:

private DirectorySearcher _searcher;
public DirectorySearcher Searcher
{
  get
  {
    if (_searcher != null) return _searcher;
    var searcher = new DirectorySearcher();
    try
    {
      searcher.PageSize = int.MaxValue;
      searcher.SizeLimit = int.MaxValue;
      _searcher = searcher;
    }
    catch
    {
      searcher.PageSize = 1000;
      searcher.SizeLimit = 1000;
    }
    finally
    {
      searcher.Dispose();
    }
    return _searcher;
  }
}

现在代码分析和一切都很满意,但我对解决方案一点也不满意。

有什么提示吗?

【问题讨论】:

  • 您的财产目前将返回一个已处置的DirectorySearcher...
  • "...我不喜欢在构造函数中捕获错误"。这是为什么?似乎比你现在所做的更容易接受。
  • SomeClass 应该实现 IDisposable 并在那里清理。
  • 我看到我正在返回一个已处理的 DirectorySearcher,但该死的东西出于某种原因有效。即使是对 Searcher 的第二次调用也可以正常工作。我可以将此代码移动到我的构造函数,但问题仍然是一样的......
  • “该死的东西出于某种原因工作” -> 我可以确认 DirectorySearcher 违反了所有逻辑,并且在被处理然后调用以进行目录搜索之后不会抛出任何类型的异常。但是你为什么要依赖它呢?您担心 ReSharper 对您的代码所做的纯美学评论,但不担心使用已处置的对象?

标签: c#


【解决方案1】:

问题在于编译器正在有效地生成:

public class SomeClass
{
  private DirectoryEntry _root;
  private DirectorySearcher _searcher;

  public SomeClass()
  {
    _root = new DirectoryEntry("ldap://bla");

    var temp = new DirectorySearcher(_root);
    temp.PageSize = int.MaxValue;
    temp.SizeLimit = int.MaxValue;

    _searcher = temp;
  }
}

您可以通过不为 _searcher 使用较新的属性初始化器语法来避免这种情况,这样您就可以确保在设置属性之前将其正确分配给字段,请参阅Object initializers in using-block generates code analysis warning CA2000

这里还有第二个问题,如果在SomeClass的构造过程中出现错误,调用代码将无法处理SomeClass,从而无法处理_root_searcher,见Handling IDisposable in failed initializer or constructor

【讨论】:

    【解决方案2】:

    你现在做事的方式可能会让每个人都开心,但你因为它行不通。您将在 Searcher 属性中返回已处理的 DirectorySearcher

    我会这样做:

    public SomeClass()
    {
        _root = new DirectoryEntry("ldap://bla");
    
        try
        {
            _searcher = new DirectorySearcher(_root);
            _searcher.PageSize = 1000;
            _searcher.SizeLimit = 1000;
        }
        catch
        {
             if (_searcher != null)
             {
                 _searcher.Dispose();
             }
    
             throw;
        }
    

    }

    我看不出在构造函数中使用 try-catch 块有什么问题。

    在构造 IDisposable 对象时,我不推荐使用属性初始化器语法,因为如果属性初始化抛出,您将无法正常处置它们。

    【讨论】:

    • 你和克里斯的答案都很好,所以我必须给你,因为你是第一个。
    • 这个总纲似乎有效。即使将新语法放在 Try/Catch 逻辑中,仍然无法弄清楚为什么新语法不起作用。这是一个非常微妙(最初非常烦人)的错误,但现在我明白了,我很高兴 FXCop 发出了警告。
    【解决方案3】:

    如果您在 SomeClass 实例的整个生命周期内都需要 _searcher,SomeClass 应实现 IDisposable 并在 Dispose 中处置 _searcher。

    http://msdn.microsoft.com/de-de/library/system.idisposable.aspx

    【讨论】:

    • SomeClass 实现了 IDisposable。为了简洁起见,我没有包括在内。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-14
    相关资源
    最近更新 更多