Last active
March 20, 2017 06:57
-
-
Save jkells/fc7e104e06cf9e28823b2f2dc574aff2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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