I would like to present here a little argument verification library that does not require you to type any string for specifying the name of the parameter you are checking. This makes the library faster to use, not intrusive in the actual method code, and refactor friendly. As a bonus, you can use it by just embedding a single file. We can see below an example, just to get immediately to the point:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NContracts
{
public class Demo
{
public void DoSomething(string arg1)
{
Contract.Expect(() => arg1).IsNotNull();
}
public void DoSomething(int min,int max)
{
Contract.Expect(() => min)
.IsLessThan(int.MaxValue)
.Meet(a => a <= max);
}
public void DoSomething(int arg1)
{
Contract.Expect(() => arg1).IsGreatherThan(0)
.IsLessThan(100);
;
}
public void DoSomething(int[] array)
{
Contract.Expect(() => array).Meet(a => a.Length > 0 && a.First() == 0);
}
}
}
As we can see, there is no magic string at all. All the argument names are guessed, thanks to the metadata contained in the Linq Expression we use. For example, the method at line 14 if called with a null
value will report:
Value cannot be null.
Parameter name: arg1
The same happens to the more complex check we do at line 46, when we write:
Contract.Expect(() => array).Meet(a => a.Length> 0 && a.First() == 0);
We have a complex predicate do meet, described by a lambda, standing that the input array should have first element zero, and non zero length. Notice that the name of the parameter is array, but we need to use another name for the argument of the lambda (in this case, I used ‘a
’), the library is smart enough to understand that ‘a
’ actually refers to array, and the error message will report it correctly if the condition is not met. Just to clarify, the message in case of failure would be:
Precondition not verified:((array.First() == 0) AndAlso (ArrayLength(array) > 1))
Parameter name: array
Well it is not supposed to be a message to an end real user, it is a programmer friendly message, but such validation errors are supposed to be reported to a developer (and end user should not see method validation errors at all, should he?)
Well, Meet
is a cutting edge function we can use for complex validations. Out of the box, for simpler cases we have some functions too, as we can see on the IContract
interface definition:
public interface IContract<t>
{
IContract<t> Is<tt>();
IContract<t> IsEqual<tt>(TT of) where TT : T,IEquatable<tt>;
IContract<t> IsGreatherThan<tt>(TT of) where TT : T,IComparable;
IContract<t> IsGreatherThanOrEqual<tt>(TT of) where TT :T, IComparable;
IContract<t> IsLessThan<tt>(TT of) where TT : T,IComparable;
IContract<t> IsLessThanOrEq<tt>(TT of) where TT : T,IComparable;
IContract<t> IsNot<tt>();
IContract<t> IsNotEqual<tt>(TT of) where TT : T,IEquatable<tt>;
IContract<t> IsNotNull();
IContract<t> IsNotTheSameOf(object of);
IContract<t> IsNull();
IContract<t> IsTheSameOf(object of);
IContract<t> Meet(Expression<predicate<t>> predicate);
}
An interesting portion of the codebase proposed is the one renaming the parameter on the lambda expression, to achieve the reported message reflecting the correct offending parameter. It is not so easy because plain string replacement would not work: we can have a parameter named ‘a
’, seen in any place in the expression string representation and a plain replacement would resolve in a big mess, furthermore Expressions are immutable. So I found help on StackOverflow, and a reply to this question solved the problem, let see the “Renamer” at work (Thanks to Phil):
static class PredicateRewriter
{
public static Expression<predicate<t>>
Rewrite<t>(Expression<predicate<t>> exp
, string newParamName)
{
var param = Expression.Parameter(exp.Parameters[0].Type, newParamName);
var newExpression = new PredicateRewriterVisitor(param).Visit(exp);
return (Expression<predicate<t>>)newExpression;
}
private class PredicateRewriterVisitor : ExpressionVisitor
{
private readonly ParameterExpression _parameterExpression;
public PredicateRewriterVisitor(ParameterExpression parameterExpression)
{
_parameterExpression = parameterExpression;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return _parameterExpression;
}
}
}
Basically, it is a reusable class that takes the new name of the parameter and returns a copy of the input expression with the (single) argument changed.
To improve the library or just use it, please follow/check out the project on Bitbucket. Suggestions and comments are always welcome.