Skip to content

Instantly share code, notes, and snippets.

@jkells
Last active March 20, 2017 06:57
Show Gist options
  • Save jkells/fc7e104e06cf9e28823b2f2dc574aff2 to your computer and use it in GitHub Desktop.
Save jkells/fc7e104e06cf9e28823b2f2dc574aff2 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace ConsoleApp1
{
/// <summary>
/// An example of a redux-saga style async runner in C#
/// </summary>
public class Program
{
public IEnumerable<AsyncOp> LoginSaga(string user, string pass)
{
var api = new Api();
var loginAction = AsyncOp.Create(() => api.Login(user, pass));
yield return loginAction;
var userId = loginAction.Result;
var nameAction = AsyncOp.Create(() => api.GetName(userId));
yield return nameAction;
var name = nameAction.Result;
var writeLineAction = AsyncOp.Create(() => api.ConsoleWriteLine(name));
yield return writeLineAction;
}
public async Task Middleware(IEnumerable<AsyncOp> steps)
{
foreach (var step in steps)
await step.Step();
}
public static void Main(string[] args)
{
var p = new Program();
p.Middleware(p.LoginSaga("jkells", "mypass")).Wait();
Console.ReadKey();
}
}
/// <summary>
/// Some sample API methods
/// </summary>
public class Api
{
public async Task<int> Login(string user, string pass)
{
await Task.Delay(1000);
return 12345;
}
public async Task<string> GetName(int id)
{
await Task.Delay(1000);
if (id == 12345)
return "Jared";
return null;
}
public Task<object> ConsoleWriteLine(string message)
{
Console.WriteLine(message);
return Task.FromResult((object) null);
}
}
/// <summary>
/// Description of an async operation. The middleware uses this to make the actual call
/// </summary>
public abstract class AsyncOp
{
public abstract Task Step();
public static AsyncOp<T> Create<T>(Expression<Func<Task<T>>> func)
{
return new AsyncOp<T>(func);
}
}
/// <summary>
/// Description of an async operation. The middleware uses this to make the actual call
/// </summary>
public class AsyncOp<T> : AsyncOp
{
public AsyncOp(Expression<Func<Task<T>>> expression)
{
Expression = expression;
Func = expression.Compile();
}
public Expression<Func<Task<T>>> Expression { get; }
public T Result { get; set; }
public Func<Task<T>> Func { get; }
public override async Task Step()
{
Result = await Func();
}
}
/// <summary>
/// Examples of how you could test such a function.
/// </summary>
public class Test
{
public static void TestLoginSaga()
{
var p = new Program();
var api = new Api();
var saga = p.LoginSaga("jkells", "mypass").GetEnumerator();
saga.MoveNext();
var step1 = (AsyncOp<int>)saga.Current;
AssertExpressionEquals(step1.Expression, () => api.Login("jkells", "mypass"));
step1.Result = 1337;
saga.MoveNext();
var step2 = (AsyncOp<string>)saga.Current;
AssertExpressionEquals(step2.Expression, () => api.GetName(1337));
step2.Result = "bob";
saga.MoveNext();
var step3 = (AsyncOp<object>)saga.Current;
AssertExpressionEquals(step3.Expression, () => api.ConsoleWriteLine("bob"));
}
public static void AssertExpressionEquals<T>(Expression<Func<Task<T>>> left, Expression<Func<Task<T>>> right)
{
var leftBody = left.Body as MethodCallExpression;
var rightBody = right.Body as MethodCallExpression;
if (leftBody == null || rightBody == null)
throw new Exception("Unsupported expression types");
if (!leftBody.Method.Equals(rightBody.Method))
throw new Exception("Expression not equal. Wrong method");
if (leftBody.Arguments.Count != rightBody.Arguments.Count)
throw new Exception("Expression not equal. Wrong argument count");
for (var i = 0; i < leftBody.Arguments.Count; i++)
{
var leftArgument =
Expression.Lambda<Func<object>>(Expression.Convert(leftBody.Arguments[i], typeof(object)))
.Compile()
.Invoke();
var rightArgument =
Expression.Lambda<Func<object>>(Expression.Convert(rightBody.Arguments[i], typeof(object)))
.Compile()
.Invoke();
if (!leftArgument.Equals(rightArgument))
throw new Exception($"Expression not equal. Argument {i} mismatch");
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment