• 注意使用Linq延迟执行特性带来的性能问题
  • 发布于 2个月前
  • 213 热度
    0 评论
当我们使用 Linq 时,我们需要谨慎处理它的延迟执行特性,以免导致性能问题。

问题
例如,我们可能会写出以下代码:
public class TestClass  
{  
    // 堆代码 duidaima.com
    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,避免性能瓶颈,从而写出更高效和优秀的代码。
用户评论