C#链接操作

时间:2017-08-31 05:00:09

标签: c# action chain

我希望实现类似于任务链接的方式:

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

但是,我不需要异步运行这些操作。我希望能够链接动作(执行第一个,然后是第二个,然后是第三个等),最重要的是,我想添加一个

.Catch((a) => { } 

当链中的任何操作引发错误时将执行的操作的动作。

最后,我希望它看起来像这样:

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

我已经尝试实现一个Factory类,它在从Action类继承的派生类上运行,但不幸的是'Action'类是密封的,因此无法工作。

修改

我的主要原因是我想记录每个包装操作的统计信息,并根据严重程度以不同方式处理错误。目前我通过执行一个具有以下签名的操作来执行此操作:

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;
    }
}

并像这样调用它:

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

在我看来,效果很好而且很容易阅读。现在我只想添加一个.Catch:

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

1 个答案:

答案 0 :(得分:3)

我认为逐个执行所有任务并由try ... catch包装的方法就足够了,以防我理解你的要求。

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

上述方法的消费者将负责以正确的顺序创建任务集合。“

然而,下一个要求似乎很少让人感到困惑

  

但是,我不需要同步运行这些操作。我想要   能够连锁行动(执行第一个,然后第二个,然后执行   第三,等等)

如果不需要同步运行操作,那么您可以立即开始执行所有操作(大约“一次”)并观察由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()
    }
}

回应关于可读性的评论

当然,将所有操作置于内联将违反任何以集合为参数的api的可读性。

通常你有一些实例或静态方法的方法,在这种情况下,调用看起来像

var actions = new Action[]
{
    StaticClass.Action1,
    SomeInstance.Action2,
    AnotherStaticClass.Action3
}

await Execute(actions, Logger.Log);

您可以使用的另一种方法是“构建器模式”

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

然后使用它

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