为了账号安全,请及时绑定邮箱和手机立即绑定

C#中的结果模式:一种更简洁的错误处理方式

标签:
C#

异常是控制流程中的传统做法。

如果出现错误,创建并抛出一个异常。

把这个剩下的交给高级函数。

这种方法虽然有效,但也存在一些缺点。

高层函数最好知道底层函数的工作原理。

当你不停地加入特例时,代码就会变得杂乱冗长。

实际上,使用异常会增加成本并影响性能。

连调试都更难了。

    public double GetCustomerDiscount(Guid customerId, double price)  
    {  
        try  
        {  
            var customer = GetCustomer(customerId);  

            return customer.DiscountRate * price;  
        }  
        catch (CustomerNotFoundException ex)  
        {  
            // 处理一下异常  
        }  
        catch (Exception ex)  
        {  
            // 进行一些处理  
        }  
    }
    public Customer 根据ID查找客户(Guid 客户ID)  
    {  
        try  
        {  
            var 客户 = context.Customers.FirstOrDefault(c => c.CustomerId == 客户ID);  
            if (客户 is null)  
            {  
                return new CustomerNotFoundException();  
            }  

            return 客户;  
        }  
        catch(Exception ex)  
        {  
            logger.LogError(ex.Message.ToString());  
            throw;  
        }  
    }

正如你所见,高层函数才能更好地了解底层实现的细节。

    public double GetCustomerDiscount(Guid customerId, Guid productId)  
    {  
        try  
        {  
            var customer = GetCustomer(customerId);  
            var product = GetProduct(productId);  

            return customer.折扣率 * product.Price;  
        }  
        catch (CustomerNotFoundException ex)  
        {  
            // 处理异常  
        }  
        catch (ProductNotFoundException ex)  
        {  
            // 处理特殊情况  
        }  
        catch (Exception ex)  
        {  
            // 处理异常  
        }  
    }
    public Product GetProduct(Guid productId)  
    {  
        try  
        {  
            var product = context.Products.FirstOrDefault(p => p.ProductId == productId);  
            if (product == null)  
            {  
                return new ProductNotFoundException();  
            }  

            return product;  
        }  
        catch (Exception ex)  
        {  
            logger.LogError(ex.Message.ToString());  
            throw;  
        }  
    }

稍微改一下代码,你会发现它变长且更难打理了。

幸運的是,我們有一個替代的控制流方法,叫做結果匹配模式。

public abstract record 结果模型(string 信息);  

public record 错误(string 信息) : 结果模型(信息);  

public record 警告(string 信息) : 结果模型(信息);
    public class Result  
    {  
        public bool Is成功 => Errors.Count == 0;  
        public bool Is失败 => !Is成功;  

        public List<Error> Errors { get; protected set; } = [];  
        public List<Warning> Warnings { get; protected set; } = [];  

        protected Result(List<Error> errors, List<Warning> warnings)  
        {  
            Errors = errors;  
            Warnings = warnings;  
        }  

        public static Result Ok()  
        {  
            return new Result([], []);  
        }  

        public static Result Fail(params List<Error> errors)  
        {  
            var validErrors = errors.Where(e => e is not null).ToList();  
            如果有效错误列表为空  
            {  
                validErrors.Add(new Error("没有指定任何错误信息的失败结果被创建"));  
            }  

            return new Result(validErrors, []);  
        }  

        public static implicit operator Result(Error error)  
        {  
            return Fail(error);  
        }  

        public static implicit operator Result(List<Error> errors)  
        {  
            return Fail(errors);  
        }  

        public void Add(ResultBaseModel model)  
        {  
            如果模型是错误对象  
            {  
                Errors.Add((model as Error)!);  

                return;  
            }  

            如果模型是警告对象  
            {  
                Warnings.Add((model as Warning)!);  

                return;  
            }  
        }  

        public void AddRange(IReadOnlyList<ResultBaseModel> models)  
        {  
            foreach (var model in models)  
            {  
                Add(model);  
            }  
        }  
    }
    public sealed class Result<T> : Result where T : notnull  
    {  
        public T? Payload { get; } = default;  

        private Result(T payload) : base([], [])  
        {  
            Payload = payload;  
        }  

        private Result(List<Error> 错误, List<Warning> 警告) : base(错误, 警告)  
        {  
        }  

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

        public static new Result<T> Fail(params List<Error> errors)  
        {  
            var 有效错误 = errors.Where(e => e 不是 null).ToList();  
            if (有效错误.Count == 0)  
            {  
                有效错误.Add(new Error("失败结果是在没有任何错误的情况下创建的."));  
            }  

            return new Result<T>(有效错误, []);  
        }  

        public static implicit operator Result<T>(T payload)  
        {  
            return new Result<T>(payload);  
        }  

        public static implicit operator Result<T>(Error error)  
        {  
            return Result<T>.Fail(error);  
        }  

        public static implicit operator Result<T>(List<Error> errors)  
        {  
            return Result<T>.Fail(errors);  
        }  
    }

还有额外的:

        public static void Merge(this Result result, params Result[] results)  
        {  
            var errors = results.SelectMany(r => r.Errors).ToList();  
            result.AddRange(errors);  

            var warnings = results.SelectMany(r => r.Warnings).ToList();  
            result.AddRange(warnings);  
        }  

        public static void MergeErrorsAsWarnings(this Result result, params Result[] results)  
        {  
            var errors = results.SelectMany(r => r.Errors).ToList();  
            result.AddRange(errors.Select(x => new Warning(x.Message)).ToList());  

            var warnings = results.SelectMany(r => r.Warnings).ToList();  
            result.AddRange(warnings);  
        }  

        public static Result<T> ToResult<T>(this Result result) where T : notnull  
        {  
            var resultT = Result<T>.Fail([.. result.Errors]);  
            resultT.AddRange(result.Warnings);  

            return resultT;  
        }

我们现在准备好使用结果模式(Result Pattern)了。

    public Result<double> 获取客户折扣率(Guid 客户ID, Guid 产品ID)  
    {  
        var 客户结果 = 获取客户(客户ID);  
        if (客户结果.IsFailed)  
        {  
            return 客户结果.Errors;  
        }  

        var 产品结果 = 获取产品(产品ID);  
        if (产品结果.IsFailed)  
        {  
            return 产品结果.Errors;  
        }  

        var 折扣率 = 客户结果.Payload!.DiscountRate;  
        var 价格 = 产品结果.Payload!.Price;  

        return 折扣率 * 价格; // 返回折扣后的价格  
    }
    public Result<Customer> GetCustomer(Guid 客户Id)  
    {  
        try  
        {  
            var 客户 = context.Customers.FirstOrDefault(c => c.CustomerId == 客户Id);  
            if (客户 is null)  
            {  
                return new Error($"未找到ID为'{客户Id}'的客户。");  
            }  

            return 客户;  
        }  
        catch(Exception ex)  
        {  
            return new Error($"获取ID为'{客户Id}'的客户时出错。错误消息: '{ex}'.");  
        }  
    }
    public Result<Product> GetProduct(Guid productId)  
    {  
        try  
        {  
            var product = context.Products.FirstOrDefault(p => p.ProductId == productId);  
            if (product is null)  
            {  
                return new Error($"找不到该ID为'{productId}'的产品。");  
            }  

            return product;  
        }  
        catch(Exception ex)  
        {  
            return new Error($"获取ID为'{productId}'的产品时出错。错误信息:'{ex.Message}'.");  
        }  
    }

如果有任何疑问,请在下方留言。

不必复制代码。

代码库地址在这里:https://github.com/dcyuksel/Result

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消