迭代Linq表达式结果而不是首先将它分配给变量是否相同?

时间:2016-03-11 22:10:53

标签: c# .net linq foreach

所以,用语言解释起来比较困难,所以我会把代码示例。 我想我已经有了一个我想要过滤的客户列表。

基本上我想知道是否:

foreach(var client in list.Where(c=>c.Age > 20))
{
  //Do something
}

与此相同:

var filteredClients =  list.Where(c=>c.Age > 20);

foreach(var client in filteredClients)
{
  //Do something
}

我被告知第一种方法在每次迭代中执行.Where()

如果这是重复的话,我很抱歉,我找不到任何相关的问题。

提前致谢。

2 个答案:

答案 0 :(得分:2)

是的,这两个例子在功能上是相同的。一个只是将Enumerable.Where的结果存储在一个变量中,然后再访问它,而另一个只是直接访问它。

要真正理解为什么这不会产生影响,您必须了解foreach循环本质上是做什么的。示例中的代码(两者都是)基本上等同于此(我假设这里有一个已知类型Client):

IEnumerable<Client> x = list.Where(c=>c.Age > 20);

// foreach loop
IEnumerator<Client> enumerator = x.GetEnumerator();
while (enumerator.MoveNext())
{
    Client client = enumerator.Current;
    // Do something
}

所以这里实际发生的是LINQ方法的IEnumerable结果不是直接消耗的,而是首先请求它的枚举器。然后foreach循环除了重复地从枚举器中请求一个新对象并处理每个循环体中的当前元素之外别无其他。

考虑到这一点,上述代码中的x是否真的是x(即先前存储的变量),或者它是list.Where()调用是没有意义的本身。只有枚举器对象 - 只创建一次 - 在循环中使用。

现在介绍科林发布的SharePoint example。它看起来像这样:

SPList activeList = SPContext.Current.List;
for (int i=0; i < activeList.Items.Count; i++)
{
    SPListItem listItem = activeList.Items[i];
    // do stuff
}

但这是一个根本不同的事情。由于这不是使用foreach循环,我们没有得到一个用于遍历列表的枚举器对象。相反,我们重复访问activeList.Items:一旦进入循环体,通过索引获取项目,并且一次进入for循环的延续条件,我们得到集合{ {1}}属性值。

不幸的是,Microsoft并没有始终遵循自己的指南,因此即使CountSPList对象上的属性,它实际上也是在创建一个新的{每次{3}}对象。默认情况下,该对象为空,只有在您第一次访问项目时才会懒惰地加载实际项目。因此,上面的代码最终会创建大量的Items,每个都会从数据库中获取项目。在属性文档的SPListItemCollection中也提到了这种行为。

在选择属性与方法时,这通常会违反remarks section

  

在以下情况下,请使用方法而不是属性。

     
      
  • 每次调用操作时,操作都会返回不同的结果,即使参数没有改变。
  •   

请注意,如果我们再次为该SharePoint示例使用SPListItemCollection循环,那么一切都会没问题,因为我们将再次只请求一个foreach并为其创建一个枚举器:

SPListItemCollection

答案 1 :(得分:1)

它们并不完全相同:

这是原始的C#代码:

static void ForWithVariable(IEnumerable<Person> clients)
{
    var adults = clients.Where(x => x.Age > 20);
    foreach (var client in adults)
    {
        Console.WriteLine(client.Age.ToString());
    }
}

static void ForWithoutVariable(IEnumerable<Person> clients)
{
    foreach (var client in clients.Where(x => x.Age > 20))
    {
        Console.WriteLine(client.Age.ToString());
    }
}

以下是此结果导致的反编译中间语言(IL)代码(根据ILSpy):

private static void ForWithVariable(IEnumerable<Person> clients)
{
    Func<Person, bool> arg_21_1;
    if ((arg_21_1 = Program.<>c.<>9__1_0) == null)
    {
        arg_21_1 = (Program.<>c.<>9__1_0 = new Func<Person, bool>(Program.<>c.<>9.<ForWithVariable>b__1_0));
    }
    IEnumerable<Person> enumerable = clients.Where(arg_21_1);
    foreach (Person current in enumerable)
    {
        Console.WriteLine(current.Age.ToString());
    }
}

private static void ForWithoutVariable(IEnumerable<Person> clients)
{
    Func<Person, bool> arg_22_1;
    if ((arg_22_1 = Program.<>c.<>9__2_0) == null)
    {
        arg_22_1 = (Program.<>c.<>9__2_0 = new Func<Person, bool>(Program.<>c.<>9.<ForWithoutVariable>b__2_0));
    }
    foreach (Person current in clients.Where(arg_22_1))
    {
        Console.WriteLine(current.Age.ToString());
    }
}

正如您所看到的,存在一个关键区别:

IEnumerable<Person> enumerable = clients.Where(arg_21_1);
然而,一个更实际的问题是差异是否会影响绩效。我编造了一个测试来测量它。

class Program
{
    public static void Main()
    {
        Measure(ForEachWithVariable);
        Measure(ForEachWithoutVariable);
        Console.ReadKey();
    }

    static void Measure(Action<List<Person>, List<Person>> action)
    {
        var clients = new[]
        {
            new Person { Age = 10 },
            new Person { Age = 20 },
            new Person { Age = 30 },
        }.ToList();
        var adultClients = new List<Person>();
        var sw = new Stopwatch();
        sw.Start();
        for (var i = 0; i < 1E6; i++)
            action(clients, adultClients);
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds.ToString());
        Console.WriteLine($"{adultClients.Count} adult clients found");
    }

    static void ForEachWithVariable(List<Person> clients, List<Person> adultClients)
    {
        var adults = clients.Where(x => x.Age > 20);
        foreach (var client in adults)
            adultClients.Add(client);
    }

    static void ForEachWithoutVariable(List<Person> clients, List<Person> adultClients)
    {
        foreach (var client in clients.Where(x => x.Age > 20))
            adultClients.Add(client);
    }
}

class Person
{
    public int Age { get; set; }
}

在多次运行程序后,我发现ForEachWithVariableForEachWithoutVariable之间没有任何显着差异。它们总是在时间上接近,并且都不会比另一个更快。有趣的是,如果我将1E6更改为1000ForEachWithVariable实际上一直更慢,大约1毫秒。

因此,我得出结论,对于 LINQ to Objects ,没有实际的区别。如果您的特定用例涉及LINQ to Entities(或SharePoint),则可以运行相同类型的测试。