【问题标题】:In C#, how can I consume an instance of `TryAsync`?在 C# 中,如何使用 `TryAsync` 的实例?
【发布时间】:2021-11-19 19:28:01
【问题描述】:

我有以下方法:

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

这可以编译,但我不知道如何使用结果,要么做接下来的任何事情,要么记录异常。

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

【问题讨论】:

  • 为什么要“使用 TryAsync”?我的意思是,如果您不知道它的用途,那么为什么首先要使用它?
  • 鉴于TryAsync 本身返回一个Task ...为什么?无论您的意思是什么,这都不会使您的代码更具“功能性”。使用 Result 类要有趣得多。在这种情况下,您应该询问如何创建和使用 Result 类。在 C# 7 及更高版本中,您将使用模式匹配
  • @Evk,我知道它的用途,我只是不知道如何在 C# 中使用它。
  • @PanagiotisKanavos,您能演示一下吗?谢谢。

标签: c# language-ext


【解决方案1】:

当前在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 上抛出异常,在这种情况下这不是你所期望的,你期望结果是失败状态。因此,虽然您返回的内容与 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);
}

现在上面的代码消费它不会抛出但返回预期的失败结果:

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

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

编辑:没有必要立即解开TryAsync 的结果,您可以对TryAsync 本身执行各种操作,因为它是一个单子。既然你有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;
        }
    }
}

【讨论】:

  • 感谢您的冗长回复。与@Sweeper 的上述方法相比,是否有任何理由喜欢或不喜欢这种方法? (也许我需要了解更多关于“代表”的信息?)
  • Sweeper 答案没有说明您如何构造TryAsync,它假定您的Relay 方法已经返回TryAsync(因为它确实如此,即使它是错误的)。所以我们的答案不包含不同的方法。
  • 是的,在你的情况下是:TryAsync(async () =&gt; { ... return await _attachmentController...}) 所以基本上和你之前做的一样,但是将异步委托传递给库提供的TryAsync 静态方法。
  • 但是TryAsync 是一个单子。你有一些Task&lt;T&gt;,你可以用答案中描述的方式(通过调用TryAsync() 方法)将该值包装在TryAsync monad 中。然后,您可以根据需要像使用任何 monad 一样对其进行操作,例如 TryAsync 值的扩展方法 Bind。我的意思是获得结果并不是您继续进行的唯一方法。
  • 是的,您所描述的是一个特定的单子示例,这个概念本身比这更广泛。我已经用一个类似于你可以在 scala 中使用Try 执行的示例更新了答案。
【解决方案2】:

TryAsyncdeclaration 是:

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

所以你可以这样做:

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

并获得Result&lt;bool&gt;。有很多方法可以使用Result。例如

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

【讨论】:

  • 这看起来很有希望。看起来我需要进一步修改 Relay 方法以使其适合,但我知道如何做到这一点。 :-)
  • Relay 方法的内部结构,它会返回 Result&lt;bool&gt;,而我上面的方法没有。
猜你喜欢
  • 2017-06-22
  • 2021-11-11
  • 2012-04-27
  • 2021-11-20
  • 2021-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多