迭代强类型泛型 List<T> 的最佳方法是啥?
Posted
技术标签:
【中文标题】迭代强类型泛型 List<T> 的最佳方法是啥?【英文标题】:What is the best way to iterate through a strongly-typed generic List<T>?迭代强类型泛型 List<T> 的最佳方法是什么? 【发布时间】:2010-09-06 02:59:08 【问题描述】:在 C#.NET 和 VB.NET 中迭代强类型泛型 List 的最佳方法是什么?
【问题讨论】:
【参考方案1】:对于 C#:
foreach(ObjectType objectItem in objectTypeList)
// ...do some stuff
Purple Ant 对 VB.NET 的回答:
For Each objectItem as ObjectType in objectTypeList
'Do some stuff '
Next
【讨论】:
因为它是强类型的,你也可以使用:foreach(var item in itemlist)
.【参考方案2】:
对于任何通用的 IEnumerable 实现,最好的方法是:
//C#
foreach( var item in listVariable)
//do stuff
但是有一个重要的例外。 IEnumerable 涉及 Current() 和 MoveNext() 的开销,而这正是 foreach 循环实际编译到的内容。
当你有一个简单的结构数组时:
//C#
int[] valueTypeArray;
for(int i=0; i < valueTypeArray.Length; ++i)
int item = valueTypeArray[i];
//do stuff
更快。
更新
在与@Steven Sudit 讨论之后(参见 cmets),我认为我最初的建议可能已经过时或错误,所以我进行了一些测试:
// create a list to test with
var theList = Enumerable.Range(0, 100000000).ToList();
// time foreach
var sw = Stopwatch.StartNew();
foreach (var item in theList)
int inLoop = item;
Console.WriteLine("list foreach: " + sw.Elapsed.ToString());
sw.Reset();
sw.Start();
// time for
int cnt = theList.Count;
for (int i = 0; i < cnt; i++)
int inLoop = theList[i];
Console.WriteLine("list for : " + sw.Elapsed.ToString());
// now run the same tests, but with an array
var theArray = theList.ToArray();
sw.Reset();
sw.Start();
foreach (var item in theArray)
int inLoop = item;
Console.WriteLine("array foreach: " + sw.Elapsed.ToString());
sw.Reset();
sw.Start();
// time for
cnt = theArray.Length;
for (int i = 0; i < cnt; i++)
int inLoop = theArray[i];
Console.WriteLine("array for : " + sw.Elapsed.ToString());
Console.ReadKey();
所以,我在发布时进行了所有优化:
list foreach: 00:00:00.5137506
list for : 00:00:00.2417709
array foreach: 00:00:00.1085653
array for : 00:00:00.0954890
然后在没有优化的情况下进行调试:
list foreach: 00:00:01.1289015
list for : 00:00:00.9945345
array foreach: 00:00:00.6405422
array for : 00:00:00.4913245
所以看起来相当一致,for
比 foreach
快,并且数组比通用列表快。
但是,这是经过 100,000,000 次迭代,最快和最慢方法之间的差异约为 0.4 秒。除非您正在执行大规模的性能关键循环,否则不值得担心。
【讨论】:
问题是关于List<T>
,而不是数组。无论如何,我不相信原生数组上的foreach
实际上使用IEnumerable
,至少在优化代码中没有,所以没有真正的加速。事实上,我的测试显示foreach
只花费了for
的四分之三时间。
@Steven Sudit - 在应用编译器优化后,foreach
循环实际上比for
循环更受苦。然而,我做了一些挖掘——在数组foreach
的情况下,优化为与for
完全相同的IL。对于List<T>
,编译器无法使用此优化,并且生成的 IL 稍慢。看来我们都错了(._.)
我不只是从理论上讲。我写了一个简短的测试并运行它。如果您愿意,我很乐意将其挖掘并发布,以便您了解它是如何为您工作的。
@Steven Sudit - 是的,我也是。我已经用结果更新了答案。
感谢您的辛勤工作,但我应该提到我的测试有一个关键区别:我确保循环将每个元素传递给外部方法。如果没有这个,编译器很可能会完全优化掉赋值。我不知道这是否会导致微小的速度提升,但它很容易做到。【参考方案3】:
C#
myList<string>().ForEach(
delegate(string name)
Console.WriteLine(name);
);
匿名委托目前在 VB.Net 中未实现,但 C# 和 VB.Net 都应该能够执行 lambda:
C#
myList<string>().ForEach(name => Console.WriteLine(name));
VB.Net
myList(Of String)().ForEach(Function(name) Console.WriteLine(name))
正如 Grauenwolf 指出的,上述 VB 无法编译,因为 lambda 不返回值。其他人建议的普通 ForEach 循环现在可能是最简单的,但像往常一样,它需要一段代码才能完成 C# 可以在一行中完成的事情。
这是一个老生常谈的例子,说明为什么这可能有用:这使您能够从另一个范围传入循环逻辑,而不是 IEnumerable 存在的地方,因此如果您不想要,您甚至不必公开它到。
假设您有一个要设为绝对的相对 url 路径列表:
public IEnumerable<String> Paths(Func<String> formatter)
List<String> paths = new List<String>()
"/about", "/contact", "/services"
;
return paths.ForEach(formatter);
那么你可以这样调用函数:
var hostname = "myhost.com";
var formatter = f => String.Format("http://01", hostname, f);
IEnumerable<String> absolutePaths = Paths(formatter);
给你"http://myhost.com/about", "http://myhost.com/contact"
等。显然在这个特定的例子中有更好的方法来实现这一点,我只是想演示基本原理。
【讨论】:
您的 VB 代码不起作用。 VB这个版本只支持匿名函数,要等到VB10才有匿名子程序。 你说得对,发帖前我没测试过。难怪 VB 中关于 lambda 表达式的文档如此稀缺。它们几乎没有那么有用。 显而易见的问题:当您可以执行foreach
时,为什么还要打扰回调?
您假设 IEnumerable 和委托来自同一范围,但尽管我的示例很简单,但它们不一定是。能够将闭包从另一个作用域传递给函数可能是一种非常强大的设计模式。
也许举个例子可能会有所帮助。请以“@Steve”开始您的帖子,以便我收到通知。【参考方案4】:
对于 VB.NET:
For Each tmpObject as ObjectType in ObjectTypeList
'Do some stuff '
Next
【讨论】:
【参考方案5】:在不了解列表的内部实现的情况下,我认为通常迭代它的最佳方法是 foreach 循环。因为 foreach 使用 IEnumerator 遍历列表,所以由列表本身决定如何从一个对象移动到另一个对象。
如果内部实现是链表,那么简单的 for 循环会比 foreach 慢很多。
这有意义吗?
【讨论】:
是的,因为链表需要对每个新项目进行线性搜索,而对其进行迭代只需一次。【参考方案6】:这取决于您的应用程序:
for 循环,如果优先考虑效率 foreach 循环或 ForEach 方法,以更清楚地传达您的意图为准【讨论】:
foreach 转换为 GetEnumerator()、MoveNext() 和 Current() 调用,这显然比递增索引器和从数组中选择要慢。不过,我们说的是纳秒级,通常没有任何问题。 "for (index = objectListType.Count - 1; count >= 0; --count) /* ... */ " 在我看来是最好的方法,原因之一。它允许您在迭代时删除项目。如果您决定在循环时从 objectListType "objectListType.RemoveAt(index)" 中删除一个项目,您将不会遇到任何奇怪或 Modification-While-Enumeration 异常。性能很好。【参考方案7】:我可能遗漏了一些东西,但是如果您使用下面的示例,遍历通用列表应该相当简单。 List 类实现了 IList 和 IEnumerable 接口,因此您可以轻松地以任何您想要的方式遍历它们。
最有效的方法是使用 for 循环:
for(int i = 0; i < genericList.Count; ++i)
// Loop body
您也可以选择使用 foreach 循环:
foreach(<insertTypeHere> o in genericList)
// Loop body
【讨论】:
不需要<insertTypeHere>
。编译器使用var
为你做这件事。以上是关于迭代强类型泛型 List<T> 的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章