【问题标题】:Validating properties of c# records验证 c# 记录的属性
【发布时间】:2022-03-15 20:25:20
【问题描述】:

record 是 C# 9 中的一种新对象类型。当需要不可变对象时,它可以节省大量输入。在某些情况下,可能需要验证记录的属性。在this question 中,提出了一种模式。该解决方案有效,但我认为我找到了一种更优雅的方式(请参阅我的回复)。我建议:

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;
        }
    }
}

这个想法是使用编译器生成的属性,如果有什么不对的话,有一些验证函数会抛出异常。不幸的是,有无意义的 _dummy 变量。有没有办法摆脱它?

【问题讨论】:

  • 将光标放在第一个{下方的最左侧位置,然后按住shift并按两次向下箭头,最后点击键盘上的delete或backspace
  • 没有写显式构造函数,没有。 (Ab)使用初始化器将一些代码注入到生成的构造器中必然需要初始化一个字段。
  • 此外,它是私有财产,因此无论使用该记录的任何人都不会意识到它的存在。
  • @MathiasR.Jessen - 非常有趣。现在请告诉我如何完成验证?
  • 当然,具有名为 _dummy 的私有字段的类与没有的类之间存在明显的差异,即使只是反射和(在很小程度上)垃圾收集器 - 而不是重写它以使用显式构造函数。是否值得被打扰是另一回事。我会说记录绝不是一个完成的功能。可以想象,未来的 C# 版本将使这种情况变得更容易,而无需 hack。

标签: c#


【解决方案1】:

这是一个绝妙的想法,它激发了我围绕这个想法实现一种基于扩展方法的验证框架。 我创建了以下验证类(为简洁起见删除了其他验证):

public static class Validation
{
    public static bool IsValid<T>(this T _)
    {
        return true;
    }
    public static T NotNull<T>(T @value, [CallerArgumentExpression("value")] string? thisExpression = default)
    {
        if (value == null) throw new ArgumentNullException(thisExpression);
        return value;
    }

    public static string LengthBetween(this string @value, int min, int max, [CallerArgumentExpression("value")] string? thisExpression = default)
    {
        if (value.Length < min) throw new ArgumentOutOfRangeException(thisExpression, $"Can't have less than {min} items");
        if (value.Length > max) throw new ArgumentOutOfRangeException(thisExpression, $"Can't have more than {max} items");
        return value;
    }

    public static IComparable<T> RangeWithin<T>(this IComparable<T> @value, T min, T max, [CallerArgumentExpression("value")] string? thisExpression = default)
    {
        if (value.CompareTo(min) < 0) throw new ArgumentOutOfRangeException(thisExpression, $"Can't have less than {min} items");
        if (value.CompareTo(max) > 0) throw new ArgumentOutOfRangeException(thisExpression, $"Can't have more than {max} items");
        return value;
    }
}

然后您可以将其与以下内容一起使用:

// FirstName may not be null and must be between 1 and 5
// LastName may be null, but when it is defined it must be between 3 and 10
// Age must be positive and below 200
record Person(string FirstName, string? LastName, int Age, Guid Id)
{
    private readonly bool _valid = Validation.NotNull(FirstName).LengthBetween(1, 5).IsValid() &&
        (LastName?.LengthBetween(2, 10).IsValid() ?? true) &&
        Age.RangeWithin(0, 200).IsValid();
        
}

?? true 非常重要,这是为了确保在可为空的 LastName 确实为空的情况下继续验证,否则它会短路。 也许使用另一个静态 AllowNull 方法来包装该变量的整个验证会更好(更安全),如下所示:

public static class Validation
{
    public static bool IsValid<T>(this T _)
    {
        return true;
    }
    public static bool AllowNull<T>(T? _)
    {
        return true;
    }
    public static T NotNull<T>(T @value, [CallerArgumentExpression("value")] string? thisExpression = default)
    {
        if (value == null) throw new ArgumentNullException(thisExpression);
        return value;
    }

    public static string LengthBetween(this string @value, int min, int max, [CallerArgumentExpression("value")] string? thisExpression = default)
    {
        if (value.Length < min) throw new ArgumentOutOfRangeException(thisExpression, $"Can't have less than {min} items");
        if (value.Length > max) throw new ArgumentOutOfRangeException(thisExpression, $"Can't have more than {max} items");
        return value;
    }

    public static IComparable<T> RangeWithin<T>(this IComparable<T> @value, T min, T max, [CallerArgumentExpression("value")] string? thisExpression = default)
    {
        if (value.CompareTo(min) < 0) throw new ArgumentOutOfRangeException(thisExpression, $"Can't have less than {min} items");
        if (value.CompareTo(max) > 0) throw new ArgumentOutOfRangeException(thisExpression, $"Can't have more than {max} items");
        return value;
    }
}

record Person(string FirstName, string? LastName, int Age, Guid Id)
{
    private readonly bool _valid = Validation.NotNull(FirstName).LengthBetween(1, 5).IsValid() &&
        Validation.AllowNull(LastName?.LengthBetween(2, 10)) &&
        Age.RangeWithin(0, 200).IsValid();
}

仍然不太喜欢那部分,但除此之外,我认为它很酷!虽然还没有实际测试过:)所以要小心!

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2010-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多