Как связать методы в .net с помощью async / await - PullRequest
0 голосов
/ 10 октября 2018

Я начал изучать функциональное программирование, и хотя методы цепочки выглядят великолепно (на мой взгляд) в нормальных случаях, он действительно становится уродливым при работе с async / await

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

Есть ли способудалить этот шум?

Редактировать:

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)
...

Редактировать2: с помощью методов расширений, принимающих задачу

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

Так вот, что я искал, даже если япутать с последними комментариями

1 Ответ

0 голосов
/ 10 октября 2018

Весь представленный код я загрузил в виде запроса LinqPad , так что вы можете попробовать его прямо сейчас.

Функциональное программирование имеет концепцию монады (незнакомые программисты на C # настоятельно рекомендуем начать с предоставленной ссылки).Задание AC # можно считать монадой, и, насколько я понимаю, это именно то, что Вам нужно.

Для целей этого ответа я сделал упрощенный пример того, что у Вас есть:

await (await (await A.GetNumber()).DoubleIt()).SquareIt()

где методы следующие (для моего удобства они определены как статические):

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

Теперь вы можете легко связать их цепочкой с помощью небольшого количества клея, который может выглядеть следующим образом:

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

Метод AndThen действует точно так же, как монадное связывание:

await 
    A.GetNumber()
    .AndThen(A.DoubleIt)
    .AndThen(A.SquareIt)

Что более важно, C # имеет хороший синтаксис для работы с монадами: синтаксис понимания запросов 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));
}

С ним вы можете использовать синтаксис:

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

И вы получите number: 3 doubled: 6, squared: 9.

Простая версия SelectMany позволит вам использовать squared в качестве единственно возможного выражения в последней строке select.Версия "hardcodre" позволяет использовать любое выражение, которое использует любое из значений, определенных после ключевого слова from.

...