C# 记录类型作为受歧视的联合与第三方库

问题描述 投票:0回答:2

在一个项目中,我正在尝试使用记录来实现可区分的联合,以摆脱抛出异常以处理应用程序层的“预期”错误。添加第三方库似乎有点矫枉过正,所以我尝试自己滚动并以与此记录类似的内容结束:

public abstract record CreateCustomerResponse
{
    private CreateCustomerResponse() { }

    public sealed record Success(Customer Customer) : CreateCustomerResponse;

    public sealed record Error(string Code, string Message) : CreateCustomerResponse, IErrorResponse;

    public sealed record Unauthorized() : CreateCustomerResponse;
}

这基本上是一个抽象记录,除了它的子记录外不能被继承,这些子记录又被密封,限制了你可以拥有的结果类型。

它的实现方式与使用库的任何其他 DU 的实现方式没有太大区别:

static CreateCustomerResponse CreateCustomer(Customer customer)
{
    // Or do data validation however you prefer.
    if (string.IsNullOrEmpty(customer.FirstName))
        return new CreateCustomerResponse.Error(nameof(customer.FirstName), "First name is required");

    if (string.IsNullOrEmpty(customer.LastName))
        return new CreateCustomerResponse.Error(nameof(customer.LastName), "Last name is required");

    return new CreateCustomerResponse.Success(customer);
}

并且可以根据需要使用更新的 C# 功能(例如模式匹配)非常轻松地使用/转换它:

static string PrintResponse(CreateCustomerResponse response)
{
    return response switch
    {
        CreateCustomerResponse.Success result => $"OK, so {result.Customer.FirstName} was created",
        CreateCustomerResponse.Error => $"Sorry, operation failed: {response}",
        CreateCustomerResponse.Unauthorized => "You're unauthorized pal",
        _ => throw new NotImplementedException()
    };
}

我见过很多人使用第三方库(OneOf 和其他人)来完成类似的事情,但看起来很简单,不需要这个用例的库;它甚至允许使用模式匹配,因此您不需要“匹配”方法等来处理结果。

我发现的唯一问题是,如果不包含

switch
模式,
_
表达式认为不会涵盖所有情况(这是不正确的),但添加它不会造成伤害。然而我看到了同样的好处:你必须检查实际结果才能使用它,并且绑定到一组已知的选项。

所以问题是这样的:

我可能没有考虑到此实施中的任何明显缺点吗?在这种情况下不使用已知的第三方库,我是否遗漏了什么,这似乎被普遍接受?

非常感谢社区的意见。

c# .net record api-design discriminated-union
2个回答
0
投票

例如使用 OneOf 的优点是编译时检查是否处理了联合的每个可能值。

在您的实施中,如果您为

CreateCustomerResponse
添加另一个可能的值,没有什么可以阻止您在不更改 switch 表达式的情况下构建和运行程序,并且您最终可能会抛出
NotImplementedException
因为您忘记了处理它那里。

使用 OneOf,如果不处理刚刚添加到记录中的值,您甚至无法首先构建解决方案。


0
投票

这里的一个主要缺点是这些是类。现在每个结果都涉及堆分配 类型测试。那个可怜的垃圾收集者!

结构记录在这里可能工作得更好,也许有一些简单的访问器来检查结果(可以通过布尔值或枚举)。

当然这仍然在every结果中留下很多分支,而不是让成功案例无分支地运行,并且只在失败案例中进行错误处理(这可能很少见)。您仍然通常需要异常处理,因为这些不是唯一可能失败的方式,因此您添加了大量分支但没有删除任何故障处理。

© www.soinside.com 2019 - 2024. All rights reserved.