F# under the covers XIII -- Sequence expressions
After more than a year since the previous instalment...
A C# method that returns an enumerator via yield return
generates a synthetic class that looks like this:
private sealed class <ToEnumerable>d__0 : IEnumerable<T>, IEnumerable, IEnumerator<T>, IEnumerator, IDisposable | |
{ | |
// data fields omitted | |
private T System.Collections.Generic.IEnumerator<System.T>.Current { get; } | |
private object System.Collections.IEnumerator.Current { get; } | |
public <ToEnumerable>d__0(int <>1__state) : base() { ... } | |
private IEnumerator<T> System.Collections.Generic.IEnumerable<System.T>.GetEnumerator() { ... } | |
private IEnumerator System.Collections.IEnumerable.GetEnumerator(){ ... } | |
private bool MoveNext() { ... } | |
private void System.Collections.IEnumerator.Reset() | |
{ | |
throw new NotSupportedException(); | |
} | |
private void System.IDisposable.Dispose() | |
{ | |
} | |
} |
where I omit unimportant detail based on the particular enumeration, but draw attention to how the other IEnumerator
methods are implemented.
The F# result from a sequence expression is very different, with an explicit close and dispose:
internal sealed class ToEnumerable@18 : GeneratedSequenceBase<T> | |
{ | |
// data fields omitted | |
public ToEnumerable@18(Stream stream, int result, T v, IEnumerator<T> @enum, int pc, T current) { ... } | |
public override int GenerateNext(IEnumerable<T> next) { ... } | |
public override void Close() { ... } | |
public override bool get_CheckClose() { ... } | |
public override T get_LastGenerated() | |
{ | |
return this.current; | |
} | |
public override IEnumerator<T> GetFreshEnumerator() | |
{ | |
return new Chunk.ToEnumerable@18( ... ); | |
} | |
} |
and in GeneratedSequenceBase
we have
private virtual void System-IDisposable-Dispose() | |
{ | |
if (this.redirect) | |
{ | |
GeneratedSequenceBase<T> arg_11_0 = this.redirectTo; | |
tail(); | |
arg_11_0.Close(); | |
return; | |
} | |
else | |
{ | |
GeneratedSequenceBase<T> arg_1A_0 = this; | |
tail(); | |
arg_1A_0.Close(); | |
return; | |
} | |
} |
The Close
method renders the enumeration non-functional, though the closed enumerator does not throw, it simply yields no more. That the resulting sequence has an active Dispose
behaviour, unlike the C# case, explains why in the Window
example I needed to add the Controlled
wrapper to block that, so that the Stream
based enumeration written as a sequence expression would draw more than one tranche before stopping.
By contrast, you don't need the Controlled
wrapper for windowing the BCL derived array-as-enumerator; presumably that has a no-op Dispose
.
No comments :
Post a Comment