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.
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;
}
}
}
yield return
and await
/async
are just different specialized forms of coroutines. You've shown that you can (essentially) implement yield return
using await
/async
, and I would not be surprised to find that it were possible the other way around. I'm sure that they are implemented in a very similar manner.
In practice, of course, I would not use await
/async
for iteration, since yield return
is much simpler and more clear.
So,
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With