【问题标题】:iterating through disposable objects遍历一次性对象
【发布时间】:2012-08-22 16:59:04
【问题描述】:

以下是遍历一次性对象的安全方法吗?或者这会导致物体不安全吗? ETC?如果我使用 dispose 语句而不是 using 嵌套会怎样?

public static void Main()
{
   foreach (ChildObject oChild in webApp)
   {
        //On Noes!  Unexpected Error!
   }
}

public static IEnumerable<ChildObject> SafelyGetNextObjInWebApp(WebApplication webApp)
{
    foreach (ParentObject oParent in webApp.Parents)
    {
        using (parent)
        {
            foreach (ChildObject oChild in oParent.Children)
            {
                using (oChild)
                {
                    yield return oChild;
                }
            }
        }
    }
}

【问题讨论】:

标签: c#


【解决方案1】:

除非你的调用者枚举了SafelyGetNextObjInWebApp()返回的所有对象,否则你的方法是不安全的。考虑以下语句中发生的情况:

ChildObject o = SafelyGetNextObjInWebApp(arg).First();

在这种情况下,SafelyGetNextObjInWebApp() 的执行将在 yield 语句处停止,并且永远不会继续。因此,此调用不会处理该对象。

如果您想使用迭代器一次返回一个 Web 服务创建的对象,您应该确保调用是异常安全的,并强制迭代器调用的调用者处置。举例说明:

public IEnumerable<ParentObject> GetParents(WebApplication webApp)
{
    // assumes webApp.Parents uses deferred execution.
    return webApp.Parents;
}

public void ProcessParent(WebApplication webApp)
{
    foreach (ParentObject p in GetParents())
    {
        // Assumes p.Dipsose() calls ChildObject.Dispose() for all p.ChildObjects.
        using(p)
        {
            foreach (ChildObject o in p.ChildObjects)
            {
                // do something with o
            }
        }
    }
}

}

【讨论】:

  • 你对我如何写这个有什么建议吗?我是否必须编写 2 个 ienumerables,一个用于 ParentObject,一个用于 ChildObject,并期望调用者同时调用并正确处理?这看起来就像将整个语句留在 main 方法的前几行一样混乱。
  • 您也可以让 ParentObject.Dispose() 对所有 ChildObject 引用调用 Dispose(),从而消除使用块的部分。但是,您应该强制调用者在 Web 应用程序检索到的每个 ParentObject 上调用 Dispose()。 (编辑以说明)
  • ParentObject 是在 Web 服务中定义的,我想我可以继承它来按照您的建议进行操作,但我认为这在这种情况下效果不佳。看起来我又开始使用 public void ProcessParent()。这只是意味着我编写的每个实用程序都以 Try{} 开头,然后是 2 个 foreachs、2 个 usings,并且“//do something with o”成为对真正实用程序的调用。希望有更好的东西:)
  • 如果 ParentObject 是一次性的并且它聚合(并拥有)一次性类型,那么设计约定是 ParentObject.Dispose() 处理聚合的对象。我会验证情况是否如此,因为它将简化您的一些实现。此外,如果 ChildObject 具有对 ParentObject 的引用,那么您可能会使用 IEnumerable.SelectMany() 来展平循环,然后编写自己的 Dispose() 方法来处理孩子的父母。
  • 嘿,你是对的。我反映到 ParentObject,它会跟踪所有打开的 ChildObjects,并且对它们的 dispose 方法调用也会关闭它们。我从来不知道这个约定。这确实简化了一些事情,虽然我想在循环时,我可能想更快地关闭它们。
【解决方案2】:

毕竟,“安全”方法可能不会更安全。如果在迭代所有父对象和子对象之前迭代中断(或失败)怎么办?剩余的对象不会被释放(至少,不会在那个特定的方法中)。

似乎应该将迭代和处置分开。您将拥有更简洁的代码并更好地控制程序正在执行的操作。

还有更多...

C# 迭代器模式会使“安全”方法以一种微妙的方式失败。在yield 子对象之后,程序将有效地“退出”using {...} 块,从而处置子对象,使其对通过迭代 SafelyGetNextObjInWebApp() 获得它的人无法使用。

可以做什么

SafelyGetNextObjInWebApp() 中取出using 语句。将产生的子对象封装在“知道”何时处置子对象的“工作单元”类中。

【讨论】:

  • 我的问题是,针对这个 api,在我编写的几乎所有内容中,我都需要针对一次性对象深度迭代 2 或 3 层。与其让我的每个实用程序的主要语句开始 4 个选项卡深度只是为了调用 Run() 方法,如果我能找到某种更简单的方法来执行此操作,或者将其放到单独的方法中,那就太好了。
  • Linq 方法 SelectMany() 会将多个级别的迭代折叠到一个语句中。但是,您会丢失对父对象的任何引用,否则这些引用会通过 foreach 语句公开。如果子对象有对父对象的引用,这应该不是问题。
【解决方案3】:

在使用你的块的末尾执行一个 dispose,所以在你的函数末尾使用或使用 dispose 之间的结果相同

【讨论】:

  • 所以安全吗?就像 foreach{ foreach{ 收益回报;处置;}处置;}?
  • 您仅将 using 或 dispose 用于非托管资源作为与数据库或流的连接..
  • 对于你不需要执行的托管资源,让垃圾收集器
  • 是的,在这个例子中,我们假设 ChildObject 和 ParentObject 是需要处理的对象,因为这是 api 告诉我的。在我上面写的代码中,有一个错误,using语句的结束块是否还在执行?
  • @Candie:如果只迭代部分集合元素,代码不好
【解决方案4】:

您的代码可能没问题,但在正常使用中可能会出现问题:您返回的对象将在下一次迭代中处理。所以如果你的调用者的代码看起来像

foreach(var i in SafelyGetNextObjInWebApp())
{
   if (IsInteresting(i))
   { 
     interestingItems.Add(i);
   }
}
// here interestingItems contains disposed items you can't use.

通过提供迭代所有项目并将Action&lt;T&gt; 作为参数的方法来反转代码可能会突出显示每个项目的处理必须在操作内完成的事实。

【讨论】:

    猜你喜欢
    • 2012-01-08
    • 2016-05-12
    • 2014-08-09
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多