I've been working on another timesaver because I really really don't like to type all those validation ifs and exceptions over an over again.What I came up with this time is a set of validation extensions that make use of expression trees even if very simple ones.There are three classes. The basis is the Validate class that creates the first validation context:public static class Validate{    public static ValidationContext<TArg> Expression<TArg>(Expression<Func<TArg>> expression)    {        var memberExpression = expression.Body as MemberExpression;        var name = memberExpression.Member.Name;        var value = expression.Compile()();        return new ValidationContext<TArg>()        {            Argument = value,            Name = name        };    }}Then a ValidationContext passes the information about the expression and its value:public class ValidationContext<TArg>{    public TArg Argument { get; internal set; }    public string Name { get; internal set; }}Finally there are all the validation extensions (I removed some of them to make the list shorter):public static class Validation{    public static ValidationContext<TArg> IsNotNull<TArg>(this ValidationContext<TArg> context)    {        if (context.Argument == null) throw new ArgumentNullException(context.Name);        return context;    }    public static ValidationContext<string> IsNotNullOrEmpty(this ValidationContext<string> context)    {        if (string.IsNullOrEmpty(context.Argument)) throw new ArgumentNullException(context.Name);        return context;    }    public static ValidationContext<TArg> IsLessThen<TArg>(this ValidationContext<TArg> context, TArg max) where TArg : IComparable    {        if (!(context.Argument.CompareTo(max) < 0))            throw new ArgumentOutOfRangeException(context.Name, $'{context.Name}' of '{context.Argument}' is not less then '{max}'.);        return context;    }    public static ValidationContext<TArg> IsLessThenOrEqual<TArg>(this ValidationContext<TArg> context, TArg max) where TArg : IComparable    {        if (!(context.Argument.CompareTo(max) <= 0))            throw new ArgumentOutOfRangeException(context.Name, $'{context.Name}' of '{context.Argument}' is not less then or eqal '{max}'.);        return context;    }    public static ValidationContext<TArg> IsBetween<TArg>(this ValidationContext<TArg> context, TArg min, TArg max) where TArg : IComparable    {        if (!(context.Argument.CompareTo(min) > 0 && context.Argument.CompareTo(max) < 0))            throw new ArgumentOutOfRangeException(context.Name, $'{context.Name}' of '{context.Argument}' is not between '{min}' and {max}.);        return context;    }    public static ValidationContext<TArg> IsBetweenOrEqual<TArg>(this ValidationContext<TArg> context, TArg min, TArg max) where TArg : IComparable    {        if (!(context.Argument.CompareTo(min) >= 0 && context.Argument.CompareTo(max) <= 0))            throw new ArgumentOutOfRangeException(context.Name, $'{context.Name}' of '{context.Argument}' is not between or equal '{min}' and {max}.);        return context;    }       public static ValidationContext<TArg> IsEqual<TArg>(this ValidationContext<TArg> context, TArg value) where TArg : IComparable    {        if (context.Argument.CompareTo(value) != 0) throw new Exception($'{context.Name}' is not equal '{value.ToString()}'.);        return context;    }    public static ValidationContext<TArg> Where<TArg>(this ValidationContext<TArg> context, Func<TArg, bool> predicate)    {        if (!predicate(context.Argument)) throw new Exception($'{context.Name}' is not valid.);        return context;    }    public static ValidationContext<string> IsMatch(this ValidationContext<string> context, string pattern, RegexOptions options = RegexOptions.None)    {        if (!Regex.IsMatch(context.Argument, pattern, options)) throw new Exception($'{context.Name}' does not match '{pattern}'.);        return context;    }    public static ValidationContext<TArg2> And<TArg, TArg2>(this ValidationContext<TArg> context, Expression<Func<TArg, TArg2>> expression)    {        var memberExpression = expression.Body as MemberExpression;        var name = memberExpression.Member.Name;        var value = expression.Compile()(context.Argument);        return new ValidationContext<TArg2>()        {            Argument = value,            Name = name        };    }}The last extension in this class allows to chain another expression and more validations.Here are some examples (the lines throwing exceptions are commented out):var foo = (string)null;//Validate.Expression(() => foo).IsNotNull(); // bam!//Validate.Expression(() => foo).IsNotNullOrEmpty(); // bam!foo = bar;Validate.Expression(() => foo).IsNotNull().IsMatch([a-z]+);//Validate.Expression(() => foo).IsNotNull().Matches(^[a-z]{2}$); // bam!Validate.Expression(() => foo.Length).IsEqual(3);//Validate.Expression(() => foo.Length).IsEqual(4); // bam!// chaining more expressionsValidate.Expression(() => foo).IsNotNull().And(s => s.Length).IsEqual(3);//Validate.Expression(() => foo).IsNotNull().And(s => s.Length).IsEqual(4); // bam!Validate.Expression(() => foo.Length).IsBetween(2, 4);//Validate.Expression(() => foo.Length).IsBetween(3, 4);Validate.Expression(() => foo.Length).IsBetweenOrEqual(3, 6);//Validate.Expression(() => foo.Length).IsBetweenOrEqual(4, 6); // bam!var baz = 3;Validate.Expression(() => baz).IsLessThen(4);Validate.Expression(() => baz).IsLessThenOrEqual(3);//Validate.Expression(() => baz).IsLessThenOrEqual(2); // bam!You can also return the final value:var length = Validate.Expression(() => foo.Length).IsBetween(2, 4).Argument;The main goals are:easy to useeasy to extendprovide as much information as possiblesimplify validation
Validation extensions
c#;validation;extension methods;framework;expression trees
This time I'm going to write my own review ;-)For most of you this isn't a great discovery but compiling an expression is a real performance hit. For 10k loops it makes a difference of at least 3sec on my machine.I refactored my code and implemented a few optimizations. They are not very pretty but at least the difference between the expression and expression-less version is about 4milliseconds for 10k loops so actually negligible.The new Validate class:public static class Validate{    public static ValidationContext<TArg> Expression<TArg>(        Func<TArg> lambda,         Func<Expression<Func<TArg>>> memberExpression)    {        return new ValidationContext<TArg>()        {            Argument = lambda(),            Expression = memberExpression        };    }    public static ValidationContext<TArg> Expression<TArg>(        Func<TArg> lambda,         string memberName)    {        return new ValidationContext<TArg>()        {            Argument = lambda(),            MemberName = memberName        };    }    }The updated context:public class ValidationContext<TArg>{    private string _memberName;    public TArg Argument { get; internal set; }    public string MemberName    {        get        {            if (!string.IsNullOrEmpty(_memberName)) return _memberName;            var memberExpression = Expression().Body as MemberExpression;            return memberExpression.Member.Name;        }        set { _memberName = value; }    }    internal Func<Expression<Func<TArg>>> Expression { get; set; }}As you can see I either can specify the member name or evaluate it dynamicaly. I do it only in case of an exception. The Func<Expression<Func<TArg>>> prevents the expression to be build prematurely.It's call probably wouldn't win a beauty competition but what else we can do:var foo = bar;Validate.Expression(() => foo, () => () => foo).IsNotNullOrEmpty();So after all it's not that bad, isn't it? ;-)