如果更改 IEnumerable 数据源,它会更改结果

If IEnumerable data source is changed, it changes the results

给定以下代码:

using System.Linq;              
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        //Init data
        char[] chars = new char[10];
        FillData(chars);

        // Write the initial data
        PrintContents("Initial data:", chars);
        //Take some data:
        IEnumerable<char> acc = chars.Take(3);
        //View data
        PrintContents("Enum:", acc);

        //Edit data
        chars[0] = 'z';
        chars[1] = 'z';
        chars[2] = 'z';

        //View data again
        PrintContents("Enum after modifing source:", acc);

        //Restart data
        chars = new char[5];
        FillData(chars);

        //View data when source is replaced
        PrintContents("Enum after new source:", acc);
    }

    //Gets a ref
    private static void FillData(char[] data)
    {
        for(int i = 0; i < data.Length; i++)
        {
            data[i] = (char)('a' + i);
        }
    }

    private static void PrintContents(string what, IEnumerable<char> src)
    {
        System.Console.WriteLine(what);
        string s = "";
        foreach(char ch in src)
        {
            s += ch;
        }
        if(s.Length > 0)
        {
            System.Console.WriteLine(s);
        }
    }
}

我得到这个输出:

Initial data:
abcdefghij
Enum:
abc
Enum after modifing source:
zzz
Enum after new source:
zzz

我知道延迟执行,但这是预期的行为吗? 这意味着我应该在不创建新集合的情况下重用 IEnumerable 或 IEnumerable 上使用的任何数据,因为我可能会更改程序的结果。

这意味着 IEnumerable 也将保留对数据源的引用,即使它们也未被可见代码使用,并且在收集 IEnumerable 本身之前不会被垃圾收集。

我在最近的一个项目中经常使用 IEnumerable,我越看越不喜欢它们。不要误会我的意思,Linq 做得很好,但有时我更喜欢它 return 相同类型的源代码。

是的,这是预期的行为。

您应该将 LINQ 方法的结果视为 "compute result when I enumerate" 而不是 "collection of items"。对我来说,当我第二次枚举它时,它会再次计算结果,因为我遍历项目。

在源数据可能发生变化(如问题中的示例)或获取结果成本高昂(查询数据库是隐藏成本的常见情况)的情况下,这很重要。不幸的是,没有通用的方法来阐明枚举是昂贵的(即 DB)还是本质上免费的(即列表),这两种情况——重复查询实时数据或重复枚举缓存结果——都是常用的。 IQueryable 在某种程度上表明了昂贵的、惰性评估的可枚举但仅具有 IEnumerable 并不能说明 costly/up-to-date 结果如何。

关于您担心查询使数据源保持活动状态的时间可能比您预期的更长 - 是的,这是一个问题。您应该了解结果的预期用途是什么,并考虑 returning 非惰性结果是否更好(即 .ToList())。从一次性来源(数据库、文件和不可搜索的来源,如网络流)获取数据时要小心——通常更容易强制对查询和 return List(或任何其他非lazy) 集合来控制数据源的处理方式和时间。

例如,您应该强烈考虑将非惰性枚举传递给 ASP.Net MVC 视图 - 数据可能很容易迭代多次以呈现(甚至 .Count() 是一次迭代),因此惰性计算可枚举DB 渲染页面的成本很容易翻倍或翻三倍。