Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have the following method:

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

I'd like to refactor it to use TryAsync from LanguageExt.Core (or some other functional Try type).

I've managed to refactor the above method to:

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

This compiles, but I haven't been able how to consume the result, either doing whatever comes next or logging the exception.

How can I check the result and either do something with either the returned value or any exceptions?

like image 540
Brian Kessler Avatar asked Dec 19 '25 16:12

Brian Kessler


2 Answers

The declaration of TryAsync is:

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

So you can just do:

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

And get a Result<bool>. There are many ways to consume a Result. e.g.

result.IfFail(exception => { ... });
result.IfSucc(result => { ... });
like image 68
Sweeper Avatar answered Dec 21 '25 05:12

Sweeper


The current way of constructing TryAsync inside Relay is not correct. For example suppose you have this:

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

And you construct TryAsync from it the way you are doing now:

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

Now what would happen if you try to consume this?

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

It will throw an exception on await, this is not what you expect in such case, you expect result in a failed state instead. So while what you return matches TryAsync signature - that's not enough.

The library has a way to construct TryAsync in the expected way:

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

Now the code above consuming it will not throw but return expected failed result:

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

So after you constructed correct TryAsync - you can either obtain the result from it by executing the delegate (await ...()) and then work with result, or work with TryAsync itself, because library provides quite some extension methods for it directly.

Edit: it's not necessary to unwrap the result of TryAsync right away, you can perform various operations on TryAsync itself, since it's a monad. Since you have scala background, we can look at this example and do something similar.

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;
        }
    }
}
like image 28
Evk Avatar answered Dec 21 '25 07:12

Evk



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!