我很好奇执行时,特别是在更新数据和第二次调用时。是否在使用查询变量时,例如在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();
答案 0 :(得分:6)
Linq延迟分析,直到迭代序列,通过Foreach语句或获取迭代器。请注意,在.ToArray()和.ToList调用下,执行此类迭代。你可以通过使用方法调用版本并按F9断点传入的lambda来看到这一点。
var posNums = nums
.Where(n => n > 0);
请注意,因为Linq函数创建枚举器,所以每次迭代序列时它们也会重新评估所有查询函数,因此如果要执行多个,使用.ToArray()将集合复制到内存通常是有利的。 (或嵌套!)迭代查询的结果。如果您想对更改的数据 source 执行多次迭代,那么您将重复使用相同的Linq结果。
如果您有点好奇,还可以在Reference Source
查看.NET框架用于各种Linq语句的源代码答案 1 :(得分:3)
每次迭代foreach的结果时,都会执行posNums
查询。
查看此操作的简单方法是在查询中引入副作用。编译器将您的查询表达式转换为:
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
答案 2 :(得分:0)
查看完全正确的最简单方法是实际构建与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
时,可枚举开始从基础可枚举中提取值,并且当您有MoveNext
循环(以及其他可能性)时调用foreach
。
我们在此处可以看到的其他内容是,每当我们致电foreach
时,我们再次致电GetEnumerator
,这会获得全新的枚举器形式Where
,它从底层源序列中获取一个全新的枚举器。这意味着每次调用foreach
时,您都会从一开始就再次迭代基础序列。