C# Linq后期绑定混乱
有人能解释一下我在这里遗漏了什么吗。根据我的基本理解,将在使用结果时计算linq结果,我可以在下面的代码中看到这一点C# Linq后期绑定混乱,c#,linq,C#,Linq,有人能解释一下我在这里遗漏了什么吗。根据我的基本理解,将在使用结果时计算linq结果,我可以在下面的代码中看到这一点 static void Main(string[] args) { Action<IEnumerable<int>> print = (x) => { foreach (int i in x) { Console.WriteLine(i); }
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();
}
static void Main(字符串[]args)
{
动作打印=(x)=>
{
foreach(整数i在x中)
{
控制台写入线(i);
}
};
int[]arr={1,2,3,4,5};
int截止=1;
IEnumerable result=arr.Where(x=>x
输出:
First Print
1
2
Second Print
1
2
3
第一次印刷
1.
2.
第二次印刷
1.
2.
3.
现在我改变了主意
arr.Where(x => x < cutoff);
arr.Where(x=>x
到
IEnumerable<int> result = arr.Take(cutoff);
IEnumerable result=arr.Take(截止);
输出如下
First Print
1
Second Print
1
第一次印刷
1.
第二次印刷
1.
为什么使用Take时,它不使用变量的当前值?
Take
不使用lambda,而是一个整数,因此,当您更改原始变量时,它不会更改。您看到的行为来自于对LINQ函数的参数求值的不同方式。其中
方法接收lambda,该lambda通过引用捕获值截止
。它是按需评估的,因此可以看到当时的截止值
Take
方法(以及类似的方法,如Skip
)采用int
参数,因此cutoff
按值传递。使用的值是调用Take
方法时cutoff
的值,而不是在计算查询时
注意:这里的术语延迟绑定有点不正确。后期绑定通常指在运行时与编译时确定表达式绑定到的成员的过程。在C语言中,你可以通过动态或反射来实现这一点。LINQ按需评估其部件的行为称为延迟执行 这里有一些不同的东西被弄糊涂了
后期绑定:这是在编译代码后确定代码含义的地方。例如,如果编译器检查x
类型的对象是否具有DoStuff()
方法(也考虑扩展方法和默认参数),然后在其输出的代码中生成对它的调用,或者由于编译器错误而失败,则x.DoStuff()
是早期绑定的。如果在运行时完成对DoStuff()
方法的搜索,并且如果没有DoStuff()
方法,则会引发运行时异常,则这是后期绑定。这两种方法各有利弊,C#通常是早期绑定,但支持后期绑定(最简单的方法是通过动态,但涉及反射的更复杂的方法也很重要)
延迟执行:严格来说,所有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);
}
捕获的变量:通常,当我们使用变量时,我们会利用其当前状态:
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
使用该值。调用,其中
传递从lambda表达式创建的Func
,该lambda表达式捕获一个int
变量Where
对该捕获一无所知,但Func
知道
这就是为什么这两个人在处理cutoff
时表现如此不同的原因,这与后期绑定无关……谢谢Jon。我知道后期绑定,但我正在处理多个问题,并在那里使用了错误的术语,而不是延迟执行。但是读你的答案还是很有趣的。谢谢,伙计。在您的Take
示例实现中,参数异常不会在您指定的位置抛出。您编写的代码在第一次移动到下一次之前都不会运行。您需要有一个外部方法来进行参数验证,并调用一个用yields编写的helper方法。@GideonEngelberth感谢您的帮助。我想要简洁,因为它的目的是阅读而不是运行,但它完全违背了我的写作要点。仔细看一下真实情况,我意识到它应该把负数当作零,但我想我会把这个错误留在那里,因为它不会挫败我的论点。
int i = 2;
string s = "abc";
Action λ = () =>
{
Console.WriteLine(i);
Console.WriteLine(s);
};
i = 3;
s = "xyz";
λ();