Sim*_*tes 20 c# validation c#-9.0 c#-record-type
使用 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));
}
}
Run Code Online (Sandbox Code Playgroud)
Tho*_*que 21
我参加聚会迟到了,但这可能仍然对某人有帮助......
实际上有一个简单的解决方案(但请在使用之前阅读下面的警告)。像这样定义基本记录类型:
public abstract record RecordWithValidation
{
protected RecordWithValidation()
{
Validate();
}
protected virtual void Validate()
{
}
}
Run Code Online (Sandbox Code Playgroud)
并使您的实际记录继承RecordWithValidation并覆盖Validate:
record Person(Guid Id, string FirstName, string LastName, int Age) : RecordWithValidation
{
protected 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));
}
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,这几乎就是OP的代码。这很简单,而且很有效。
但是,如果您使用它,请务必小心:它仅适用于使用“位置记录”语法(也称为“主构造函数”)定义的属性。
原因是我在这里做了一些“坏”的事情:我从基本类型的构造函数调用虚拟方法。通常不鼓励这样做,因为基类型的构造函数在派生类型的构造函数之前运行,因此派生类型可能未完全初始化,因此重写的方法可能无法正常工作。
但对于位置记录,事情不会按这个顺序发生:首先初始化位置属性,然后调用基类型的构造函数。因此,当Validate调用该方法时,属性已经初始化,因此它可以按预期工作。
如果您要将Person记录更改为具有显式构造函数(或仅初始化属性而无构造函数),则调用Validate将在设置属性之前发生,因此会失败。
编辑:这种方法的另一个恼人的限制是它不能与with(例如person with { Age = 42 })一起使用。这使用了不同的(生成的)构造函数,它不会调用Validate...
Aik*_*Aik 16
您可以在初始化期间验证该属性:
record Person(Guid Id, string FirstName, string LastName, int Age)
{
public string FirstName {get;} = FirstName ?? throw new ArgumentException("Argument cannot be null.", nameof(FirstName));
public string LastName{get;} = LastName ?? throw new ArgumentException("Argument cannot be null.", nameof(LastName));
public int Age{get;} = Age >= 0 ? Age : throw new ArgumentException("Argument cannot be negative.", nameof(Age));
}
Run Code Online (Sandbox Code Playgroud)
https://sharplab.io/#gist:5bfbe07fd5382dc2fb38ad7f407a3836
下面的代码也实现了它,并且更短(而且我认为也更清晰):
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果有一种方法可以消除虚拟变量就好了。
如果您可以在没有位置构造函数的情况下生活,您可以在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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
否则,您将需要创建上面评论中提到的自定义构造函数。
(顺便说一句,请考虑使用ArgumentNullExceptionandArgumentOutOfRangeException而不是ArgumentException。这些继承自ArgumentException,但对于发生的错误类型更具体。)
这里的其他答案都非常好,但是with据我所知,都没有涵盖操作员。我需要确保我们不能在域模型上输入无效状态,并且我们喜欢在适用的情况下使用记录。我在寻找最佳解决方案时偶然发现了这个问题。我已经为不同的场景和解决方案创建了一堆测试,但是大多数都因with表达式而失败。我尝试过的变体可以在这里找到: https: //gist.github.com/C0DK/d9e8b99deca92a3a07b3a82ba4a6c4f8
我最终采用的解决方案是:
public record Foo(int Value)
{
private readonly int _value = GetValidatedValue(Value);
public int Value
{
get => _value;
init => _value = GetValidatedValue(value);
}
private static int GetValidatedValue(int value)
{
if (value < 0) throw new Exception();
return value;
}
}
Run Code Online (Sandbox Code Playgroud)
遗憾的是,您目前需要两者来处理更新/创建记录的两种方式。
| 归档时间: |
|
| 查看次数: |
1822 次 |
| 最近记录: |