在C#中了解LINQ中的惰性评估 [英] Understanding lazy evaluation in LINQ in C#
问题描述
我正在阅读这篇文章关于LINQ的信息,无法理解根据延迟评估执行查询的方式.
I was reading this article about LINQ and can't understand how the query is executed in terms of lazy evaluation.
因此,我将示例中的示例简化为以下代码:
So, I simplified the example from the article to this code:
void Main()
{
var data =
from f in GetFirstSequence().LogQuery("GetFirstSequence")
from s in GetSecondSequence().LogQuery("GetSecondSequence", f)
select $"{f} {s}";
data.Dump(); // I use LINQPAD to output the data
}
static IEnumerable<string> GetFirstSequence()
{
yield return "a";
yield return "b";
yield return "c";
}
static IEnumerable<string> GetSecondSequence()
{
yield return "1";
yield return "2";
}
public static class Extensions
{
private const string path = @"C:\dist\debug.log";
public static IEnumerable<string> LogQuery(this IEnumerable<string> sequence, string tag, string element = null)
{
using (var writer = File.AppendText(path))
{
writer.WriteLine($"Executing query {tag} {element}");
}
return sequence;
}
}
执行此代码后,我在debug.log文件中具有可以从逻辑上解释的输出:
After executing this code, I have in debug.log file the output that can be logically explained:
执行查询GetFirstSequence
执行查询GetSecondSequence a
执行查询GetSecondSequence b
执行查询GetSecondSequence c
Executing query GetFirstSequence
Executing query GetSecondSequence a
Executing query GetSecondSequence b
Executing query GetSecondSequence c
当我想将前三个元素与后三个元素交织在一起时,事情变得很奇怪:
The things got strange when I want to interleave first three element with last three elements like this:
void Main()
{
var data =
from f in GetFirstSequence().LogQuery("GetFirstSequence")
from s in GetSecondSequence().LogQuery("GetSecondSequence", f)
select $"{f} {s}";
var shuffle = data;
shuffle = shuffle.Take(3).LogQuery("Take")
.Interleave(shuffle.Skip(3).LogQuery("Skip")).LogQuery("Interleave");
shuffle.Dump();
}
当然,我需要添加扩展方法来交织两个序列(摘自上述文章):
Sure I need to add extension method to interleave two sequences (gotten from the above mentioned article):
public static IEnumerable<string> Interleave(this IEnumerable<string> first, IEnumerable<string> second)
{
var firstIter = first.GetEnumerator();
var secondIter = second.GetEnumerator();
while (firstIter.MoveNext() && secondIter.MoveNext())
{
yield return firstIter.Current;
yield return secondIter.Current;
}
}
执行以下代码行后,我在txt文件中得到以下输出:
After executing these lines of code I get the following output in my txt file:
执行查询GetFirstSequence
执行查询Take
执行查询Skip
执行查询Interleave
执行查询GetSecondSequence a
执行查询GetSecondSequence a
执行查询GetSecondSequence b
执行查询GetSecondSequence c
执行查询GetSecondSequence b
Executing query GetFirstSequence
Executing query Take
Executing query Skip
Executing query Interleave
Executing query GetSecondSequence a
Executing query GetSecondSequence a
Executing query GetSecondSequence b
Executing query GetSecondSequence c
Executing query GetSecondSequence b
这让我感到尴尬,因为我不了解查询的执行顺序.
and that makes me embarrassed because I don't understand the sequence in which my query is executing.
为什么以这种方式执行查询?
Why the query has been executed this way?
推荐答案
var data =
from f in GetFirstSequence().LogQuery("GetFirstSequence")
from s in GetSecondSequence().LogQuery("GetSecondSequence", f)
select $"{f} {s}";
只是另一种写作方式
var data = GetFirstSequence()
.LogQuery("GetFirstSequence")
.SelectMany(f => GetSecondSequence().LogQuery("GetSecondSequence", f), (f, s) => $"{f} {s}");
让我们逐步讲解代码:
var data = GetFirstSequence() // returns an IEnumerable<string> without evaluating it
.LogQuery("GetFirstSequence") // writes "GetFirstSequence" and returns the IEnumerable<string> from its this-parameter without evaluating it
.SelectMany(f => GetSecondSequence().LogQuery("GetSecondSequence", f), (f, s) => $"{f} {s}"); // returns an IEnumerable<string> without evaluating it
var shuffle = data;
shuffle = shuffle
.Take(3) // returns an IEnumerable<string> without evaluating it
.LogQuery("Take") // writes "Take" and returns the IEnumerable<string> from its this-parameter without evaluating it
.Interleave(
shuffle
.Skip(3) // returns an IEnumerable<string> without evaluating it
.LogQuery("Skip") // writes "Skip" and returns the IEnumerable<string> from its this-parameter without evaluating it
) // returns an IEnumerable<string> without evaluating it
.LogQuery("Interleave"); // writes "Interleave" and returns the IEnumerable<string> from its this-parameter without evaluating it
到目前为止的代码负责输出的前四行:
The code so far is responsible for the first four lines of output:
Executing query GetFirstSequence
Executing query Take
Executing query Skip
Executing query Interleave
IEnumerable< string>中没有一个已经评估过了.
None of the IEnumerable<string> have been evaluated yet.
最后,shuffle.Dump()
在shuffle
上进行迭代,从而评估IEnumerables.
Finally, shuffle.Dump()
iterates over shuffle
and thus evaluates the IEnumerables.
遍历data
会显示以下内容,因为SelectMany()
为GetFirstSequence()
中的每个元素调用GetSecondSequence()
和LogQuery()
:
Iterating over data
prints the following, because SelectMany()
calls GetSecondSequence()
and LogQuery()
for each element in GetFirstSequence()
:
Executing query GetSecondSequence a
Executing query GetSecondSequence b
Executing query GetSecondSequence c
遍历shuffle
与遍历
Interleave(data.Take(3), data.Skip(3))
Interleave()
在data
上交错两次迭代中的元素,因此也交错了对其进行迭代而导致的输出.
Interleave()
interleaves the elements from two iterations over data
and thus also interleaves the output caused by iterating over them.
firstIter.MoveNext();
// writes "Executing query GetSecondSequence a"
secondIter.MoveNext();
// writes "Executing query GetSecondSequence a"
// skips "a 1" from second sequence
// skips "a 2" from second sequence
// writes "Executing query GetSecondSequence b"
// skips "b 1" from second sequence
yield return firstIter.Current; // "a 1"
yield return secondIter.Current; // "b 2"
firstIter.MoveNext();
secondIter.MoveNext();
// writes "Executing query GetSecondSequence c"
yield return firstIter.Current; // "a 2"
yield return secondIter.Current; // "c 1"
firstIter.MoveNext();
// writes "Executing query GetSecondSequence b"
secondIter.MoveNext();
yield return firstIter.Current; // "b 1"
yield return secondIter.Current; // "c 2"
这篇关于在C#中了解LINQ中的惰性评估的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!