Problems writing C# method parameter validation that supports fluent interface (call chaining)
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:
pub开发者_JS百科lic 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
- I would like to avoid multiple
Compile()
calls, so basically myCreate()
method should returnFunc<T>
and reject methods should be extension methods ofFunc<T>
. - If my
Create()
does returnFunc<T>
I don't get the chance to read parameter names that should be supplied to various exceptions (usingMemberExpression
). - If I return
Expression<Func<T>>
instead I will have to callCompile()
in eachReject
extension method.
Questions
- Is there a C# library that already does this kind of chaining?
- 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:
- I repeat the same lines of code over and over
- it uses magic strings
So validation should be done with a one liner per check as described above in the desired scenario.
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.
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.
精彩评论