C# 链接操作
C# Chaining Actions
我想实现类似于如何链接任务的东西:
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();
});
我已经尝试实现一个工厂 class,它在继承自 Action class 的派生 class 上工作,但不幸的是 'Action' class是密封的,所以不会工作。
编辑:
我这样做的主要原因是我想记录每个包装操作的统计信息,并根据严重程度不同地处理错误。目前我正在通过执行一个具有以下签名的操作来做到这一点:
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...");
});
我认为一个一个执行所有任务并由 try ... catch
包装的方法就足够了,以防我正确理解你的要求。
public async Task Execute(IEnumerable<Func<Task>> actions, Action catchAction)
{
try
{
foreach (var action in actions)
{
await action();
}
}
catch (Exception ex)
{
catchAction(ex)
}
}
上述方法的使用者将负责按正确顺序创建任务集合。´
然而下一个要求似乎有点混乱
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)
如果不需要动作 运行 同步,那么您可以立即开始执行它们(大约“一次”)并观察它们的完成情况 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(ex)
}
}
}
那就用吧
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();
我想实现类似于如何链接任务的东西:
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();
});
我已经尝试实现一个工厂 class,它在继承自 Action class 的派生 class 上工作,但不幸的是 'Action' class是密封的,所以不会工作。
编辑:
我这样做的主要原因是我想记录每个包装操作的统计信息,并根据严重程度不同地处理错误。目前我正在通过执行一个具有以下签名的操作来做到这一点:
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...");
});
我认为一个一个执行所有任务并由 try ... catch
包装的方法就足够了,以防我正确理解你的要求。
public async Task Execute(IEnumerable<Func<Task>> actions, Action catchAction)
{
try
{
foreach (var action in actions)
{
await action();
}
}
catch (Exception ex)
{
catchAction(ex)
}
}
上述方法的使用者将负责按正确顺序创建任务集合。´
然而下一个要求似乎有点混乱
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)
如果不需要动作 运行 同步,那么您可以立即开始执行它们(大约“一次”)并观察它们的完成情况 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(ex)
}
}
}
那就用吧
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();