当我们使用 Linq 时,我们需要谨慎处理它的延迟执行特性,以免导致性能问题。
问题
例如,我们可能会写出以下代码:.
public class TestClass
{
public int Number { get; set; }
public TestClass(int i)
{
Console.WriteLine($"TestClass({i})");
Number = i;
}
}
IEnumerable<TestClass> enumerable = Enumerable.Range(0, 2).Select(i => new TestClass(i));
var result = Enumerable.Range(0, 3).Select(number =>
enumerable.Where(p => p.Number == number).Count()
).ToList();
输出结果如下:
TestClass(0)
TestClass(1)
TestClass(0)
TestClass(1)
TestClass(0)
TestClass(1)
输出结果表明,TestClass 的构造函数被调用了 6 次,而不是我们预期的 2 次。如果构造函数执行的是复杂的逻辑,那么性能问题就会更加严重。
原因
这个问题的原因在于,enumerable 中的每个元素并不是一个 TestClass 的实例,而是一个 new TestClass 的委托。在 foreach 循环中,每次都会调用委托,也就是调用委托中的构造函数。
等效代码如下:
//enumerable
List<Func<TestClass>> func1 = new List<Func<TestClass>>();
func1.Add(() => new TestClass(0));
func1.Add(() => new TestClass(1));
List<int> result2 = new List<int>();
for (int i = 0; i < 3; i++)
{
var count = 0;
foreach (var func in func1)
{
TestClass testClass = func();
if (testClass.Number == i)
{
count++;
}
}
result2.Add(count);
}
解决方案
解决这个问题的方法很简单,在调用 Linq 方法之前,将集合转换成 List 避免延迟执行,例如:
IEnumerable<TestClass> enumerable = Enumerable.Range(0, 2).Select(i => new TestClass(i)).ToList();
var result = Enumerable.Range(0, 3).Select(number =>
enumerable.Where(p => p.Number == number).Count()
).ToList();
总结
当我们在使用Linq时,需要注意延迟执行特性带来的性能问题,尤其是在处理复杂的逻辑时。
一些常见的解决方法包括使用ToList将集合转换成List类型、适时地调整Linq方法的顺序以优化性能。
了解这些技巧和方法可以帮助我们更好地使用Linq,避免性能瓶颈,从而写出更高效和优秀的代码。