Coding interviews
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
bool leftDisposed=false,rightDisposed=false;
IEnumerable<int> Left() { try { yield return 1; yield return 2; } finally { leftDisposed=true; } }
IEnumerable<int> Right() { try { yield return 10; yield return 20; } finally { rightDisposed=true; } }
Console.WriteLine("pair:"+string.Join(",",Pairs(Left(),Right()).Take(1)));
Console.WriteLine("disposed:"+leftDisposed.ToString().ToLowerInvariant()+","+rightDisposed.ToString().ToLowerInvariant());
static IEnumerable<int> Pairs(IEnumerable<int> left,IEnumerable<int> right) {
using var a=left.GetEnumerator(); using var b=right.GetEnumerator();
while(a.MoveNext() && b.MoveNext()) yield return checked(a.Current+b.Current);
}
Reveal answer and reasoning
The output lines, in order, are “pair:11”; “disposed:true,true”. Enumeration obtains both source enumerators, advances them once and yields one plus ten as eleven. Take one then stops, disposing the outer iterator; its using scopes dispose both sources, whose finally blocks set the two flags. The remaining values two and twenty are never requested. Merely creating the deferred sequence would not have executed either source.
Take one disposes the outer iterator when it stops. Compiler-generated iterator cleanup executes the using scopes, which in turn run the source finally blocks. That is why both flags are true before the second print. The code does not need to enumerate the unused second pair merely to trigger cleanup.
For p yielded pairs, work is O(p) with constant auxiliary iterator state, excluding resources owned by the sources. Because the condition advances the left side first, exhaustion of the right side can consume one extra left value. That detail matters for one-shot or side-effecting inputs. Disposing an enumerable object is not the same as disposing the enumerators it creates. Returning a lazy query also extends source-resource lifetimes beyond method return.
This sample does not make non-repeatable streams replayable or promise that no source side effect occurs during MoveNext.
What a strong answer covers
- Exact output: pair:11 | disposed:true,true
- Enumeration obtains both source enumerators, advances them once and yields one plus ten as eleven.
- Disposing an enumerable object is not the same as disposing the enumerators it creates.
Common pitfall
Disposing an enumerable object is not the same as disposing the enumerators it creates. Returning a lazy query also extends source-resource lifetimes beyond method return. This sample does not make non-repeatable streams replayable or promise that no source side effect occurs during MoveNext.
Follow-up questions
- Use a shorter right sequence and count left-side MoveNext observations. Hint: left is evaluated first in the short-circuit condition. Then stop before the second output and verify both finally blocks still run.
- Which source can be advanced one extra time when the other source is exhausted, and why?
Self-review
Choose what to revisit. These marks are your own assessment and are separate from lesson completion.