在 C# 中,如何使用 `TryAsync` 的实例?

In C#, how can I consume an instance of `TryAsync`?

我有以下方法:

private async Task<(bool, string)> Relay(
        WorkflowTask workflowTask,
        MontageUploadConfig montageData,
        File sourceFile,
        CancellationToken cancellationToken
    )
{
    try
    {
        byte[] fileContent = await _httpClient.GetByteArrayAsync(sourceFile.Url, cancellationToken);
        await _attachmentController.TryUploadAttachment(montageData.EventId, fileContent, sourceFile.Name);
        return (true, null);
    }
    catch (Exception exception)
    {
        _logger.LogError(exception, $"File cannot be uploaded: {sourceFile.Name}", workflowTask);
        return (false, exception.ToString());
    }
}

我想重构它以使用 LanguageExt.Core 中的 TryAsync(或其他一些功能性 Try 类型)。

我已经设法将上述方法重构为:

private TryAsync<bool> Relay(
    MontageUploadConfig montageData,
    File sourceFile,
    CancellationToken cancellationToken
) => new(async () =>
{
    byte[] fileContent = await _httpClient.GetByteArrayAsync(sourceFile.Url, cancellationToken);
    return await _attachmentController.TryUploadAttachment(montageData.EventId, fileContent, sourceFile.Name);
});

这可以编译,但我无法使用结果,无论是执行接下来的操作还是记录异常。

如何检查结果并对返回值或任何异常进行处理?

TryAsyncdeclaration是:

public delegate Task<Result<A>> TryAsync<A>();

所以你可以这样做:

var result = await Relay(...)()

并获得 Result<bool>。消费Result的方式有很多种。例如

result.IfFail(exception => { ... });
result.IfSucc(result => { ... });

当前在 Relay 中构造 TryAsync 的方法不正确。例如假设你有这个:

static async Task<string> MightThrow() {
    await Task.Delay(0);
    throw new Exception("test");
}

然后您按照现在的方式从中构建 TryAsync

static TryAsync<string> WrongTryAsync() {
    return new (async () => {
        return await MightThrow();
    });
}

现在如果你尝试食用这个会发生什么?

var tryAsync = WrongTryAsync();
var result = await tryAsync();

它会在 await 上抛出异常,这不是您在这种情况下所期望的,您期望的结果是失败状态。因此,虽然您 return 匹配 TryAsync 签名 - 这还不够。

库有一种以预期方式构造TryAsync的方法:

using static LanguageExt.Prelude; // not necessary but convenient
...
static TryAsync<string> CorrectTryAsync() {
    return TryAsync(MightThrow);
    // use static call if you didn't add using static above
    // return LanguageExt.Prelude.TryAsync(MightThrow);
}

现在上面使用它的代码不会抛出但 return 预期的失败结果:

var tryAsync = CorrectTryAsync();
var result = await tryAsync();
// result.IsFaulted == true;

因此,在构建正确的 TryAsync 之后 - 您可以通过执行委托(await ...())从中获取结果,然后使用结果,或者使用 TryAsync 本身,因为库直接为它提供了相当多的扩展方法。

编辑:不必立即解包 TryAsync 的结果,您可以对 TryAsync 本身执行各种操作,因为它是一个 monad。由于您有 scala 背景,我们可以查看 this example 并做类似的事情。

using System;
using System.Threading.Tasks;
using static LanguageExt.Prelude;

namespace ConsoleApp4 {
    class Program {
        static async Task Main(string[] args) {
            await Test();
            Console.ReadKey();
        }

        static async Task Test() {
            // we have two async methods which return ints, and we want to divide them, but any of them might throw
            // we want to do that in a functional fashion
            // I use varibles for clarity, you can combine all this into one function call chain.
            var dividend = TryAsync(GetDividend);
            var divisor = TryAsync(GetDivisor);

            // now apply the division function to the (wrapped) values of our TryAsync instances 
            var result = apply((a, b) => a / (float)b, dividend, divisor);
            // decide what to do on success or on failure and execute
            await result
                .Match(
                    r => Console.WriteLine($"Success, result is: {r}"),
                    ex => Console.WriteLine($"Failed to compute, exception was: {ex}")
                );
        }

        static async Task<int> GetDividend() {
            await Task.Delay(0);
            // or might throw
            return 10;
        }

        static async Task<int> GetDivisor() {
            await Task.Delay(0);
            return 5;
        }
    }
}