using System.Collections.Generic;
using System.Threading.Tasks;
using OperationsList = System.Collections.Generic.LinkedList<OperationType>;
using OperationsResult = System.Collections.Generic.Dictionary<string, object?>;
var runner = new TransactionRunner();
.Add("Op 1", () => { Console.WriteLine(1); return 2; })
.Add("Op 2", (res) => { Console.WriteLine(res.GetResult<int>("Op 1")); })
() => { throw new Exception(); },
() => Console.WriteLine("Exception was thrown. Rolling back...")
public class OperationType
public required string OperationName { get; set; }
public required Delegate Operation { get; set; }
public Delegate? Rollback { get; set; }
public class TransactionRunner
private readonly OperationsList _operations = new OperationsList();
internal readonly OperationsResult _results = new OperationsResult();
private RunnerState _state = RunnerState.Initialized;
public RunnerState State => _state;
public TransactionRunner Add(string name, Delegate operation, Delegate? rollback = null)
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
public TransactionRunner Add<TResult>(string name, Func<TResult> operation, Delegate? rollback = null)
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
public TransactionRunner Add(string name, Func<OperationsResult, Delegate> operation, Delegate? rollback = null)
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
public TransactionRunner Add(string name, Action<OperationsResult> operation, Delegate? rollback = null)
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
public TransactionRunner Add<TResult>(string name, Func<OperationsResult, Func<TResult>> operation,
Delegate? rollback = null)
var op = new OperationType { OperationName = name, Operation = operation, Rollback = rollback };
private void AppendOperation(OperationType operation)
_operations.AddLast(operation);
public ExecutionType<T> Returns<T>(string operationName)
return new ExecutionType<T>(this, operationName);
new ExecutionType(this).Execute();
public Task ExecuteAsync()
return new ExecutionType(this).ExecuteAsync();
if (_state is not RunnerState.Initialized)
throw new ApplicationException("Runner can't be called more than once");
var currentNode = _operations.First;
while (currentNode is not null)
var operation = currentNode.Value;
if (operation.Operation is Func<OperationsResult> or Action<OperationsResult>)
_results[operation.OperationName] = operation.Operation.DynamicInvoke(_results) ?? null;
_results[operation.OperationName] = operation.Operation.DynamicInvoke() ?? null;
currentNode = currentNode.Next;
throw new Exception($"Transaction have failed on operation \"{operation.OperationName}\" and was rolled back",
public async Task AsyncExecutor()
if (_state is not RunnerState.Initialized)
throw new ApplicationException("Runner can't be called more than once");
var currentNode = _operations.First;
while (currentNode is not null)
var operation = currentNode.Value;
if (operation.Operation is Func<OperationsResult> or Action<OperationsResult>)
_results[operation.OperationName] = operation.Operation.DynamicInvoke(_results) ?? null;
else if (operation.Operation.Method.ReturnType.IsSubclassOf(typeof(Task)))
dynamic invoke = operation.Operation.DynamicInvoke();
_results[operation.OperationName] = await invoke ?? null;
_results[operation.OperationName] = operation.Operation.DynamicInvoke() ?? null;
currentNode = currentNode.Next;
await RollbackAsync(currentNode);
throw new Exception($"Transaction have failed on operation \"{operation.OperationName}\" and was rolled back",
private void Rollback(LinkedListNode<OperationType>? currentNode)
while (currentNode?.Previous is not null)
var rollback = currentNode.Value.Rollback;
if (rollback is Func<OperationsResult> or Action<OperationsResult>)
rollback.DynamicInvoke(_results);
rollback?.DynamicInvoke();
currentNode = currentNode.Previous;
_state = RunnerState.RolledBack;
private async Task RollbackAsync(LinkedListNode<OperationType>? currentNode)
while (currentNode is not null)
var rollback = currentNode.Value.Rollback;
if (rollback is Func<OperationsResult> or Action<OperationsResult>)
rollback.DynamicInvoke(_results);
else if (rollback is Func<Task>)
dynamic invoke = rollback.DynamicInvoke();
if (invoke is null) continue;
else if (rollback is not null)
rollback.DynamicInvoke();
currentNode = currentNode.Previous;
_state = RunnerState.RolledBack;
public class ExecutionType
private readonly TransactionRunner _parent;
public ExecutionType(TransactionRunner parent)
public void Execute() => _parent.Executor();
public async Task ExecuteAsync() => await _parent.AsyncExecutor();
public class ExecutionType<T>
private readonly TransactionRunner _parent;
private readonly string _returnKey;
public ExecutionType(TransactionRunner parent, string returnKey)
return (T?)_parent._results[_returnKey];
public async Task<T?> ExecuteAsync()
await _parent.AsyncExecutor();
return (T?)_parent._results[_returnKey];
public static class DictionaryExtensions
public static object? GetResult(this Dictionary<string, object?> instance, string name)
var value = instance[name];
public static T? GetResult<T>(this Dictionary<string, object?> instance, string name)
return (T?)GetResult(instance, name);
throw new ApplicationException("Resulting value is not available");