【问题标题】:Elegant way to validate values验证值的优雅方式
【发布时间】:2011-10-03 17:19:14
【问题描述】:

我有一个包含许多字段的类,它们代表不同的物理值。

class Tunnel
{
    private double _length;
    private double _crossSectionArea;
    private double _airDensity;
    //...

每个字段都使用读/写属性公开。我需要检查 setter 值是否正确,否则会生成异常。所有验证都是相似的:

    public double Length
    {
        get { return _length; }
        set
        {
            if (value <= 0) throw new ArgumentOutOfRangeException("value",
                    "Length must be positive value.");
            _length = value;
        }
    }

    public double CrossSectionArea
    {
        get { return _crossSectionArea; }
        set
        {
            if (value <= 0) throw new ArgumentOutOfRangeException("value",
                    "Cross-section area must be positive value.");
            _crossSectionArea = value;
        }
    }

    public double AirDensity
    {
        get { return _airDensity; }
        set
        {
            if (value < 0) throw new ArgumentOutOfRangeException("value",
                    "Air density can't be negative value.");
            _airDensity = value;
        }
    }
    //...

有没有优雅灵活的方式来完成这种验证?

【问题讨论】:

  • 我认为你的做法是正确的。唯一的变化可能是创建一个validator function,您可以在准备好时调用它一次检查所有的值。根据我的经验,Visual Studio(不知道你在用什么)似乎会吞下属性设置器中发生的异常。
  • @jp2code,setter 中的异常工作正常。刚刚检查过。

标签: c# .net validation


【解决方案1】:

假设您想要这种行为,您可能会考虑一些辅助方法,例如

public static double ValidatePositive(double input, string name)
{
    if (input <= 0)
    {
        throw new ArgumentOutOfRangeException(name + " must be positive");
    }
    return input;
}

public static double ValidateNonNegative(double input, string name)
{
    if (input < 0)
    {
        throw new ArgumentOutOfRangeException(name + " must not be negative");
    }
    return input;
}

然后你可以写:

public double AirDensity
{
    get { return _airDensity; }
    set
    {            
        _airDensity = ValidationHelpers.ValidateNonNegative(value,
                                                            "Air density");
    }
}

如果您需要它用于各种类型,您甚至可以将其设为通用:

public static T ValidateNonNegative(T input, string name)
    where T : IComparable<T>
{
    if (input.CompareTo(default(T)) < 0)
    {
        throw new ArgumentOutOfRangeException(name + " must not be negative");
    }
    return input;
}

请注意,这些都不是非常友好的 i18n...

【讨论】:

  • 感谢您改进代码。但我不明白你所说的“i18n-friendly”是什么意思......
  • @archer:消息被硬编码为英语。如果您想将它们翻译成不同的语言,您还有一些工作要做。
  • 好的。谢谢你的解释。
  • 老鼠!我喜欢这个比我刚写的小验证程序要好得多。我什至呼求你的名! :)
【解决方案2】:

一切都取决于您使用的是什么技术 - 如果您在 MVC 下,您可以使用属性,就像这样;

http://msdn.microsoft.com/en-us/library/ee256141(v=vs.98).aspx

【讨论】:

  • 我也不是 - MVC 属性同时处理客户端和服务器端验证(请参阅 Model.IsValid 方法)
【解决方案3】:

这是我的版本,在某些方面比 Jon 的版本更干净:

interface IValidator <T>
{
  bool Validate (T value);
}

class IntValidator : IValidator <int>
{
  public bool Validate (int value)
  {
    return value > 10 && value < 15;
  }
}
class Int2Validator : IValidator<int>
{
  public bool Validate (int value)
  {
    return value > 100 && value < 150;
  }
}

struct Property<T, P> where P : IValidator<T>, new ()
{
  public T Value
  {
    set
    {
      if (m_validator.Validate (value))
      {
        m_value = value;
      }
      else
      {
        Console.WriteLine ("Error validating: '" + value + "' is out of range.");
      }
    }

    get { return m_value; }
  }

  T m_value;
  static IValidator<T> m_validator=new P();
}

class Program
{
  static void Main (string [] args)
  {
    Program
      p = new Program ();

    p.m_p1.Value = 9;
    p.m_p1.Value = 12;
    p.m_p1.Value = 25;
    p.m_p2.Value = 90;
    p.m_p2.Value = 120;
    p.m_p2.Value = 250;
  }

  Property<int, IntValidator>
    m_p1;

  Property<int, Int2Validator>
    m_p2;
}

【讨论】:

    【解决方案4】:

    尝试使用这样的方法:

     public void FailOrProceed(Func<bool> validationFunction, Action proceedFunction, string errorMessage)
        {
            // !!! check for nulls, etc
            if (!validationFunction())
            {
                throw new ArgumentOutOfRangeException(errorMessage);
            }
    
            proceedFunction();
        }
    

    【讨论】:

      【解决方案5】:

      是的,通过创建您自己的验证属性。

      阅读这篇文章:Business Object Validation Using Attributes in C#

      我不会在这里复制它:)

      【讨论】:

        【解决方案6】:

        使用我在上面的评论中提到的 Validator 函数,我会做这样的事情(未经测试的代码):

        void textBox_Changed(object sender, EventArgs e) {
          submitButton.Enabled = validator();
        }
        
        bool validator() {
          const string NON_POSITIVE = "Value must be greater than Zero";
          bool result = false;
          string controlName = "Length";
          try {
            _length = Convert.ToDouble(txtLength.Text);
            if (_length <= 0) throw new Exception(NON_POSITIVE);
            controlName = "Cross Section Area";
            _crossSectionArea = Convert.ToDouble(txtCrossSectionArea.Text);
            if (_crossSectionArea <= 0) throw new Exception(NON_POSITIVE);
            controlName = "Air Density";
            _airDensity = Convert.ToDouble(txtAirDensity.Text);
            if (_airDensity <= 0) throw new Exception(NON_POSITIVE);
            result = true; // only do this step last
          } catch (Exception err) {
            MessageBox.Show(controlName + " Error: " + err.Message, "Input Error");
          }
          return result;
        }
        

        John Skeet 可能有更好的方法,但这很有效。 :)

        【讨论】:

          【解决方案7】:

          您可以使用 System.ComponentModel.DataAnnotations 中的类来实现此目的

          class Tunnel
          {
              [Range(0, double.MaxValue, ErrorMessage = "Length must be positive value.")]
              public double Length { get; set; }
          }
          

          验证:

          var tunnel = new Tunnel { Length = 0 };
          var context = new ValidationContext(tunnel, null, null);
          Validator.ValidateObject(tunnel, context, true);
          

          您还可以实现自己的验证属性,覆盖 ValidationAttribute

          【讨论】:

          • 这似乎肯定会引入一些开销?
          • @Skizz,这是使用属性的验证,仅此而已。我不喜欢在 set 访问器中实现验证。
          • 我得到的是属性和属性之间的运行时连接。也许 .net 在这里做了一些聪明的事情。
          • @Skizz,这取决于任务,但许多 .NET 框架,如 Entity Framework、WPF、ASP.NET MVC 等都使用反射。这种验证类似于简单的 AOP 版本。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-19
          • 1970-01-01
          • 1970-01-01
          • 2013-08-02
          相关资源
          最近更新 更多