LINQ何时执行更新的数据列表?

时间:2014-05-23 19:06:28

标签: c# linq

我很好奇执行时,特别是在更新数据和第二次调用时。是否在使用查询变量时,例如在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(); 

3 个答案:

答案 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时,您都会从一开始就再次迭代基础序列。