结合foreach和using使用方法

42

我正在遍历一个ManageObjectCollection。(这是WMI接口的一部分)。

然而,重要的是以下代码行:

foreach (ManagementObject result in results)
{
    //code here
}

关键在于ManageObject也实现了IDisposable,因此我想将"result"变量放在using块中。有什么办法能做到这一点,而不会变得太奇怪或太复杂吗?

6个回答

38
foreach (ManagementObject result in results)
using(result)
{
    //code here
}
using 块之外分配变量通常不是一个好习惯,因为资源可能会被释放但仍然保持在作用域内。但在这里,将 using 语句嵌套到 foreach 中会使代码更清晰。
编辑: 如另一个答案中所指出的,ManagementObjectCollection 也实现了 IDisposable,因此我已经将其添加到了 using 块中。 无需将 ManagementObjectCollection 放置在 using 声明中。foreach 将在枚举器上调用 Dispose()

我认为Dispose方法必须由创建"result"对象的站点调用。 - Arseny
不,Dispose可以在任何地方调用。 - Cylon Cat
2
foreach 循环会自动调用可枚举对象(Enumerable)的 Dispose 方法,如果它实现了 IDisposable 接口。因此,首先使用 using 是不必要的。 - Alexander
3
在 foreach 循环中,会调用 ManagementObjectCollection.GetEnumerator() 方法返回的枚举器上的 Dispose 方法。但是,ManagementObjectCollection 对象本身不会被调用 Dispose 方法。需注意保持原意不变,同时让翻译更易懂。 - Edward Olamisan
如果//在此处的代码引发异常,那么会发生什么?!所有剩余的结果对象都不会被处理,你就会有一个内存泄漏!! - ALX

23

你可以采取以下方法。

foreach (ManagementObject result in results)
{
  using (result)
  {
    // Your code goes here.
  }
}

C#的好处之一是不同的语言结构可以共享作用域代码块。这意味着您可以执行以下操作以消除嵌套。

foreach (ManagementObject result in results) using (result)
{
  // Your code goes here.
}

了解 foreach 循环语句会调用目标 IEnumerator 上的 Dispose 方法也是很有用的。上面的代码等同于:

IEnumerator enumerator = results.GetEnumerator()
try
{
  while (enumerator.MoveNext())
  {
    ManagementObject result = (ManagementObject)enumerator.Current;
    IDisposable disposable = (IDisposable)result;
    try
    {
      // Your code goes here.
    }
    finally
    {
      disposable.Dispose();
    }
  }
}
finally
{
  IDisposable disposable = enumerator as IDisposable;
  if (disposable != null)
  {
    disposable.Dispose();
  }
}

7

这里有一个更简洁的语法:

foreach (ManagementObject obj in result) using (obj)
{
  // do your stuff here
}

2
我喜欢这种语法,但是Visual Studio会搞砸格式(它会在块中添加一个额外的制表符)。 - Mark

4

您可以通过扩展方法和枚举器获得一个漂亮简洁的语法。首先,在代码中的某个地方定义这个 public static class

public static IEnumerable<ManagementObject> WithDisposal(
                    this ManagementObjectCollection list)
{
    using (list)
    {
        foreach (var obj in list)
        {
            using (obj)
            {
                yield return obj;
            }
        }
    }
 }

...然后你只需要使用这个:

foreach (var obj in /*get the results*/.WithDisposal())
{
    // ...
}

需要注意的是,如果您使用 WithDisposal,那么您将无法保存任何对象以供将来使用。


我喜欢这个。你甚至可以通过对IDisposable施加类型约束来使其通用。 - Chad
1
你可以这样做,但这绝对是一个特例。通常情况下,处理可处理对象的集合应该同时处理它所包含的对象。(我在我的代码中定义了一个通用的“DisposableList”来实现这个目的。) - Miral
1
还有一个需要注意的地方——只有在完全遍历返回的枚举时,才会处理所有内容。如果您中途停止,则集合仍将被处理,但是您尚未查看的任何对象都不会被处理。当然,这也适用于此处发布的大多数其他答案。 - Miral

4

ManagementObjectCollection本身是IDisposable接口...

因此,应该...

using (var results = ..)
{
    foreach (var result in results)
    {
        using (result)
        {
            ...
        }
    }
}

1
这不会在每个“ManagementObject”上调用Dispose(),而只会在“ManagementObjectCollection”本身上调用。 - David Neale
非常严谨的话,应该在foreach循环中使用的ManagementObjectCollectionIEnumerator上都调用Dispose方法。 - Brian Gideon

-1

这看起来很奇怪 - 迭代数组并处理其中包含的每个对象。如果您真的想这样做,请使用

foreach (ManagementObject result in results)
{
    try {
        // code here
    }
    finally {
        result.Dispose();
    }
}

/* do not forget to, or to not reuse results!
results = null; 
results.Clear();
*/

这正是using语句所做的。


1
因为这正是“using语句的作用”,所以你不应该这样做。 - CertifiedCrazy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接