代码之家  ›  专栏  ›  技术社区  ›  kling

LINQ生成的IEnumerable与原始列表之间的关系是什么?[副本]

  •  0
  • kling  · 技术社区  · 1 年前

    我有以下代码行:

    var list = new List<Test>() { new Test("Test1"), new Test("Test2") };
    var enumerable = list.Where(t => t.Content == "Test1");
    
    Console.WriteLine($"Enumerable count: {enumerable.Count()}");
    Console.WriteLine($"List count: {list.Count}");
    
    list.RemoveAll(t => t.Content == "Test1");
    
    Console.WriteLine($"Enumerable count: {enumerable.Count()}");
    Console.WriteLine($"List count: {list.Count}");
    

    我希望输出是

    Enumerable count: 1
    List count: 2
    Enumerable count: 1
    List count: 1
    

    但事实上,输出是

    Enumerable count: 1
    List count: 2
    Enumerable count: 0
    List count: 1
    

    这意味着从列表中删除对象,同时也将其从IEnumerable中删除。我认为我对面向对象编程有相当牢固的理解,但这种行为似乎出乎我的意料。

    有人能解释幕后发生了什么吗?如果我加上 .ToList() 至原件 Where -声明,一切如我所料。

    编辑:问题结束时引用了一个关于列表和IEnumerable之间区别的问题。这与这个问题的进展完全无关。这里的问题是IEnumerable是对LINQ查询本身的引用,而不是它自己的集合。

    1 回复  |  直到 1 年前
        1
  •  4
  •   Guru Stron    1 年前

    LINQ是懒惰的,实际执行是 deferred 直到调用一个可实现的操作( Count , foreach , ToList , First 等)。并且将为每个此类操作枚举整个可枚举项。这很容易观察到副作用:

    var enumerable = list
        .Where(t =>
        {
            Console.WriteLine("Test deffered: " + t.Content);
            return t.Content == "Test1";
        });
    

    因此,在这种情况下,您将对整个列表执行两次枚举( enumerable.Count() )但在枚举之间,列表已更改,因此您可以看到效果。

    这种惰性实际上在很多情况下非常有用——例如,当以动态方式构建查询(通过实体框架为数据库)时:

    var query = context.Something.AsQueriable();
    
    if(filter.Name is not null)
    {
       query = query.Where(s => s.Name == filter.Name);
    }
    ...
    

    或者重用查询以获取不同的结果:

    var query = ...;
    var total = await query.CountAsync();
    
    var page = await query.Skip(page*size).Take(size).ToListAsync();