什么时候用哪个?

编辑其他选项和下面稍微扩展的问题。

考虑一个类体的这个人为的抽象例子。 它演示了执行“for”迭代的四种不同方法。

private abstract class SomeClass { public void someAction(); } void Examples() { List someList = new List(); //A. for for (int i = 0; i  o.someAction()); //D. plinq someList.AsParallel().ForAll(o => o.someAction()); 

编辑:从答案和研究中添加一些选项。

  //E. ParallelEnumerable ParallelEnumerable.Range(0, someList.Count - 1) .ForAll(i => someList[i].someAction()); //F. ForEach Parallel Extension Parallel.ForEach(someList, o => o.someAction()); //G. For Parallel Extension Parallel.For(0, someList.Count - 1, i => someList[i].someAction()) } 

我的问题分为两部分。 我错过了一些重要的选择吗? 考虑可读性但主要是性能,哪个选项是最佳选择?

请指出SomeClass实现的复杂性或someListCount someList会影响此选择。

编辑:有这么令人眼花缭乱的选项,我不希望我的代码被选择破坏。 要在我的问题中添加第三部分,如果我的列表可以是任何长度,我应该默认为并行选项吗?

作为一个稻草人。 我怀疑在SomeClass所有实现和someList选项的所有长度//E. ParallelEnumerable 鉴于多处理器架构的普遍性, //E. ParallelEnumerable将提供最佳的平均性能。 我没有做任何测试来certificate这一点。

注意:并行扩展将需要使用System.Threading.Tasks命名空间。

选项A仅对实现索引的序列有意义,并且只对具有O(1)查找时间的序列执行。 一般来说,我会使用foreach和变体,除非你有特殊的逻辑。

另请注意,类似于for (int i = 1; i < list.Count; i++) “特殊逻辑”可以使用Linq扩展方法实现: foreach(var item in sequence.Skip(1))

所以,一般比B更喜欢B.

对于C:如果他们不习惯function风格,这可能会让其他开发人员感到困惑。

至于D:这取决于很多因素。 我想对于简单的计算,你不想这样做 - 如果循环体需要一段时间来计算,你只会真正受益于并行化。

你错过了:

 Parallel.ForEach(someList, o => o.someAction()) Parallel.For(0, someList.Length, i => someList[i].someAction()) 

IL告诉我们for循环是最有效的。 没有状态机可以担心。

用于产生以下内容

 IL_0036: br.s IL_0048 IL_0038: ldloc.0 IL_0039: ldloc.1 IL_003A: callvirt System.Collections.Generic.List.get_Item IL_003F: callvirt UserQuery+SomeClass.someAction IL_0044: ldloc.1 IL_0045: ldc.i4.1 IL_0046: add IL_0047: stloc.1 IL_0048: ldloc.1 IL_0049: ldloc.0 IL_004A: call System.Linq.Enumerable.Count IL_004F: blt.s IL_0038 

IL_0051:ret

这里为foreach生成的IL显示了状态机在工作。 LINQ版本和ForEach产生类似的输出。

 IL_0035: callvirt System.Collections.Generic.List.GetEnumerator IL_003A: stloc.3 IL_003B: br.s IL_004B IL_003D: ldloca.s 03 IL_003F: call System.Collections.Generic.List.get_Current IL_0044: stloc.1 IL_0045: ldloc.1 IL_0046: callvirt UserQuery+SomeClass.someAction IL_004B: ldloca.s 03 IL_004D: call System.Collections.Generic.List.MoveNext IL_0052: brtrue.s IL_003D IL_0054: leave.s IL_0064 IL_0056: ldloca.s 03 IL_0058: constrained. System.Collections.Generic.List<>.Enumerator IL_005E: callvirt System.IDisposable.Dispose IL_0063: endfinally IL_0064: ret 

我没有做过任何测试,但我认为这是一个安全的假设。

话虽这么说,但并不意味着应始终使用关键字。 这一切都取决于你的风格,你的团队风格,或者如果那段代码你的写作确实需要每个CPU周期,你可以得到你的手。

我不认为我会将AsParallel()与for,foreach或lambda等价物进行比较。 你使用AsParallel()拆分CPU密集型任务或阻塞操作,你不会只是迭代“普通”集合。

就性能而言,我认为其中一种效果最好。

  //A. for for (int i = 0; i < someList.Count(); i++) { someList[i].someAction(); } 

要么

  //D. plinq someList.AsParallel().ForAll(o => o.someAction()); 

虽然在A的情况下,我宁愿不每次都做someList.Count()。

就性能而言,与foreach相比表现更好。 D可以比A好,但它取决于场景。 如果你在某些列表中有一些大数据,Parallelism可能有所帮助,但如果你有小数据,它可能会造成额外的负担

一般来说,我选择的逻辑上与我正在做的事情相符。 如果我循环遍历整个列表,则所有使用foreach但如果我循环遍历子集,则使用for循环。 此外,如果要在循环中修改集合,则必须使用for循环。

我所知道的唯一另一个尚未说明的选项是手动执行foreach正在执行的操作,如果您需要将枚举数的状态维持在其创建范围之外,这将非常有用。

 using(var myEnum = aList.GetEnumerator()){ while(myEnum.MoveNext()){ myEnum.Current.SomeAction(); } } 

for(int i = 0...)要使用此方法,您必须拥有一个可以逐个访问每个元素的数组。

foreach (SomeClass o in someList)此语法可用于可枚举类,即实现IEnumerable的类。 IEnumerable有一个方法GetEnumerator() ,它知道如何遍历集合的每个元素。 现在,DOES上面的数组实现了IEnumerable 。 它知道如何枚举集合的方式是你如何定义它。 但是,并非所有可以使用foreach语法的IEnumerable类都可以使用第一种方法,因为并非所有集合都提供对每个元素的访问。 考虑以下函数(没有测试它):

 public IEnumerable GetACoupleOfInts() { yield return 1; yield return 2; } 

}

此方法将允许您使用foreach构造,因为运行时知道如何枚举GetACoupleInts()的值,但不允许for构造。

someList.ForEach(o => o.someAction()); – 我理解它的方式,这个lambda将被转换为与foreach (SomeClass o in someList)相同的表达式foreach (SomeClass o in someList)

someList.AsParallel().ForAll(o => o.someAction()); – 在决定是否使用PLINQ时,您必须决定“果汁是否值得挤压”。 如果someAction()中的someAction()是微不足道的,那么运行时试图组织并发操作中的所有数据的开销将是太多了,你最好连续执行它。

tl; dr – 前三个可能会导致相同的调用并且对性能没有实际影响,尽管它们在框架内具有不同的含义。 第四种选择在使用前需要更多考虑。

除了(C),它似乎向后看,我可以想到你可能想要使用其他每一个的情况。 另外,根据你正在做的事情,你也可以将标准的LINQ投入到混音中。 例如,如果您的循环只使用列表项来创建其他对象。

  (E) var someOtherCollection = someList.Select( l => transform(l) ); 

对于选项(A),如果您需要知道列表中的位置以及使用该项目。 选项(B)或(E)将是我通常使用的。 如果列表很大并且操作可以并行化(项目之间没有或可管理的依赖性),则选项(D)是有意义的。

除了(E)是O(N)之外,您使用的是通用列表。 Count()应该是O(1)操作,因为它在内部保存在变量中。 在其他可枚举类型上,您需要知道如何构造数据结构。 如果你不知道集合的类型,我会在索引实现上使用foreach实现或LINQ,因为集合可能没有索引,并且可能会将枚举转换为O(N 2 )操作。