如果更改 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 渲染页面的成本很容易翻倍或翻三倍。
给定以下代码:
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 渲染页面的成本很容易翻倍或翻三倍。