和原来的问题没有直接关系,但是经过上面的讨论,我对循环性能做了一些实验。
这是一个测试类。它设置了一个包含 10000 个整数的数组,然后使用不同的循环将所有整数连接成一个字符串。
[TestFixture]
class LinqPerformanceTest
{
private List<int> m_intList;
[SetUp]
public void SetUp()
{
m_intList = new List<int>();
for (int i = 0; i < 10000; i++)
{
m_intList.Add(i);
}
}
[Test]
[Repeat(5000)]
public void LoopWithForeach()
{
StringBuilder b = new StringBuilder();
foreach (int v in m_intList)
{
b.Append(v.ToString(CultureInfo.InvariantCulture));
}
}
[Test]
[Repeat(5000)]
public void LoopWithFor()
{
StringBuilder b = new StringBuilder();
for (int j = 0; j < m_intList.Count; j++)
{
int v = m_intList[j];
b.Append(v.ToString(CultureInfo.InvariantCulture));
}
}
[Test]
[Repeat(5000)]
public void LoopWithLinq()
{
StringBuilder b = new StringBuilder();
for (int j = 0; j < m_intList.Count(); j++)
{
int v = m_intList.ElementAt(j);
b.Append(v.ToString(CultureInfo.InvariantCulture));
}
}
[Test]
[Repeat(100)]
public void LoopWithLinq2()
{
StringBuilder b = new StringBuilder();
var myEnumerator = new MyEnumerableWrapper<int>(m_intList);
for (int j = 0; j < myEnumerator.Count(); j++)
{
int v = myEnumerator.ElementAt(j);
b.Append(v.ToString(CultureInfo.InvariantCulture));
}
}
private class MyEnumerableWrapper<T> : IEnumerable<T>
{
private readonly IEnumerable<T> m_innerEnum;
public MyEnumerableWrapper(IEnumerable<T> innerEnum)
{
m_innerEnum = innerEnum;
}
public IEnumerator<T> GetEnumerator()
{
return m_innerEnum.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
前两个解决方案 (LoopWithForeach()
和LoopWithFor()
) 的时间相同(在我的计算机上大约 7 秒),在 foreach 循环上有一点优势。
LoopWithLinq
证明 Count() 和 ElementAt() Linq 函数确实在可能的情况下使用了快捷方式。时间是可比的,但仍然比前两次尝试慢一点(大约 8 秒)。尽管 Linq 进行了快捷评估以发现底层对象实际上是一个列表并且 Count() 是一个 O(1) 操作,但由于需要额外的强制转换,因此成本更高。
在最后一个实现中LoopWithLinq2
,我特意编写了一个不支持直接索引的枚举器,这绝对是天方夜谭。仅 100 次迭代就需要 91 秒,比它慢了 500 多倍!
底线:出于性能原因,尽可能使用您拥有的特定接口。接口变得越通用,直接访问相关数据的方法就越少。