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");
Instead reflection is used to get the name of the argument. This can of course affect performance.
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#... ;)