How to validate a method's arguments?
Yesterday I wrote a post about developers that skip validation of arguments. In this post I will give some example of how validation can be done. There are probably better way to do it, and that is why I need your feedback and comments.
The first example is a simple method that uses a simple validation by using “If”.
public string DoSomething(int value1, string value2) { if (value1 > 10 || value1 < 0) throw new ArgumentException("The value must be between 0 or 10", "value1"); if (string.IsNullOrEmpty(value2)) throw new ArgumentException("The value can't be null or empty", "value2"); //... }
I started to use this solution to validate arguments, but what I notice really fast was that I repeat my self over and over again when I created more methods. I solved this by creating an ArgumentHelper class where I add my validation logic.
public static class ArgumentHelper { public static void RequireRange(int value, int minValue, int maxValue, string argumentName) { if (value > maxValue || value < minValue) throw new ArgumentException(string.Format("The value must be between {0} and {1}", minValue, maxValue), argumentName); } public static void RequireNotNullOrEmpty(string value, string argumentName) { if (string.IsNullOrEmpty(value)) throw new ArgumentException("The value can't be null or empty", argumentName); } }
public string DoSomething(int value1, string value2) { ArgumentHelper.RequireRange(value1, 0, 10, "value1"); ArgumentHelper.RequireNotNullOrEmpty(value2, "value2"); //... }
With C# 3.0 we now have Extension Methods, so why not use Extension Methods to do the validation for a given type? Here is an example where I use Extension methods instead of an ArgumentHelper class:
public static class ArgumentHelperExtension { public static void RequireRange(this int value, int minValue, int maxValue, string argumentName) { if (value > maxValue || value < minValue) throw new ArgumentException(string.Format("The value must be between {0} and {1}", minValue, maxValue), argumentName); } public static void RequireNotNullOrEmpty(this string value, string argumentName) { if (string.IsNullOrEmpty(value)) throw new ArgumentException("The value can't be null or empty", argumentName); } }
public string DoSomething(int value1, string value2) { value1.RequireRange(0, 10, "value1"); value2.RequireNotNullOrEmpty("value2"); //... }
As you can see the ArgumentHelper class have no changes, well the "this" key word is added to the first argument in the methods. I can then simply use my argument and call an Extension method that will do the validation.
I recently had a discussion with my friend Roger Alsing and he had some interesting ideas. If we take a look at the Extension Method example, we can see that we add validation extension method to a given type. Let assume we don't want to add several Extension method to the type Int, instead only one method and that method returns a given "interface" which we can extend instead. For example to Int we can have a Extension method called Require and let it return a Numeric<T>, and then we can create Extension methods for the Numeric<T> class instead:
public static string DoSomething(int value) { value1.Require().InRange(0, 10, "value1"); //... }
Another interesting solution Roger had is the following:
static void MyMethod(string arne) { RequireNotNull( () => arne); }
static void RequireNotNull<T>(ArgDelegate<T> del) { T value = del(); FieldInfo[] fields = del.Target.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); if (fields.Length != 1) throw new NotSupportedException("Invalid argument"); string argName = fields[0].Name; if (value == null) throw new Exception(string.Format("Parameter '{0}' may not be null", argName)); }
By using this solution we don't need to pass the name of the argument as a string like in the following solutions:
ArgumentHelper.RequireRange(value1, 0, 10, "value1"); value1.RequireRange(0, 10, "value1");
How do you implement your validation code, do you have some cool solution?
Why can't we just have Design By Contract support for C#... ;)