Wednesday, April 13, 2011

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()
{
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

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( ... );
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

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;
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

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 :