如何使用 async/await 链接 .net 中的方法

Xav*_* Sc 12 .net c# method-chaining async-await

我已经开始学习函数式编程,虽然在正常情况下链接方法看起来很棒(在我看来),但在处理 async/await 时它真的变得很难看

await (await (await CosmosDbRepository<ApplicationProcess>
    .GetItemAsync(param.ProcessId))
.Historize(() => _analyseFinanciereService.ProcessAsync(), 
    ProcessStepEnum.Application))
.Notify(p => p.GetLastStep());
Run Code Online (Sandbox Code Playgroud)

有什么办法可以消除这种噪音吗?

编辑 :

public static async Task<ApplicationProcess> Historize(
this ApplicationProcess process, 
Func<Task> fn, 
ProcessStepEnum stepEnum)
{
    var dateStart = DateTime.UtcNow;
    var error = string.Empty;
    try
    {
        await fn();
        return process;
    }
    …

public static async Task Notify<TResult>(
    this ApplicationProcess process, 
    Func<ApplicationProcess, TResult> fn)
...
Run Code Online (Sandbox Code Playgroud)

Edit2:使用扩展方法接受任务

await CosmosDbRepository<ApplicationProcess>
    .GetItemAsync(param.ProcessId)
    .HistorizeAsync(() => _analyseFinanciereService.ProcessAsync(), ProcessStepEnum.Application)
    .NotifyAsync(p => p.GetLastStep());
Run Code Online (Sandbox Code Playgroud)

所以这就是我一直在寻找的,即使我对最新的评论感到困惑

Grz*_*cki 14

我上传的所有代码都是作为LinqPad 查询上传的,因此您可以立即尝试。

函数式编程有一个monad的概念(我强烈建议不熟悉的 C# 程序员从提供的链接开始)。AC# 任务可能被认为是一个 monad,据我所知,它正是您所需要的。

为了这个答案的目的,我做了一个你所拥有的简化示例:

await (await (await A.GetNumber()).DoubleIt()).SquareIt()
Run Code Online (Sandbox Code Playgroud)

其中方法如下(为了方便起见,定义为静态):

public static class A
{
    public static Task<int> GetNumber(){return Task.FromResult(3);}
    public static Task<int> DoubleIt(this int input){return Task.FromResult(2 * input);}
    public static Task<int> SquareIt(this int input){return Task.FromResult(input * input);}
}
Run Code Online (Sandbox Code Playgroud)

现在您只需用一点胶水就可以轻松地将它们链接起来,如下所示:

public static async Task<TOut> AndThen<TIn, TOut>(this Task<TIn> inputTask, Func<TIn, Task<TOut>> mapping)
{
    var input = await inputTask;
    return (await mapping(input));
}
Run Code Online (Sandbox Code Playgroud)

AndThen方法的行为与 monadic 绑定完全一样:

await A
     .GetNumber()
     .AndThen(A.DoubleIt)
     .AndThen(A.SquareIt)
Run Code Online (Sandbox Code Playgroud)

更重要的是,C# 有很好的语法来处理 monad:LINQ 查询理解语法。您只需要定义一个 SelectMany 方法,该方法适用于您想要的类型(在本例中为 Task),您就可以开始使用了。

下面我实现了 SelectMany 最“核心”的重载(带有附加resultSelector),它为您提供了最大的灵活性。简单版本几乎与AndThen(我认为只需重命名即可)完全相同。

public static async Task<TOut> SelectMany<TIn, TInterm, TOut>(
   this Task<TIn> inputTask,
   Func<TIn, Task<TInterm>> mapping,
   Func<TIn, TInterm, TOut> resultSelector)
{
    var input = await inputTask;
    return resultSelector(input, await mapping(input));
}
Run Code Online (Sandbox Code Playgroud)

有了它,您可以使用以下语法:

var task = 
    from num in A.GetNumber()
    from doubled in num.DoubleIt()
    from squared in num.SquareIt()
    select $"number: {num} doubled: {doubled}, squared: {squared}";
    
Console.WriteLine(await task);
Run Code Online (Sandbox Code Playgroud)

你得到number: 3 doubled: 6, squared: 9.

简单的 SelectMany 版本将允许您squared在最后select一行中用作唯一可能的表达式。“硬编码”版本允许您使用任何使用from关键字后定义的任何值的表达式。