迭代强类型泛型 List<T> 的最佳方法是什么?
-
08-06-2019 - |
题
在 C#.NET 和 VB.NET 中迭代强类型泛型列表的最佳方法是什么?
解决方案
对于 C#:
foreach(ObjectType objectItem in objectTypeList)
{
// ...do some stuff
}
VB.NET 的答案来自 紫蚂蚁:
For Each objectItem as ObjectType in objectTypeList
'Do some stuff '
Next
其他提示
对于 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讨论后(参见评论),我认为我最初的建议可能已经过时或错误,所以我进行了一些测试:
// 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 秒。除非您正在执行大量性能关键循环,否则不值得担心。
对于 VB.NET:
For Each tmpObject as ObjectType in ObjectTypeList
'Do some stuff '
Next
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网络
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://{0}{1}", hostname, f);
IEnumerable<String> absolutePaths = Paths(formatter);
给你 "http://myhost.com/about", "http://myhost.com/contact"
ETC。显然,在这个具体示例中有更好的方法来完成此任务,我只是想演示基本原理。
在不知道列表的内部实现的情况下,我认为迭代它的最佳方法通常是 foreach 循环。因为 foreach 使用 IEnumerator 遍历列表,所以由列表本身决定如何从一个对象移动到另一个对象。
如果内部实现是一个链表,那么简单的 for 循环将比 foreach 慢很多。
那有意义吗?
这取决于您的应用:
- for循环,如果效率是优先考虑的
- foreach 循环或 ForEach 方法,以更清楚地传达您的意图为准
我可能遗漏了一些东西,但是如果您使用下面的示例,则迭代通用列表应该相当简单。List<> 类实现了 IList 和 IEnumerable 接口,因此您基本上可以以任何您想要的方式轻松地迭代它们。
最有效的方法是使用 for 循环:
for(int i = 0; i < genericList.Count; ++i)
{
// Loop body
}
您还可以选择使用 foreach 循环:
foreach(<insertTypeHere> o in genericList)
{
// Loop body
}