这里有一些不同的东西会混淆。
后期绑定:这是在编译后确定代码含义的地方。例如,如果编译器检查x 类型的对象是否具有DoStuff() 方法(也考虑扩展方法和默认参数),然后在它输出的代码中产生对它的调用,则x.DoStuff() 是早期绑定的,否则会因编译器错误而失败。如果DoStuff() 方法的搜索是在运行时完成的,并且如果没有DoStuff() 方法,则抛出运行时异常。各有利弊,C# 通常是早期绑定的,但支持后期绑定(最简单的是通过dynamic,但涉及反射的更复杂的方法也很重要)。
延迟执行:严格来说,所有的 Linq 方法都会立即产生结果。但是,该结果是一个对象,它存储对可枚举对象的引用(通常是先前 Linq 方法的结果),当它本身被枚举时,它将以适当的方式处理。例如,我们可以编写自己的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);
}
现在,当我们使用它时:
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.
捕获的变量:通常当我们使用一个变量时,我们会利用它的当前状态:
int i = 2;
string s = "abc";
Console.WriteLine(i);
Console.WriteLine(s);
i = 3;
s = "xyz";
这很直观,打印的是 2 和 abc 而不是 3 和 xyz。但是,在匿名函数和 lambda 表达式中,当我们使用变量时,我们会将其“捕获”为变量,因此我们最终将使用调用委托时它所具有的值:
int i = 2;
string s = "abc";
Action λ = () =>
{
Console.WriteLine(i);
Console.WriteLine(s);
};
i = 3;
s = "xyz";
λ();
创建λ 不使用i 和s 的值,而是创建一组指令,说明在调用λ 时如何处理i 和s。只有当这种情况发生时,才会使用 i 和 s 的值。
将所有内容放在一起:在您的任何情况下,您都没有任何后期绑定。这与您的问题无关。
在这两种情况下,您都延迟了执行。对Take 的调用和对Where 的调用都返回可枚举的对象,这些对象在枚举它们时将作用于arr。
只有一个你有一个捕获的变量。对Take 的调用将一个整数直接传递给Take,Take 使用该值。对 Where 的调用传递了一个从 lambda 表达式创建的 Func<int, bool>,并且该 lambda 表达式捕获了一个 int 变量。 Where 对这次捕获一无所知,但 Func 知道。
这就是两人对待cutoff的方式如此不同的原因。