简体   繁体   English

Linq后期绑定混乱

[英]Linq late binding confusion

Can someone please explain me what I am missing here. 有人可以向我解释一下我在这里缺少什么。 Based on my basic understanding linq result will be calculated when the result will be used and I can see that in following code. 根据我的基本理解,在使用结果时将计算linq结果,我可以在以下代码中看到它。

 static void Main(string[] args)
 {
     Action<IEnumerable<int>> print = (x) =>
     {
         foreach (int i in x)
         {
             Console.WriteLine(i);
         }
     };

     int[] arr = { 1, 2, 3, 4, 5 };
     int cutoff = 1;
     IEnumerable<int> result = arr.Where(x => x < cutoff);
     Console.WriteLine("First Print");
     cutoff = 3;
     print(result);
     Console.WriteLine("Second Print");
     cutoff = 4;
     print(result);
     Console.Read();
}

Output: 输出:

First Print
1
2
Second Print
1
2
3

Now I changed the 现在我改变了

arr.Where(x => x < cutoff); 

to

IEnumerable<int> result = arr.Take(cutoff); 

and the output is as follow. 输出如下。

First Print
1
Second Print
1

Why with Take, it does not use the current value of the variable? 为什么使用Take,它不使用变量的当前值?

The behavior your seeing comes from the different way in which the arguments to the LINQ functions are evaluated. 您看到的行为来自对LINQ函数的参数进行评估的不同方式。 The Where method recieves a lambda which captures the value cutoff by reference. Where方法接收一个lambda,该lambda通过引用捕获值cutoff值。 It is evaluated on demand and hence sees the value of cutoff at that time. 它是按需评估的,因此可以看到当时的cutoff值。

The Take method (and similar methods like Skip ) take an int parameter and hence cutoff is passed by value. Take方法(以及类似Skip类似方法)采用int参数,因此按值传递cutoff值。 The value used is the value of cutoff at the moment the Take method is called, not when the query is evaluated 使用的值是调用Take方法时的cutoff值,而不是评估查询时的cutoff

Note: The term late binding here is a bit incorrect. 注意:这里的术语“后期绑定”有点不正确。 Late binding generally refers to the process where the members an expression binds to are determined at runtime vs. compile time. 后期绑定通常是指表达式绑定到的成员是在运行时还是编译时确定的过程。 In C# you'd accomplish this with dynamic or reflection. 在C#中,您可以通过dynamic或反射来实现。 The behavior of LINQ to evaluate it's parts on demand is known as delayed execution. LINQ按需评估零件的行为称为延迟执行。

There's a few different things getting confused here. 这里有些不同的事情变得令人困惑。

Late-binding : This is where the meaning of code is determined after it was compiled. 后期绑定 :这是在编译后确定代码含义的地方。 For example, x.DoStuff() is early-bound if the compiler checks that objects of x 's type have a DoStuff() method (considering extension methods and default arguments too) and then produces the call to it in the code it outputs, or fails with a compiler error otherwise. 例如,如果编译器检查x类型的对象是否具有DoStuff()方法(也考虑扩展方法和默认参数),然后在其输出的代码中生成对其的调用,则x.DoStuff()是早期绑定的,否则将因编译器错误而失败。 It is late-bound if the search for the DoStuff() method is done at run-time and throws a run-time exception if there was no DoStuff() method. 如果用于搜索它是后期绑定DoStuff()方法在运行时完成,并抛出一个运行时异常,如果没有DoStuff()方法。 There are pros and cons to each, and C# is normally early-bound but has support for late-binding (most simply through dynamic but the more convoluted approaches involving reflection also count). 每种都有优点和缺点,C#通常是早期绑定的,但支持后期绑定(最简单的方法是通过dynamic但更复杂的方法也需要反射)。

Delayed execution : Strictly speaking, all Linq methods immediately produce a result. 延迟执行 :严格来说,所有Linq方法都会立即产生结果。 However, that result is an object which stores a reference to an enumerable object (often the result of the previous Linq method) which it will process in an appropriate manner when it is itself enumerated. 但是,该结果是一个对象,该对象存储对可枚举对象的引用(通常是以前的Linq方法的结果),当对其自身进行枚举时,它将以适当的方式进行处理。 For example, we can write our own Take method as: 例如,我们可以将自己的Take方法编写为:

private static IEnumerable<T> TakeHelper<T>(IEnumerable<T> source, int number)
{
  foreach(T item in source)
  {
    yield return item;
    if(--number == 0)
      yield break;
  }
}
public static IEnumerable<T> Take<T>(this IEnumerable<T> source, int number)
{
  if(source == null)
    throw new ArgumentNullException();
  if(number < 0)
    throw new ArgumentOutOfRangeException();
  if(number == 0)
    return Enumerable.Empty<T>();
  return TakeHelper(source, number);
}

Now, when we use it: 现在,当我们使用它时:

var taken4 = someEnumerable.Take(4);//taken4 has a value, so we've already done
                                    //something. If it was going to throw
                                    //an argument exception it would have done so
                                    //by now.

var firstTaken = taken4.First();//only now does the object in taken4
                                        //do the further processing that iterates
                                        //through someEnumerable.

Captured variables : Normally when we make use of a variable, we make use of how its current state: 捕获的变量 :通常,当我们使用变量时,我们利用其当前状态:

int i = 2;
string s = "abc";
Console.WriteLine(i);
Console.WriteLine(s);
i = 3;
s = "xyz";

It's pretty intuitive that this prints 2 and abc and not 3 and xyz . 这很直观,它显示2abc而不是3xyz In anonymous functions and lambda expressions though, when we make use of a variable we are "capturing" it as a variable, and so we will end up using the value it has when the delegate is invoked: 但是,在匿名函数和lambda表达式中,当我们使用变量时,会将其“捕获”为变量,因此最终将使用调用委托时具有的值:

int i = 2;
string s = "abc";
Action λ = () =>
{
  Console.WriteLine(i);
  Console.WriteLine(s);
};
i = 3;
s = "xyz";
λ();

Creating the λ doesn't use the values of i and s , but creates a set of instructions as to what to do with i and s when λ is invoked. 创建λ不使用is的值,而是创建一组关于调用λ时如何处理is的指令。 Only when that happens are the values of i and s used. 只有在这种情况发生时,才会使用is的值。

Putting it all together : In none of your cases do you have any late-binding. 放在一起 :在任何情况下,您都没有任何后期绑定。 That is irrelevant to your question. 那和你的问题无关。

In both you have delayed execution. 在这两种情况下,您都延迟执行。 Both the call to Take and the call to Where return enumerable objects which will act upon arr when they are enumerated. Take的调用和对Where的调用都返回可枚举的对象,这些对象将在枚举时作用于arr

In only one do you have a captured variable. 在只有一个中,您有一个捕获的变量。 The call to Take passes an integer directly to Take and Take makes use of that value. Take的调用将一个整数直接传递给Take并使用Take的值。 The call to Where passes a Func<int, bool> created from a lambda expression, and that lambda expression captures an int variable. Where的调用传递了从lambda表达式创建的Func<int, bool> ,该lambda表达式捕获一个int变量。 Where knows nothing of this capture, but the Func does. Where都不知道该捕获,但是Func知道。

That's the reason the two behave so differently in how they treat cutoff . 这就是两个人对待cutoff行为如此不同的原因。

Take不需要lambda,而是一个整数,因此,当您更改原始变量时,它不能更改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM