Linq:执行顺序链查询

8
我希望了解链式查询的处理方式。例如,我们考虑以下查询:
var sumOfRoots = numbers           //IEnum0
     .Where(x => x > 0)            //IEnum1
     .Select(x => Math.Sqrt(x))    //IEnum2
     .Select(x => Math.Exp(x))     //IEnum3
     .Sum();

例如,numbers={-1, 4, 9 }.

这是背后发生的事情吗:

1. 获取所有枚举器(向前遍历)

  • numbers 调用 GetEnumerator(),返回 (我们称之为) IEnum0 实例
  • IEnum0 调用 GetEnumerator(),返回 IEnum1 实例
  • IEnum1 调用 GetEnumerator(),返回 IEnum2 实例
  • IEnum2 调用 GetEnumerator(),返回 IEnum3 实例

2. 调用 MoveNext (向后遍历)

  • .Sum()IEnum3 上调用 MoveNext()
  • IEnum3IEnum2 上调用 MoveNext()
  • IEnum2IEnum1 上调用 MoveNext()
  • IEnum1IEnum0 上调用 MoveNext()

3. 从 MoveNext 返回(向前-向后遍历)

  • IEnum0 移动到元素 -1 并返回 true
  • IEnum1 检查是否满足条件(这是不正确的),因此 IEnum1IEnum0 上调用 MoveNext()
  • IEnum0 移动到元素 4 并返回 true
  • IEnum1 检查是否满足条件(这是正确的)并返回 true
  • IEnum2 什么也不做,只返回 IEnum1 的输出,即 true
  • IEnum2 什么也不做,只返回 IEnum2 的输出,即 true

4. 调用 Current(向后遍历)

  • .Sum() 调用 IEnum3 上的 Current
  • IEnum3 调用 IEnum2 上的 Current
  • IEnum2 调用 IEnum1 上的 Current
  • IEnum1 调用 IEnum0 上的 Current

5. 返回 Current (正向传递)

  • IEnum0 返回 4
  • IEnum1 返回 4
  • IEnum2 返回 sqrt(4)=2
  • IEnum3 返回 exp(2)

6. 重复步骤2-5,直到步骤3返回false

如果链式查询有不同的处理方式,请纠正我。


每个链块内的过程都已完成。Where(x => x > 0) 过滤数字并将 {4,9} 留给下一个链块,如此循环进行。 - Cetin Basoz
你可以使用Visual Studio的“调试框架代码”功能,在调试时实时查看。或者,前往参考源,将方法复制到你的解决方案中并进行调试。 - dymanoid
大部分是正确的,但我从来没有想过像这样使用链式查询,因为LINQ的发明就是为了防止我们将数据管道视为循环/迭代器。 - qxg
@CetinBasoz,我不理解你的评论,每个块内部的处理如何工作?这对我来说似乎是不正确的。 - Dejan
1个回答

11

您可以使用委托来了解执行顺序。例如:

static void Main(string[] args)
{
    var numbers = new []{ -1, 4, 9 };

    double sumOfRoots = numbers.Where(IsGreaterThanZero)   
                               .Select(ToSquareRoot)      
                               .Select(ToExp)              
                               .Sum(x => ToNumber(x));

    Console.WriteLine($"sumOfRoots = {sumOfRoots}");

    Console.Read();
}

private static double ToNumber(double number)
{
    Console.WriteLine($"SumNumber({number})");

    return number;
}

private static double ToSquareRoot(int number)
{
    double value =  Math.Sqrt(number);

    Console.WriteLine($"Math.Sqrt({number}): {value}");

    return value;
}

private static double ToExp(double number)
{
    double value =  Math.Exp(number);

    Console.WriteLine($"Math.Exp({number}): {value}");

    return value;
}

private static bool IsGreaterThanZero(int number)
{
    bool isGreater = number > 0;

    Console.WriteLine($"{number} > 0: {isGreater}");

    return isGreater;
}

输出:

-1 > 0:假

4 > 0:真

Math.Sqrt(4):2

Math.Exp(2):7.38905609893065

SumNumber(7.38905609893065)

9 > 0:真

Math.Sqrt(9):3

Math.Exp(3):20.0855369231877

SumNumber(20.0855369231877)

sumOfRoots = 27.4745930221183


1
该死,我从未想过LINQ查询可以像这样链接,使用委托是个好主意。 - Haytam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接