简体   繁体   English

C#链接动作

[英]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. 我已经尝试实现一个Factory类,该类在从Action类继承的派生类上工作,但不幸的是'Action'类是密封的,所以不能工作。

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: 现在我只想添加一个.Catch:

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

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. 我认为一个接一个地执行所有任务并且通过try ... catch包裹的方法就足够了,以防我正确理解你的要求。

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

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 如果不需要同步运行操作,那么您可以立即开始执行它们(大约“一次”)并通过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. 当然,将所有动作置于内联将违反任何以集合为参数的api的可读性。

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" 您可以使用的另一种方法是“Builder模式”

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()
        }            
    }
}

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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM