The concept and environment
A spread element contributes the elements produced by an enumerable expression to a newly constructed collection. It does not store the iterator itself as one element, and it does not turn the resulting collection into a deferred query. Here the destination is an integer array containing one, the values read from a List, and five. The local iterator also increments a counter when enumeration actually starts. This gives a small, bounded way to observe construction and later ownership without depending on allocation counters, compiler-generated type names, or runtime-specific performance behavior.
Walk through the portable program
Read begins when the collection expression materializes its spread. It increments enumerations once, then yields two and four from the current source List. The completed array is therefore one, two, four, five. Changing the first source element to nine afterward does not rewrite the copied integer inside that array. The first line prints 1,2,4,5, the second prints source:9, and the final line prints enumerations:1. Merely printing the completed array does not enumerate Read again; it visits the array that was already built during the declaration.
A deeper design boundary
A snapshot claim needs an element-level qualification. Integers are copied values, so changing the source slot leaves the destination's prior integer intact. If both collections instead contain references to mutable objects, the collection construction copies those references. Replacing a source slot would still leave the destination slot unchanged, but mutating a shared object's property could be visible through both collections. Decide whether you need independent collection structure, independent element objects, or only a stable enumeration result. Those are different requirements, and spread syntax alone satisfies only the construction rules it actually expresses.
Pitfalls and limits
Do not spread an unbounded sequence and expect an IEnumerable target to preserve laziness. Collection expressions materialize their elements even when the target is an enumerable interface. Also avoid putting intricate side effects in several spread sources and inferring a universal interleaving strategy from one compiler's emitted instructions. This sample uses one simple iterator with bounded data and checks its completed observations. Exceptions during enumeration can prevent construction from completing, and changing the List while that enumeration is active is a separate invalidation problem, not a supported snapshot technique.
Practice one: isolate a contract
First construct a second array with the same expression after changing the source. Predict that the original still contains 1,2,4,5, while the new one contains 1,9,4,5, and that enumerations is now two. Hint: each construction requests a new enumeration; reading either finished array does not repeat it. Give the two arrays different names and print both before checking the counter. This makes the time of materialization explicit and prevents an assignment that overwrites the first snapshot from erasing the evidence you intended to compare.
Practice two: challenge an assumption
For another exercise, replace the integers in the source with small mutable objects, build a destination using spread, then perform two separate operations: replace one source slot and mutate an object still referenced by both collections. Predict each destination observation before running. Hint: track collection slots and object identity separately, and use a fresh fixture for each operation. If the requirement is a fully independent snapshot, implement and test an explicit element-copy policy instead of assuming a second collection allocation recursively duplicates every reachable object.
Review the resulting contract
A useful review asks when enumeration happens, how many elements are required, what is copied, and which aliases remain afterward. The sample answers those questions with an observable counter and a source mutation, while staying independent of the destination's internal allocation strategy. Apply the same reasoning when collecting data for a report or constructing a request batch: a stable container can still hold shared mutable elements. If the producer can fail or never finish, address that contract before choosing a compact construction expression that eagerly consumes everything it receives.
A working example
using System;
using System.Collections.Generic;
var source = new List<int> { 2, 4 };
int enumerations = 0;
IEnumerable<int> Read()
{
enumerations++;
foreach (int item in source) yield return item;
}
int[] snapshot = [1, .. Read(), 5];
source[0] = 9;
Console.WriteLine(string.Join(",", snapshot));
Console.WriteLine($"source:{source[0]}");
Console.WriteLine($"enumerations:{enumerations}");
Expected output
1,2,4,5 source:9 enumerations:1
Construct another snapshot after the mutation. A collection expression targeting IEnumerable is still materialized, not a lazy LINQ query.