Search code examples
c#actionchain

C# Chaining Actions


I want to achieve something similar to how tasks can be chained:

Task.Factory.StartNew(() => { })
    .ContinueWith((t) => { })
    .ContinueWith((t) => { })
    ...
    .ContinueWith((t) => { });

However, I don't need these actions to be run asynchronously. I want to be able to chain actions (Execute the first, then the second, then the third, etc), and most importantly, I want to add a

.Catch((a) => { } 

Action that will be an action to execute when an error has been thrown by any of the actions in the chain.

At the end, I want it to look like this:

Actions.Factory.Execute(() => {
    Foo foo = new Foo();
    Bar bar = new Bar();
    if (foo != bar) 
        throw new Exception('Incompatible Types!);
}).Catch((ex) => {
    MyFancyLogger.LogError("Something strange happened: " + ex.Error.ToString();
});

I have tried implementing a Factory class that works on a derived class that inherits from the Action class, but unfortunately the 'Action' class is sealed, so that wont work.

EDIT:

My main reason for this is I want to record statistics on each wrapped action, as well as handle the error differently based on the severity. Currently I am doing this by executing one action with the following signature:

public static void ExecuteAction(this Action action, 
    string name = "", 
    int severity = 0, 
    bool benchmark = false, 
    bool logToDb = false, 
    [CallerMemberName]string source = "", 
    [CallerFilePath]string callerLocation = "", 
    [CallerLineNumber]int lineNo = 0) {
    // Wrap the whole method in a try catch
    try {
        // Record some statistics
        action.Invoke();
        // Record some statistics
    } catch (Exception ex) {
        if (severity > 3) {
             // Log to DB
        } else Logger.WriteError(ex);
        throw ex;
    }
}

and calling it like so:

(() => {
    //this is the body
}).ExecuteAction("Description of the method", 3, true, false);

In my opinion that works well and it reads easily. Now I just want to add a .Catch to that:

(() => {
    //this is the body
})
.ExecuteAction("Description of the method", 3, true, false)
.Catch((e) => {
    MessageBox.Show("This shouldn't have happened...");
});

Solution

  • I think method which execute all tasks one by one and wrapped by try ... catch will be enough, In case I understand you requirements correctly.

    public async Task Execute(IEnumerable<Func<Task>> actions, Action catchAction)
    {
        try
        {
            foreach (var action in actions)
            {
                await action();
            }
        }
        catch (Exception ex)
        {
            catchAction(ex)
        }
    }
    

    Consumer of method above will be responsible for creating collection of tasks in correct order.´

    However next requirement seems little bid confusing

    However, I don't need these actions to be run synchronously. I want to be able to chain actions (Execute the first, then the second, then the third, etc)

    If don't need actions to be run synchronously, then you can start executing them all at once (approximately "at once") and observe their completion wrapped by try .. catch

    public async Task Execute(IEnumerable<Func<Task>> actions, Action catchAction)
    {
        var tasks = actions.Select(action => action());
        try
        {
            await Task.WhenAll(tasks);
        }
        catch (Exception ex)
        {
            catchAction()
        }
    }
    

    In response on the comment about readability

    Of course, putting all actions inline will violate readability of any api which takes collection as parameter.

    Usually you have methods of some instance or static methods, in this case call will look like

    var actions = new Action[]
    {
        StaticClass.Action1,
        SomeInstance.Action2,
        AnotherStaticClass.Action3
    }
    
    await Execute(actions, Logger.Log);
    

    Another approach you can use is "Builder pattern"

    public class Executor
    {
        private List<Func<Task>> _actions;
        private Action<Exception> _catchAction;
        public Executor()
        {
            _actions = new List<Func<Task>>();
            _catchAction = exception => { };
        }
    
        public Executor With(Func<Task> action)
        {
            _actions.Add(action);
            return this;
        }
    
        public Executor CatchBy(Action<Exception> catchAction)
        {
            _catchAction = catchAction;
        }
    
        public async Task Run()
        {
            var tasks = _actions.Select(action => action());
            try
            {
                await Task.WhenAll(tasks);
            }
            catch (Exception ex)
            {
                _catchAction(ex)
            }            
        }
    }
    

    Then use it

    Func<Task> doSomeDynamicStaff = () => 
    {
        // Do something
    }
    var executor = new Executor()
        .With(StaticClass.DoAction1)
        .With(StaticClass.DoAction2)
        .With(StaticClass.DoAction3)
        .With(doSomeDynamicStaff)
        .CatchBy(Logger.Log);
    
    await executor.Run();