LinQ在C#中,为什么查询语句必须转换为tolist()?

LinQ in C#, why does query sentence must convert to tolist()?

我有以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Program
{
    private static List<Member> members = new List<Member>
    {
        new Member() {m_FirstName ="Chiristopher", m_LastName="Harrison"},
        new Member() {m_FirstName ="Jeremy", m_LastName = "Foster"},
        new Member() {m_FirstName ="Stacey", m_LastName ="Mulcahy"}
    };
    static void Main(string[] args)
    {
        var result = (from m in members
                     where m.m_LastName.Length > 6
                     select m);


        Console.WriteLine("** First Execution **");

        foreach(var member in result)
        {

            Console.WriteLine(member);
        }

        members.Add(new Member() { m_FirstName ="Rian", m_LastName ="ThyTuhfadsl" });

        Console.WriteLine("** Second Execution **");

        foreach (var member in result)
        {
            Console.WriteLine(member);
        }
    }
}

当我建造它的时候:慰问:**首次执行**克里斯托弗·哈里森斯泰西穆卡西**第二次执行**克里斯托弗·哈里森斯泰西穆卡西里安·泰图法兹尔

如果我调试,结果变量的第一个值是3,但它只打印两个值。为什么?

第二个是四个值。不过,我没有重新输入任何代码

如果我打字

1
2
3
var result = (from m in members
                     where m.m_LastName.Length > 6
                     select m).toList();

在任何情况下都只有两个值。


LINQ查询可以是几种类型,如.NET中所定义的:

  • 用于Linq to Objects的IEnumerable
  • 用于Linq to SQL的IQueryable
  • 用于PLINQ的ParallelQuery
  • 等等…

在您的例子中,您使用的是linq to对象,所以result变量的类型是IEnumerable。这被定义为一个可枚举序列,在您实际枚举它之前,它不必具体化。这就是这里发生的事情。请看我的答案,在这里我详细阐述了一些关于可枚举的内容。

我们说查询的计算比较慢。第二次枚举它时,它将被重新评估。如果您调用.ToList(),您将只实现一次查询并得到一个列表,这将在两个枚举中产生相同的结果。


因为LINQ查询的计算比较慢。大多数LINQ查询方法返回一个Iterator变量。该迭代器只有在使用foreach调用某些方法(如ToArrayToList迭代结果并将其添加到列表中)时才执行。


if I debug, result variable has 3 values at the first,

除了其他人所说的,如果我正确理解你的问题,你所说的3个结果值指的是source属性,因为它只是

1
2
3
4
5
  private class WhereListIterator<TSource> : Enumerable.Iterator<TSource>
    {
      private List<TSource> source;
      private Func<TSource, bool> predicate;
      private List<TSource>.Enumerator enumerator;

所以你的代码是正确的

enter image description here


在第一个代码示例中,您枚举原始列表两次。

但是,当您用最后一个代码示例替换LINQ查询时,您将复制原始序列。linq方法tolist将序列的副本复制到List实例中。因此,在本例中,您将枚举序列的副本两次,同时更改了中间的原始列表。


这与LINQ查询的延迟评估有关。如果在LINQ查询之后编写ToList(),它将立即得到评估并具体化。如果不编写ToList(),则只有在真正需要查询时才对其进行评估。


这是"懒惰执行"的一部分,您可以在线搜索。

直到需要时才执行查询。(仅当您迭代它时)。

".tolist()"命令强制执行查询。