编写支持流畅接口的C#方法参数验证的问题(调用链接)

时间:2011-03-07 11:04:31

标签: validation c#-3.0 extension-methods method-chaining

我正在尝试编写一个通用的方法参数验证功能,可以链接(流畅的界面)来附加越来越多的验证/检查,如:

public void SomeMethod(User user, string description)
{
    ParameterHelper
        .Create(() => user)
        .RejectNull();
    ParameterHelper
        .Create(() => description)
        .RejectNull()
        .RejectEmptyString();

    // now this would be luxurious
    ParameterHelper
        .Create(() => new { user = user, desc = description })
        .RejectNull(o => o.user)
        .RejectNull(o => o.desc)
        .RejectEmptyString(o => o.desc);
}

我想在使用它们之前使用这个辅助类来测试某些值的方法参数(大多数时候null将被测试)。

当前事态

我首先开始编写静态助手类而不使用Create()方法,如:

public static class ParameterHelper
{
    public static void RejectNull(Expression<Func<T>> expr)
    {
        if (expr.Compile()().Equals(default(T)))
        {
            MemberExpression param = (MemberExpression)expr.Body;
            throw new ArgumentNullException(param.Member.Name);
        }
    }
}

但这不允许链接。这就是为什么我创建了Create()方法,它会返回可以被链式扩展方法使用的东西。

问题

  1. 我想避免多次Compile()次调用,所以基本上我的Create()方法应该返回Func<T>,拒绝方法应该是Func<T>的扩展方法。
  2. 如果我的Create()确实返回Func<T>,我没有机会阅读应提供给各种异常的参数名称(使用MemberExpression)。
  3. 如果我返回Expression<Func<T>>,则必须在每个Compile()扩展方法中致电Reject
  4. 问题

    1. 是否有C#库已经进行了这种链接?
    2. 如果没有,你有什么建议怎么做?我们热烈欢迎网上的任何例子。
    3. 附加说明

      我应该指出复杂/长验证调用代码不是一个选项,因为我当前的验证完成如下:

      if (user == null)
      {
          throw new ArgumentNullException("user");
      }
      

      if (string.IsNullOrEmpty(description))
      {
          throw new ArgumentNullException("description");
      }
      

      这有两个主要缺点:

      1. 我一遍又一遍地重复相同的代码行
      2. 它使用魔术字符串
      3. 因此,如上所述,在每个检查中应使用一个班轮进行验证。

2 个答案:

答案 0 :(得分:0)

有一种简单的方法可以实现这样一个流畅的界面。您的'ParameterHelper.Create'方法应返回某个类的实例(此类在下面命名为Requirements)。此实例应保存传递给Create的表达式。此类还应具有Require... 实例方法,这些方法将验证表达式并返回thisRequirements类可以是ParameterHelper内的私有类。我还将为此需求链引入一个接口(此接口在下面命名为IRequirements。示例:

public static class ParameterHelper
{
    public static IRequirements Create<T>(Expression<Func<T>> expression)
    {
        return new Requirements{ Expression = expression };
    }

    private class Requirements<T> : IRequirements
    {
        public readonly Expression<Func<T>> Expression { get; set; }

        public IRequirements RejectNull()
        {
            if (Expression .Compile()().Equals(default(T)))
            {
                MemberExpression param = (MemberExpression)Expression.Body;
                throw new ArgumentNullException(param.Member.Name);
            }
            return this;
        }

        // other Require... methods implemented in the same way
    }
}

public interface IRequirements
{
    IRequirements RejectNull();
}

此方法允许您实施luxurious解决方案 - 您只需要向Reject...方法添加相应的参数。此外,您可能需要使IRequirements接口通用。

答案 1 :(得分:0)

罗伯特,

我有一个解决这个问题的库。它被称为Bytes2you.ValidationProject)。它是一个快速,可扩展,直观且易于使用的C#库,为参数验证提供了流畅的API。

它完全关注您要解决的问题,但使用表达式。就是这样,因为它们比传递参数名慢很多。对于像这样的库,它被设计为在任何地方使用,性能是最重要的功能之一。

例如:

Guard.WhenArgument(stringArgument,"stringArgument").IsNullOrEmpty().IsEqual("xxx").Throw();  
// Which means - when stringArgument is null or empty OR is equal to "xxx" we will throw exception. If it is null, we will throw ArgumentNullException. If it is equal to "xxx", we will throw ArgumentException.