After a previous question on stackoverflow regarding async / await it seemed to me that await was much more powerful and general than the marketing suggested. It seems to be a general method of building computation expressions just like in F#. So after a bit of a struggle I came up with some code that successfully executes as below.
using FluentAssertions;
using System.Collections.Generic;
namespace EnumerableViaAwait.Specs
{
[global::Microsoft.VisualStudio.TestTools.UnitTesting.TestClass]
public class MyTestClass
{
public IEnumerable<int> Numbers()
{
return EnumeratorMonad.Build<int>(async Yield =>
{
await Yield(11);
await Yield(22);
await Yield(33);
});
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
public void TestEnum()
{
var v = Numbers();
var e = v.GetEnumerator();
int[] expected = { 11, 22, 33 };
Numbers().Should().ContainInOrder(expected);
}
}
}
Now carefully note what is going on here. I am NOT building a reactive observable. I am building an IEnumerable. It is strictly a pull system. I can quite happily write.
foreach item in Numbers(){
Console.WriteLine(item);
}
and it will print out
11
22
33
This is very interesting because the system is not strictly asynchronous but I'm abusing the await framework and the ability to "await anything" as outlined here. http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx.The question(s) are.
- How far can this abuse of await / async go?
- Is this pattern as powerfull as F# computation expressions
- Is traditional IEnumerator / Yield pattern just syntactic sugar that evaluates exactly to this pattern
The code implementing the pattern is below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace EnumerableViaAwait
{
public class EnumeratorMonad<T> : IEnumerable<T>, IEnumerator<T>
{
public class Yield
{
private EnumeratorMonad<T> _Monad;
public Yield(EnumeratorMonad<T> monad)
{
_Monad = monad;
}
public YieldAwaiter GetAwaiter()
{
return new YieldAwaiter(_Monad);
}
}
public class YieldAwaiter : INotifyCompletion
{
EnumeratorMonad<T> _Monad;
public YieldAwaiter(EnumeratorMonad<T> monad)
{
_Monad = monad;
}
public bool IsCompleted
{
get { return _Monad.IsCompleted(); }
}
public void GetResult()
{ }
public void OnCompleted(Action continuation)
{
_Monad.Next = continuation;
}
}
private bool Completed { get; set; }
public EnumeratorMonad()
{
Completed = false;
}
public bool IsCompleted()
{
return Completed;
}
public void Build(Func<Func<T, Yield>, Task> action)
{
Func<T, Yield> yielder = (T value) => {
_Current = value;
return new Yield(this);
};
Next = async () => {
await action(yielder);
Completed = true;
};
}
private T _Current;
public T Current
{
get { return _Current; }
}
public void Dispose()
{
}
object System.Collections.IEnumerator.Current
{
get { return _Current; }
}
Action Next;
public bool MoveNext()
{
if (!Completed )
{
Next();
}
return !Completed;
}
public void Reset()
{
throw new NotImplementedException();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return this;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this;
}
}
public class EnumeratorMonad{
public static EnumeratorMonad<T> Build<T>(Func<Func<T, EnumeratorMonad<T>.Yield>, Task> action)
{
var monad = new EnumeratorMonad<T>();
monad.Build(action);
return monad;
}
}
}