A deferred query is a recipe, not a frozen result
Deferred execution delays the work of producing sequence elements until a consumer asks for them. With an in-memory array and Where, the query stores the source and predicate needed for later traversal. Assigning that query to a variable does not automatically walk the array or create a filtered snapshot. This is a behavior of this operator and source combination, not a universal promise that every method returning IEnumerable performs no immediate work.
A terminal operation such as Sum consumes the sequence and returns an ordinary result. Materialization methods such as ToArray consume it and store the resulting elements in a new container. Those are different boundaries: after materialization you have a snapshot of selected element values, while before it a later enumeration can observe changes in the underlying mutable source. Understanding when work happens is essential for correctness, not only a performance optimization for large collections.
View full size ↗Read the diagram
Begin with source = [1, 2, 3] and query = source.Where(n => n % 2 == 0). Creating this query does not enumerate the list.
Add 4 to source before calling query.ToList(). Enumeration now produces [2, 4]; snapshot holds that materialized list.
Next add 6 to source. A fresh enumeration of query produces [2, 4, 6], while snapshot still contains [2, 4]. The source changes occur between enumerations, not during a list enumeration.
The numeric illustration is independent of the runnable sample. For reference-type elements, ToList copies references into a new list; it does not deep-clone the objects.
Illustration created with ImageGen for SharpRune.
Find the line that actually asks for a total
The source array contains decimal prices twelve, eight, and twenty-five. The assignment to kept creates a Where query that will accept prices at least ten. At that point the example has not produced a total and has printed nothing. kept.Sum() then requests enumeration, so Where checks each price and yields twelve and twenty-five. Sum combines those accepted decimals into thirty-seven, and WriteLine prints 37.
The query's variable name does not mean it owns a separate array of kept prices. It still represents work over the source. In the unchanged example the source never changes, and the query is consumed once, so no surprising difference appears between recipe and snapshot. That makes the example a useful baseline for a controlled mutation experiment. The final decimal total is an ordinary value; changing the array later does not retroactively rewrite a total that was already calculated and stored.
var kept = prices.Where(p => p >= 10m);
Console.WriteLine(kept.Sum());Repeated enumeration can repeat work and observe new state
Calling Count and then Sum on a deferred query generally enumerates it twice. With a small unchanged array and a pure predicate, that can produce consistent answers, but it is still two traversals. A changing source or side-effecting predicate can make the observations differ. Not every deferred operator is streaming: OrderBy, for example, must gather sorting information before it yields the first ordered result. Keep execution timing, buffering, and snapshot ownership as related but distinct questions.
Change the source between query creation and consumption
First insert prices[0] = 2m; after creating kept and before calling Sum. Predict twenty-five, because the later traversal now sees two, eight, and twenty-five and accepts only the last value. Hint: the query was not a frozen list containing the original twelve. Restore the source and record the original thirty-seven again before starting the second experiment, so each result has one clearly identified cause.
Next create var snapshot = kept.ToArray(); before changing the first price to two. Print snapshot.Sum and kept.Sum after that change. Predict thirty-seven for the materialized snapshot and twenty-five for the deferred query. Hint: the elements are decimal values, so the copied array stores independent numeric values. Repeat the reasoning for a sequence of mutable objects and identify what a shallow container copy would still share. Finally explain whether a live view or a stable snapshot best fits a report that must not change between pages.
Mark the execution and ownership boundaries
For every query in a short pipeline, identify where its source comes from, whether that source can change, which operator starts enumeration, and whether results are stored for reuse. You should be able to predict the unchanged thirty-seven and both mutation experiments without timing measurements. Materialize when the application needs a stable selected result, while recognizing the memory and ownership tradeoff instead of adding ToArray automatically to every query.
- The Where assignment creates a recipe over the source.
- Sum enumerates that recipe and produces the original
37. - A later enumeration can observe a changed source array.
- ToArray materializes selected values at a particular time.
A working example
var prices = new[] { 12m, 8m, 25m };
var kept = prices.Where(p => p >= 10m);
Console.WriteLine(kept.Sum());Expected output
37
Try the filter on a smaller list.