Argument Validation Utility
A while ago I’ve found a nice way to implement argument validation on the Paint.Net-blog. Its a fluent-interface like this:
public void CallMe(object sender,int size, string name) { Validate.Begin() .IsType<Guid>(sender, "sender") .BiggerThan(size, 0, "size") .IsNotEmpty(name, "name") .Check(); /*IMPL */ }
If only condition fails,
CallMe(Guid.NewGuid(), 2, “”);
you get a normal exception:
System.ArgumentException: The argument ‘name’ cannot be empty
But if more than one condition fails,
CallMe(“I’m not a guid”, 0, null);
you get an exception which reports each violation:
Core.IoC.Utils.MultiException: Validation failed with multiple causes:
The parameter ‘sender’ is not of type: ‘System.Guid’. Instance: ‘I’m not a guid’ of type: System.String
Expected that the argument ‘size’ is bigger than 0 but is is 0
Value cannot be null. Parameter name: name
Its even possible to iterate over the inner exceptions if necessary:
try{ CallMe("I'm not a guid", 0, null); } catch(MultiException ex) { foreach (var singleException in ex.InnerExceptions) { Console.Out.WriteLine(singleException); } }
Now to the clever implementation. All the checks are actually extension-methods, which extend the Validation-class. Now, the initial Validate.Begin()-Method return null! Each validation-method first checks its validation. If it fails, it checks if a Validation-class instance is there or not. If so, it adds its exception, if not, it creates the Validation class instance. The final Check-method then tests if there is actually a Validation class instance and throws the exceptions if necessary. With this clever trick, the validation-checks never allocate any object until there is an exception to throw (more detailed on the Paint.Net-blog)
public static class Validate { public static Validation Begin() { return null; } } public sealed class Validation { private readonly List<Exception> exceptions = new List<Exception>(1); internal Validation() { } internal IEnumerable<Exception> Exceptions { get { return exceptions; } } internal Validation AddException(Exception ex) { exceptions.Add(ex); return this; } } public static class ValidationExtensions { /// *snip* documentation public static Validation IsNotNull<T>(this Validation validation, T theObject, string argumentName) where T : class { if (theObject == null) { return (validation ?? new Validation()).AddException(new ArgumentNullException(argumentName)); } return validation; } /* more checks */ /// *snip* documentation public static Validation Check(this Validation validation) { if (validation == null) return validation; { if (validation.Exceptions.Take(2).Count() == 1) { throw validation.Exceptions.First(); } var messages = new StringBuilder("Validation Failed with multiple causes:"); messages.AppendLine(); foreach (var exception in validation.Exceptions) { messages.AppendLine(exception.Message); } throw new MultiException(messages.ToString(), validation.Exceptions); } } } [Serializable] public class MultiException : ArgumentException { internal MultiException(string message, IEnumerable<Exception> exceptions) : base(message) { InnerExceptions = exceptions; } /* snip some irrelavant stuff */ public IEnumerable<Exception> InnerExceptions { get; private set; } /* snip serialisation-stuff */ }
As far as I know with .NET 4.0 some kind of contracts will be introduced to the core framework. Then I will start to use those, to benefit from the better tool support.
- Small DI-Containers and Autofac
- Ruby Mine 1.0 Released