C# 理解C语言中LINQ中的惰性求值#
我正在阅读关于LINQ的文章,不理解查询是如何在惰性计算方面执行的 因此,我将文章中的示例简化为以下代码:C# 理解C语言中LINQ中的惰性求值#,c#,linq,lazy-evaluation,C#,Linq,Lazy Evaluation,我正在阅读关于LINQ的文章,不理解查询是如何在惰性计算方面执行的 因此,我将文章中的示例简化为以下代码: void Main() { var data = from f in GetFirstSequence().LogQuery("GetFirstSequence") from s in GetSecondSequence().LogQuery("GetSecondSequence", f) select $"{f} {s}";
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;
}
}
当然,我需要添加扩展方法来交错两个序列(从上述文章中获得):
公共静态IEnumerable交织(此IEnumerable第一,IEnumerable第二)
{
var firstIter=first.GetEnumerator();
var secondIter=second.GetEnumerator();
while(firstIter.MoveNext()&&secondIter.MoveNext())
{
一级电流的收益率;
产生二次电流;
}
}
执行这些代码行后,我在txt文件中得到以下输出:
正在执行查询GetFirstSequence执行查询Take
执行查询跳过
执行查询交织
正在执行查询GetSecondSequence a
正在执行查询GetSecondSequence a
正在执行查询GetSecondSequence b
正在执行查询GetSecondSequence c
执行查询GetSecondSequence b 这让我很尴尬,因为我不理解查询执行的顺序 为什么以这种方式执行查询
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
在shuffle
上迭代与在
Interleave(data.Take(3), data.Skip(3))
Interleave()
在数据
上交错两次迭代中的元素,从而也交错迭代产生的输出
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"
我认为,如果你一次只关注一个部分,避免像序列本身交错这样的事情,你会发现理解起来容易得多。现在你有这么多的手术在进行,难怪会让人困惑。您还没有说您希望如何执行这个复杂的查询,所以不清楚哪部分需要解释。解释查询执行流程的每一个部分都需要花费大量时间来处理如此复杂的查询。。。如果你能把它简化成一个仍然让你感到困惑的简短查询,那么帮助你就容易多了。一个可能引起困惑的问题是LogQuery实际上不是一个懒惰的方法。执行查询时,它不会记录其参数。它在构造查询时记录它们。在这个简单的例子中,这并不重要,因为它们的顺序相同。不过,在更复杂的例子中,它们是不同的。谢谢你非常详细的解释。现在一切都清楚了。 Executing query GetFirstSequence Executing query Take Executing query Skip Executing query Interleave Executing query GetSecondSequence a Executing query GetSecondSequence b Executing query GetSecondSequence c
Interleave(data.Take(3), data.Skip(3))
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"