显然有许多方法可以迭代集合。很好奇是否有什么不同,或者为什么你用一种方式而不是另一种。

第一类型:

List<string> someList = <some way to init>
foreach(string s in someList) {
   <process the string>
}

其他方式:

List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
    <process the string>
});

我想,除了我上面使用的匿名委托,你还可以指定一个可重用的委托。


当前回答

我猜somlist . foreach()调用可以很容易地并行化,而正常的foreach不那么容易并行运行。 您可以很容易地在不同的核心上运行几个不同的委托,这对于普通的foreach来说并不容易做到。 这只是我的小意思

其他回答

我猜somlist . foreach()调用可以很容易地并行化,而正常的foreach不那么容易并行运行。 您可以很容易地在不同的核心上运行几个不同的委托,这对于普通的foreach来说并不容易做到。 这只是我的小意思

常言道,细节决定成败……

两种集合枚举方法的最大区别是foreach携带状态,而foreach (x =>{})不携带状态。

但是让我们深入挖掘一下,因为有一些事情您应该知道,这些事情可能会影响您的决定,并且在为这两种情况编写代码时,您应该注意一些注意事项。

让我们在我们的小实验中使用List<T>来观察行为。在这个实验中,我使用的是。net 4.7.2:

var names = new List<string>
{
    "Henry",
    "Shirley",
    "Ann",
    "Peter",
    "Nancy"
};

让我们先用foreach遍历这个:

foreach (var name in names)
{
    Console.WriteLine(name);
}

我们可以将其展开为:

using (var enumerator = names.GetEnumerator())
{

}

有了枚举器在手,查看被罩下的内容,我们得到:

public List<T>.Enumerator GetEnumerator()
{
  return new List<T>.Enumerator(this);
}
    internal Enumerator(List<T> list)
{
  this.list = list;
  this.index = 0;
  this.version = list._version;
  this.current = default (T);
}

public bool MoveNext()
{
  List<T> list = this.list;
  if (this.version != list._version || (uint) this.index >= (uint) list._size)
    return this.MoveNextRare();
  this.current = list._items[this.index];
  ++this.index;
  return true;
}

object IEnumerator.Current
{
  {
    if (this.index == 0 || this.index == this.list._size + 1)
      ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
    return (object) this.Current;
  }
}

有两点是显而易见的:

返回一个有状态对象,并对底层集合了如指掌。 集合的副本是浅副本。

当然,这绝不是线程安全的。正如上面所指出的,在迭代时更改集合是不好的。

但是在迭代过程中,由于我们之外的人在迭代过程中破坏集合而导致集合失效的问题怎么办?最佳实践建议在操作和迭代期间对集合进行版本控制,并检查版本以检测底层集合何时发生变化。

这就是事情变得真正模糊的地方。根据微软文档:

如果对集合进行了更改,例如添加、修改或 删除元素时,枚举器的行为是未定义的。

那是什么意思呢?举例来说,仅仅因为List<T>实现了异常处理,并不意味着所有实现IList<T>的集合都会做同样的事情。这似乎明显违反了利斯科夫替换原则:

父类的对象可以被父类的对象替换 子类,而不会破坏应用程序。

另一个问题是枚举器必须实现IDisposable——这意味着潜在内存泄漏的另一个来源,不仅是调用者弄错了,而且作者没有正确实现Dispose模式。

最后,我们有一个终生的问题…如果迭代器是有效的,但底层集合已经没有了,会发生什么?我们现在看到的是……当你分离一个集合的生命周期和它的迭代器时,你是在自找麻烦。

现在让我们检查ForEach(x => {}):

names.ForEach(name =>
{

});

这扩展为:

public void ForEach(Action<T> action)
{
  if (action == null)
    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
  int version = this._version;
  for (int index = 0; index < this._size && (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5); ++index)
    action(this._items[index]);
  if (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
    return;
  ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}

值得注意的是:

For (int index = 0;索引< this。_size &&…; + +指数) 操作(this._items(指数));

这段代码没有分配任何枚举数(没有要Dispose的枚举数),并且在迭代时没有暂停。

注意,这也执行底层集合的浅拷贝,但是集合现在是一个及时快照。如果作者没有正确地实现集合更改或“过时”的检查,快照仍然有效。

这并不能以任何方式保护你免受终身问题的困扰……如果底层的集合消失了,你现在就有了一个浅拷贝,它指向…但至少你不需要处理孤立迭代器上的Dispose问题…

是的,我说的是迭代器……有时拥有国家是有利的。假设您想要维护类似于数据库游标的东西……也许多重foreach风格的Iterator<T>'s是可行的。我个人不喜欢这种设计风格,因为有太多的寿命问题,你依赖于你所依赖的集合的作者的好意(除非你真的从头开始写所有东西)。

总有第三个选择……

for (var i = 0; i < names.Count; i++)
{
    Console.WriteLine(names[i]);
}

它并不性感,但它有牙齿(向汤姆·克鲁斯和电影《公司》道歉)

这是你的选择,但现在你知道了,这是一个明智的选择。

你可以命名匿名委托:-)

你可以把第二个写成

someList.ForEach(s => s.ToUpper())

我更喜欢这个,而且节省了很多打字。

正如Joachim所说,并行性更容易应用于第二种形式。

ForEach函数是泛型类List的成员。

我已经创建了以下扩展来复制内部代码:

public static class MyExtension<T>
    {
        public static void MyForEach(this IEnumerable<T> collection, Action<T> action)
        {
            foreach (T item in collection)
                action.Invoke(item);
        }
    }

因此,最后我们使用普通的foreach(如果你愿意,也可以使用循环for)。

另一方面,使用委托函数只是定义函数的另一种方式,以下代码:

delegate(string s) {
    <process the string>
}

等价于:

private static void myFunction(string s, <other variables...>)
{
   <process the string>
}

或者使用labda表达式:

(s) => <process the string>

在幕后,匿名委托被转换为一个实际的方法,因此如果编译器没有选择内联函数,那么第二个选择可能会有一些开销。此外,匿名委托示例主体引用的任何局部变量在本质上都会发生变化,因为编译器会使用一些技巧来隐藏它被编译为新方法的事实。这里有更多关于c#如何做到这一点的信息:

http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx