C#中LINQ延迟求值的执行机制疑问:附代码示例
Let me walk you through exactly how this LINQ query runs with deferred evaluation—this is a super common gotcha, so you’re totally not alone in being confused!
First, let’s recap the core rule of deferred evaluation in LINQ: defining the query doesn’t run any of your sequence-generating code. The GetFirstSequence() and GetSecondSequence() methods won’t execute, and no yield return statements will fire, until you actually start enumerating the query results (like when you call Dump() in LINQPad).
Step 1: Query Definition (No Execution Happens)
When you write:
var data = from f in GetFirstSequence().LogQuery("GetFirstSequence") from s in GetSecondSequence().LogQuery("GetSecondSequence", f) select $"{f} {s}";
All you’re doing is creating a query object that describes what you want to do. Think of it as a set of instructions, not a completed list of results. None of the code inside GetFirstSequence, GetSecondSequence, or even the LogQuery methods runs yet.
Step 2: Execution Starts When You Enumerate (Call Dump())
data.Dump() tells LINQPad to iterate over the query results to display them. This is when the real work begins—here’s the play-by-play:
First, pull the first element from the outer sequence
- The query asks
GetFirstSequence().LogQuery(...)for its first element. This triggers theLogQuerycall for "GetFirstSequence", and then runsGetFirstSequence()until its firstyield return "a". Nowf = "a".
- The query asks
Pull all elements from the inner sequence for this
f- Next, the query needs to generate all results for
f = "a", so it callsGetSecondSequence().LogQuery("GetSecondSequence", "a"). This triggers that log message, then runsGetSecondSequence()to get all its elements (say, if it yields "x" and "y", you’d get"a x"and"a y"as results). - The inner sequence is fully enumerated here before moving back to the outer sequence.
- Next, the query needs to generate all results for
Repeat for the next outer element
- Once the inner sequence for
f = "a"is done, the query goes back to the outer sequence and pulls the next element:yield return "b". - It then re-runs the inner sequence for
f = "b"—triggering the "GetSecondSequence, b" log, pulling all elements fromGetSecondSequence()again, and generating"b x","b y", etc.
- Once the inner sequence for
Finish when the outer sequence is exhausted
- This cycle repeats for
f = "c", and stops whenGetFirstSequence()has no more elements to yield.
- This cycle repeats for
What Your LogQuery Output Would Look Like
Assuming LogQuery logs when the sequence starts enumerating, you’d see something like this in LINQPad’s output window (before the final combined results):
Executing query: GetFirstSequence Executing query: GetSecondSequence, a Executing query: GetSecondSequence, b Executing query: GetSecondSequence, c
Notice that GetFirstSequence only logs once (since we enumerate it once, moving through each yield return in order), but GetSecondSequence logs three times—once for each value of f. That’s because every time we process a new f, we call GetSecondSequence() again, creating a new enumerable that gets enumerated from scratch.
Key Takeaways
- Deferred evaluation means "wait until you need the data to generate it".
- The query uses a "pull" model: each result is generated on-demand when the consumer (like
Dump()) asks for it. - For a nested
from(which maps toSelectMany), the inner sequence is re-enumerated once per element in the outer sequence.
内容的提问来源于stack exchange,提问作者Dmitry Stepanov

