【问题标题】:C# 9 records validationC# 9 记录验证
【发布时间】:2020-11-11 10:09:18
【问题描述】:

使用C# 9的新记录类型,如何在对象的构造过程中注入自定义参数验证/空值检查/等无需重新编写整个构造函数

类似的东西:

record Person(Guid Id, string FirstName, string LastName, int Age)
{
    override void Validate()
    {
        if(FirstName == null)
            throw new ArgumentException("Argument cannot be null.", nameof(FirstName));
        if(LastName == null)
            throw new ArgumentException("Argument cannot be null.", nameof(LastName));
        if(Age < 0)
            throw new ArgumentException("Argument cannot be negative.", nameof(Age));
    }
}

【问题讨论】:

  • 如果您使用可空类型,则不需要空检查。如果要在构造时验证参数,请使用自定义构造函数而不是生成的构造函数。除此之外,验证的工作方式与任何其他类相同,例如通过数据注释、验证器等
  • 我不确定这与记录类型有什么关系?
  • @PanagiotisKanavos 使用 NRT 时您仍然需要添加空检查——人们仍然可以通过 null,例如来自不知道为空的代码,或使用!
  • 请注意,with { .. } 仅适用于 init 的属性,因此如果您定义自己的构造函数,您将失去使用 withers 的能力

标签: c# validation c#-9.0 c#-record-type


【解决方案1】:

如果你可以不用位置构造函数,你可以在每个需要它的属性的init 部分完成验证:

record Person
{
    private readonly string _firstName;
    private readonly string _lastName;
    private readonly int _age;
    
    public Guid Id { get; init; }
    
    public string FirstName
    {
        get => _firstName;
        init => _firstName = (value ?? throw new ArgumentException("Argument cannot be null.", nameof(value)));
    }
    
    public string LastName
    {
        get => _lastName;
        init => _lastName = (value ?? throw new ArgumentException("Argument cannot be null.", nameof(value)));
    }
    
    public int Age
    {
        get => _age;
        init =>
        {
            if (value < 0)
            {
                throw new ArgumentException("Argument cannot be negative.", nameof(value));
            }
            _age = value;
        }
    }
}

否则,您将需要创建一个自定义构造函数,如上面评论中所述。

(顺便说一句,考虑使用ArgumentNullExceptionArgumentOutOfRangeException 而不是ArgumentException。它们继承自ArgumentException,但更具体地说明了发生的错误类型。)

(Source)

【讨论】:

  • “如果你可以不用位置构造函数” ...还有解构函数。
  • @Pang 当然你也可以加上构造器和解构器,你还有'with'语法的附加好处。
  • 这不会替换构造函数:FirstName 在构造对象中可以为 null,如果它没有被设置,构造函数排除了这种可能性。有没有办法强制记录运行验证器(逻辑上)(例如,通过将default 显式分配给调用代码未分配的任何属性)?
【解决方案2】:

以下也实现了它并且更短(而且imo也更清晰):

record Person (string FirstName, string LastName, int Age, Guid Id)
{
    private bool _dummy = Check.StringArg(FirstName)
        && Check.StringArg(LastName) && Check.IntArg(Age);

    internal static class Check
    {
        static internal bool StringArg(string s) {
            if (s == "" || s == null) 
                throw new ArgumentException("Argument cannot be null or empty");
            else return true;
        }

        static internal bool IntArg(int a) {
            if (a < 0)
                throw new ArgumentException("Argument cannot be negative");
            else return true;
        }
    }
}

如果有办法摆脱虚拟变量就好了。

【讨论】:

  • 见 String.IsNullOrEmpty
  • 您还应该将参数名称传递给异常。对于 C# 10,请考虑使用 CallerArgumentExpressionAttribute
  • 这真的很聪明,并且避免了添加所有这些显式属性的需要。谢谢
  • 另外,您实际上可以将常用的验证方法移动到单独的record 中并让Person 继承自它,这将使Person 的定义更短。
猜你喜欢
  • 1970-01-01
  • 2021-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-05
  • 2017-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多