使用Linq时的性能陷阱

当我们使用 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,避免性能瓶颈,从而写出更高效和优秀的代码。