LINQ何时执行更新的数据列表
本文关键字:数据 列表 更新 何时 执行 LINQ | 更新日期: 2023-09-27 18:04:54
我很好奇何时执行,尤其是在更新数据和第二次调用时。无论何时使用查询变量,例如在foreach语句中,都是这样吗?还是当我更新列表时,比如nums[1]=99?
int[] nums = { 1, -2, 3, 0, -4, 5 };
var posNums = from n in nums
where n > 0
select n;
foreach (int i in posNums)
Console.Write("" + i + " ");
Console.WriteLine();
nums[1] = 99;
foreach (int i in posNums)
Console.Write("" + i + " ");
Console.WriteLine();
Linq推迟分析,直到通过Foreach语句或获取迭代器迭代序列。请注意,在后台,.ToArray((和.ToList调用执行这样的迭代。您可以通过使用方法调用版本并按F9来中断传入的lambda来看到这一点。
var posNums = nums
.Where(n => n > 0);
请注意,因为Linq函数创建枚举器,所以每次迭代序列时,它们也会重新评估所有查询函数,因此,如果您想对查询的结果执行多次(或嵌套!(迭代,使用.ToArray((将集合复制到内存通常是有利的。如果您想对不断变化的数据源执行多次迭代,则需要重用相同的Linq结果。
如果你很好奇,你也可以在参考源
posNums
查询将在每次迭代foreach中的结果时执行。
一个简单的方法就是在查询中引入一个副作用。编译器将查询表达式转换为:
var posNums = nums.Where(n => n > 0);
我们可以通过更多的控制台输出来修改您的代码,并准确地查看执行情况:
int[] nums = { 1, -2, 3, 0, -4, 5 };
Console.WriteLine("Before query creation");
var posNums = nums.Where(n => { Console.WriteLine(" Evaluating " + n); return n > 0; });
Console.WriteLine("Before foreach 1");
foreach (int i in posNums)
Console.WriteLine(" Writing " + i);
Console.WriteLine("Before array modification");
nums[1] = 99;
Console.WriteLine("Before foreach 2");
foreach (int i in posNums)
Console.WriteLine(" Writing " + i);
输出为:
Before query creation
Before foreach 1
Evaluating 1
Writing 1
Evaluating -2
Evaluating 3
Writing 3
Evaluating 0
Evaluating -4
Evaluating 5
Writing 5
Before array modification
Before foreach 2
Evaluating 1
Writing 1
Evaluating 99
Writing 99
Evaluating 3
Writing 3
Evaluating 0
Evaluating -4
Evaluating 5
Writing 5
Where
返回的内容等效的东西,并逐步完成它。这里是一个功能等效于Where的实现(至少在源序列迭代和结果的位置(。
我省略了一些性能优化,以保持对重要内容的关注,并为清晰起见写下了一些"长期"操作:
public static IEnumerable<T> Where<T>(
this IEnumerable<T> source,
Func<T, bool> predicate)
{
return new WhereEnumerable<T>(source, predicate);
}
public class WhereEnumerable<T> : IEnumerable<T>
{
private IEnumerable<T> source;
private Func<T, bool> predicate;
public WhereEnumerable(IEnumerable<T> source, Func<T, bool> predicate)
{
this.source = source;
this.predicate = predicate;
}
public IEnumerator<T> GetEnumerator()
{
return new WhereEnumerator<T>(source.GetEnumerator(), predicate);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class WhereEnumerator<T> : IEnumerator<T>
{
private IEnumerator<T> source;
private Func<T, bool> predicate;
public WhereEnumerator(IEnumerator<T> source, Func<T, bool> predicate)
{
this.source = source;
this.predicate = predicate;
}
public T Current { get; private set; }
public void Dispose()
{
source.Dispose();
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
while (source.MoveNext())
if (predicate(source.Current))
{
Current = source.Current;
return true;
}
return false;
}
public void Reset()
{
throw new NotImplementedException();
}
}
同样值得参考的是,foreach
循环等效于什么:
foreach (int i in posNums)
Console.Write("" + i + " ");
相当于:
using(IEnumerator<int> iterator = posNums.GetEnumerator())
while(iterator.MoveNext())
{
int i = iterator.Current;
Console.Write("" + i + " ");
}
因此,现在您可以遍历并查看序列的值何时实际被提取。(我鼓励您使用调试器来遍历这段代码,在您自己的代码中使用这个Where
来代替LINQ的Where
,看看这里发生了什么。(
在序列上调用Where
完全不影响序列,序列的更改也完全不影响Where
的结果。当实际调用MoveNext
时,可枚举对象开始从底层可枚举对象中提取值,当有foreach
循环时调用MoveNext
(以及其他可能性(。
我们在这里可以看到的另一件事是,每次我们调用foreach
时,我们都会再次调用GetEnumerator
,它从Where
中获得一个全新的枚举器,它从底层源序列中获得了一个全新的枚举数。这意味着每次调用foreach
时,都会从一开始就再次迭代底层序列。