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

发布于 2024-10-20 09:47:40 字数 2053 浏览 3 评论 0 原文

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

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() 方法,该方法将返回可由链式扩展方法使用的内容。

我想避免多次 Compile() 调用的问题

  1. ,所以基本上我的 Create() 方法应该返回 Func 并拒绝方法应该是Func的扩展方法。
  2. 如果我的 Create() 确实返回 Func 我没有机会读取应提供给各种异常的参数名称(使用 MemberExpression< /代码>)。
  3. 如果我返回 Expression> ,我将必须在每个 Reject 扩展方法中调用 Compile()

问题

  1. 是否有一个 C# 库已经可以进行这种链接?
  2. 如果没有,您建议如何完成此操作?热烈欢迎来自网络的任何示例。

附加说明

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

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

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

它有两个主要缺点:

  1. 我一遍又一遍地重复相同的代码行,
  2. 它使用魔术字符串

所以验证在所需的情况下,应按照上述方式在每次检查时使用一个衬垫。

I'm trying to write a generic method parameter validation functionality that can be chained (fluent interface) to attach more and more validations/checks like:

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

I would like to use this helper class to test method parameters for certain values before using them (most of the time null will be tested).

Current state of affairs

I first started writing static helper class without the Create() method like:

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

But this doesn't allow chaining. That's why I created the Create() method that would return something that can be used by chained extension methods.

The problem

  1. I would like to avoid multiple Compile() calls, so basically my Create() method should return Func<T> and reject methods should be extension methods of Func<T>.
  2. If my Create() does return Func<T> I don't get the chance to read parameter names that should be supplied to various exceptions (using MemberExpression).
  3. If I return Expression<Func<T>> instead I will have to call Compile() in each Reject extension method.

Questions

  1. Is there a C# library that already does this kind of chaining?
  2. If not, what do you suggest how this should be done? Any examples from the web would be warmly welcome.

Additional note

I should point out that complex/long validation invocation code is not an option here, because my current validation is done like:

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

or

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

Which has two major drawbacks:

  1. I repeat the same lines of code over and over
  2. it uses magic strings

So validation should be done with a one liner per check as described above in the desired scenario.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

皇甫轩 2024-10-27 09:47:40

有一种简单的方法可以实现这样一个流畅的界面。您的“ParameterHelper.Create”方法应返回某个类的实例(该类在下面名为 Requirements)。该实例应保存传递给Create 的表达式。此外,此类应该具有 Require... instance 方法,该方法将验证表达式并返回 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();
}

这种方法将允许您实现您的豪华 解决方案 - 您只需添加一个此外,您可能需要使 IRequirements 接口通用。

There is a simple way to implement such a fluent interface. Your 'ParameterHelper.Create' method should return an instance of some class (this class is named Requirements below). This instance should hold the expression which was passed to Create. Also this class should have Require... instance methods which will validate expression and return this. Requirements class can be a private class inside ParameterHelper. I would also introduce an interface for this requirements chain (this interface is named IRequirements below. Sample:

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

This approach will allow you implementing your luxurious solution - you just need to add a corresponding parameters to Reject... methods. Also you will probably need to make IRequirements interface generic.

别忘他 2024-10-27 09:47:40

罗伯特,

我有一个库可以解决这个问题。它称为 Bytes2you.Validation (Bytes2you.Validation) com/veskokolev/Bytes2you.Validation" rel="nofollow">项目)。它是快速、可扩展、直观且易于使用的 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.

Robert,

I have a library that solves this problem. It is called Bytes2you.Validation (Project). It is fast, extensible, intuitive and easy-to-use C# library providing fluent APIs for argument validation.

It focuses exactly on the problem that you want to solve, but does not use expressions. This is so, because they are a lot slower than just passing the argument name. For a library like that, that is designed to be used everywhere the performance is one of the most critical features.

For example:

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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文