有人知道为什么在以下代码中离开while循环主体后的长度为零吗?请不要向我提供分块算法,我已经知道几种这样的算法,并且我知道如何解决此问题。我的问题只是关于RemoverRange或Take的有线行为。
static void Main(string[] args)
{
var list = new List<int>();
for (int i = 0; i < 1000; i++)
{
list.Add(i);
}
var chunks = new List<IEnumerable<int>>();
while (list.Count > 0)
{
chunks.Add(list.Take(10));
list.RemoveRange(0, 10);
}
int length = chunks.ToList()[0].Count();
}
答案 0 :(得分:3)
在以下行:
var chunks = new List<IEnumerable<int>>();
您创建一个Count
为0的列表,因为列表中没有任何项目。然后在每个步骤的foreach
语句中添加list.Take(10)
,然后从list
中删除前10个项目。 重要在这里是要认识到list.Take(10)
是懒惰求值的(您经常会听到术语延迟执行)。第一次评估的时间在以下行:
int length = chunks.ToList()[0].Count();
将在此行list.Take(10)
进行评估,并且由于您已删除了list
中的所有项目,因此list
中没有任何元素。因此,list.Take(10)
返回一个空序列,因此chunks.ToList()[0]
将是一个空列表。
更新 延迟执行说明:
让我们列出以下列表:
var list = new List<int> {1, 2, 3, 4, 5 };
var firstThree = list.Take(3);
变量firstThree
保存着对枚举数的引用-特别是IEnumerable<int>
。它不包含数组或1,2,3的列表。首次使用此迭代器时,您将开始从list
中“获取”数据。
例如,您可以调用ToList
或ToArray
方法:
var firstThreeList = firstThree.ToList();
var firstThreeArray = firstThree.ToArray();
以上两个调用都将使迭代器起作用-一般而言,这将强制在LINQ中执行查询-。此时,您将遍历list
并获取前三个项目。
话虽如此,很显然,如果您同时通过删除list
中的所有数字来修改iist
,则list
中将没有任何元素你什么也得不到。
作为测试,我建议您一次运行上述代码,然后再次运行但,在进行该调用的ToList
和ToArray
之前:
list.RemoveAll(x => true);
您现在会注意到firstThreeArray
和firstThreeList
都是空的。
答案 1 :(得分:1)
您遇到的问题是LINQ有点像视图。实际上,只有当您调用要求其产生特定值(First()
,Last()
,Count()
等)的方法时,它才会对集合进行迭代。因此,仅在调用这些方法之一时评估列表。
chunks.Add(list.Take(10));
此代码有效地表示“引用list
,并且当有人迭代您时,只能进行前10个项目”。要解决此问题,您可以将该小部分转换为列表(评估这10个项目,然后从中创建一个新列表):
chunks.Add(list.Take(10).ToList());
考虑以下代码:
List<string> names = new List<string>() { "a", "b", "c" };
IEnumerable<string> skip2 = names.Skip(2);
Console.WriteLine(string.Join(", ", skip2)); // "c"
names.Add("d");
names.Add("e");
Console.WriteLine(string.Join(", ", skip2)); // "c, d, e"
因为每次调用IEnumerable<string> skip2
时都使用迭代器(string.Join(", ", skip2)
),所以即使列表已更改,每次也会遍历列表。
这样,您将在第一次运行中获得"c"
,在第二次运行中获得"c, d, e"
。
实际上,这将是完全有效的(尽管很难阅读):
List<int> list = new List<int>();
IEnumerable<int> values = list.DefaultIfEmpty(0);
list.Add(5);
list.Add(10);
list.Add(15);
Console.WriteLine(values.Average()); // 10
答案 2 :(得分:1)
行
chunks.Add(list.Take(10));
实际上并没有从list
中提取10个项目,它只是告诉第一次引用chunks[i]
时要提取10个项目(例如,通过Count()
)。由于您要更改列表,因此参考指向空列表。您可以使用
chunks.Add(list.Take(10).ToList());
答案 3 :(得分:0)
根本没有怪异的行为。这正是IEnumerable
的预期行为。您应该记住的是IEnumerable
是惰性计算的,这意味着它是在枚举时(即您实际访问所说的IEnumerable
时)进行评估的。你在做什么基本上是→
①获取对list
对象的引用
②准备采用上述list
对象的前10个元素,还没有评估!
③将not yet evaluated
对象,在这种情况下→(LINQ.Take(10))
添加到chunks
列表中。
④删除list
的前10个元素
⑤漂洗并重复操作,直到list
中的所有项目都没有了
⑥创建一个列表,该列表全部由not yet evaluated
的{{1}}个项目组成。
⑦您采用了上述list.Take(10)
的第一个元素chunks
,但引用了not yet evaluate
的前10个元素,为空!!!
⑧您在IEnumerable实例上调用list
,该实例最终将评估一个完整的Count
的前十个元素