如何以更优雅的方式处理任务结果。
#编程 #教程 #dotnet #c

我敢肯定,许多开发人员在工作中使用“异步/等待”方法。当然,需要处理错误。我将用示例解释如何以正确的方式进行操作。最重要的是,让我们创建一个简单的控制台应用程序:

public static class Program
{
    public static async Task Main()
    {
        var result = await MakeRequestGet();
        Console.WriteLine(result);
    }

    private static async Task<string> MakeRequestGet()
    {
        var client = new HttpClient();
        var result = await client.GetStringAsync("https://www.iana.org/domains/example");
        return result;
    }
}

您可以看到,我调用异步HTTP请求,对于本文,它将以403代码返回错误。通常,以这种方式的代码没有人写,因为您会得到例外,并且该应用程序将被停止。通常,此类调用包裹在try•catch块中。而且,如果您创建了许多类似的方法,则应将其添加到此块的每种方法中。我认为这项工作使副驾驶员产生了很多可重复的代码。该代码将起作用,但这是不好的做法:

public static class Program
{
    public static async Task Main()
    {
        var result = await MakeRequestGet();
        Console.WriteLine(result);
    }

    private static async Task<string> MakeRequestGet()
    {
        try
        {
            var client = new HttpClient();
            var result = await client.GetStringAsync("https://www.iana.org/domains/example");
            return result;
        }
        catch (Exception e)
        {
            //you can use your favorite logger
                        Console.WriteLine(e);
            return string.Empty;
        }
    }
}

我想改进此代码,使其更优雅,更好。首先,让我们创建自己的结果对象,以保持结果和错误。我创建了非传播和通用类。

public class Result
{
    protected readonly Exception? ExceptionError;

    public bool Success { get; }

    public string Message => ExceptionError?.Message ?? string.Empty;

    protected Result(bool success, Exception? exceptionError)
    {
        Success = success;
        ExceptionError = exceptionError;
    }

    public Exception GetError() => ExceptionError
        ?? throw new InvalidOperationException($"Error property for this Result not set.");

    public static Result Ok => new(true, null);

    public static Result Error(Exception error)
    {
        return new Result(false, error);
    }

    public static implicit operator Result(Exception exception) =>
        new(false, exception);
}

public sealed class Result<T> : Result
    where T : class
{
    private readonly T? _payload;

    private Result(T? payload, Exception? exceptionError, bool success) : base(success, exceptionError)
    {
        _payload = payload;
    }

    public Result(T payload) : base(true, null)
    {
        _payload = payload ?? throw new ArgumentNullException(nameof(payload));
    }

    private Result(Exception error) : base(false, error)
    {
    }

    public T GetOk() => Success
        ? _payload ?? throw new InvalidOperationException($"Payload for Result<{typeof(T)}> was not set.")
        : throw new InvalidOperationException($"Operation for Result<{typeof(T)}> was not successful.");

    public new Exception GetError() => ExceptionError
        ?? throw new InvalidOperationException($"Error property for Result<{typeof(T)}> not set.");

    public new static Result<T> Ok(T payload)
    {
        return new Result<T>(payload, null, true);
    }

    public new static Result<T> Error(Exception error)
    {
        return new Result<T>(null, error, false);
    }

    public static implicit operator Result<T>(T payload) =>
        new(payload, null, true);

    public static implicit operator Result<T>(Exception exception) =>
        new(exception);
}

下一步,让我们创建一个助手来处理任务。还有两个版本的处理程序 - 非传播和通用。

public static class Helper
{

    public static async Task<Result> TryAwait(
        this Task task,
        Action<Exception> errorHandler = null
    ) {
        try {
            await task;
            return Result.Ok;
        }
        catch (Exception ex)
        {
            if (errorHandler is not null) errorHandler(ex);
            return ex;
        }
    }

    public static async Task<Result<T>> TryAwait<T>(
        this Task<T> task,
        Action<Exception> errorHandler = null
    ) where T : class {
        try {
            return await task;
        }
        catch (Exception ex)
        {
            if (errorHandler is not null) errorHandler(ex);
            return ex;
        }
    }
}

让我们检查一下并重写我们的方法:

    private static async Task<Result> MakeRequestGet()
    {
        var client = new HttpClient();
        var result = await client.GetStringAsync("https://www.iana.org/domains/example").TryAsync();

        return result;
    }

,还对主要方法进行了较小的更改:

    public static async Task Main()
    {
        var result = await MakeRequestGet();
        Console.WriteLine(result.Message);
    }

最后,您将检索与以前的代码相同的结果,但是没有尝试仅创建一次的catch块。您还可以添加自己喜欢的记录仪,但是您需要在代码中进行一些更改:

    private static async Task<Result> MakeRequestGet()
    {
        var client = new HttpClient();
        var result = await client.GetStringAsync("https://www.iana.org/domains/example").TryAsync(x =>
        {
            Console.WriteLine(x.Source);
        });
        return result;
    }

您可以看到,此代码更可读和清洁。另外,您可以根据需要更改结果类。

作为奖励,如果您不需要回调,我想展示如何处理void方法。但是,在空隙方法中也可能是例外,您应该处理它。让我们返回到帮手类,添加另一种方法。在这种方法中,我得到了处理异常的ilogger。

    public static async Task TryAwait(
        this Task task,
        ILogger logger = null
    )
    {
        try
        {
            await task;
        }
        catch (Exception ex)
        {
            if(logger is not null) logger.Log(LogLevel.Error, ex.Message);
        }
    }

我们需要安装软件包并进行注册以使用此扩展名。让我们添加此代码:

    private static ILogger<Program>? _logger;

    public static async Task Main()
    {
        SetLogger();
        await MakeRequestGet();
    }       

    private static void SetLogger()
    {
        var services = new ServiceCollection();
        services.AddLogging(builder =>
        {
            builder.AddConsole();
        });
        var serviceProvider = services.BuildServiceProvider();
        _logger = serviceProvider.GetService<ILogger<Program>>();
    }

,还需要对此方法进行一些更改:

    private static async Task MakeRequestGet()
    {
        var client = new HttpClient();
        await client.GetStringAsync("https://www.iana.org/domains/example").TryAwait(_logger);
    }

您可以看到,我将记录器调用到Tryawait方法中。当抛出异常时,记录器会显示您的消息。当然,您可以为您需要更改它。

仅此而已。我希望有人会有用。愉快的编码!

Buy Me A Beer