Tuesday, January 17, 2006

Argument validation class looks tons better using generics in CLR 2.0

I've had this class around for a long time, finally decided to update it to use generic types instead of tons of overloads. It's tons nicer this way. Contact me if you want a version with XML documentation (which just looks ugly in the blog).

using System;
using System.ComponentModel;
using System.Diagnostics;

namespace IDisposable.Utilities
{
 public static class ArgumentValidation
 {
  #region Helper
  private static string PadWord(string text)
  {
   if (String.IsNullOrEmpty(text))
    return string.Empty;
   else
    return text + " ";
  }
  #endregion

  public static void ArgumentDifferentTypeCheck(object value, string name, Type type)
  {
   ArgumentValidation.ArgumentNullCheck(value, "value");
   ArgumentValidation.ArgumentNullCheck(type, "type");

   if (value.GetType() != type)
   {
    throw new ArgumentException(
     string.Format("Argument {0}must be of type {1}.",
      ArgumentValidation.PadWord(name), type.Name), name);
   }
  }

  public static void ArgumentIncompatibleTypeCheck(object value, string name, Type type)
  {
   ArgumentValidation.ArgumentNullCheck(value, "value");
   ArgumentValidation.ArgumentNullCheck(type, "type");

   if (!type.IsAssignableFrom(value.GetType()))
   {
    throw new ArgumentException(
     string.Format("Argument {0}must be compatible with type {1}.",
      ArgumentValidation.PadWord(name), type.Name), name);
   }
  }

  public static void ArgumentNullCheck(object value, string name)
  {
   if (null == value)
   {
    throw new ArgumentNullException(name,
     string.Format("Argument {0}cannot be null.",
      ArgumentValidation.PadWord(name)));
   }
  }

  public static void ArrayArgumentDifferentRank(Array value, string name, int rank)
  {
   ArgumentValidation.ArgumentNullCheck(value, "value");

   if (value.Rank != rank)
   {
    throw new RankException(string.Format("Argument {0}must be "
     + "an array with {1} dimension.", ArgumentValidation.PadWord(name),
     rank));
   }
  }

  public static void ArgumentInvalidEnumValueCheck(object value, string name, Type enumType)
  {
   Debug.Assert(value.GetType().IsPrimitive || (value.GetType() == typeof(string))
    , "Expected a primitive type or a String!");

   ArgumentValidation.ArgumentNullCheck(value, "value");
   ArgumentValidation.ArgumentNullCheck(enumType, "enumType");

   if (enumType.GetCustomAttributes(typeof(FlagsAttribute), true).Length > 0)
   {
    throw new ArgumentException(string.Format(
     "Cannot validate against the type {0} because it has the custom attribute FlagsAttribute."
      , enumType), "enumType");
   }

   if (!Enum.IsDefined(enumType, value))
   {
    if (value.GetType() == typeof(string))
    {
     // Let the value argument be int.MaxValue if a string was passed in.
     throw new InvalidEnumArgumentException(name, int.MaxValue, enumType);
    }
    else
    {
     int valueInt32 = 0;
     TypeConverter typeConverter = TypeDescriptor.GetConverter(
             Enum.GetUnderlyingType(enumType));

     try
     {
      valueInt32 = (int)typeConverter.ConvertTo(value, typeof(int));
     }
     catch (OverflowException)
     {
      // Let the value argument be 0 if it overflows Int32.
     }

     throw new InvalidEnumArgumentException(name, valueInt32, enumType);
    }
   }
  }

  public static void ArgumentOutOfRangeCheck(T value, string name, T minValue, T maxValue) where T : IComparable
  {
   ArgumentValidation.ArgumentNullCheck(value, "value");
   ArgumentValidation.ArgumentIncompatibleTypeCheck(minValue, "minValue", typeof(T));
   ArgumentValidation.ArgumentIncompatibleTypeCheck(maxValue, "maxValue", typeof(T));

   if (value.CompareTo(minValue) < 0 || 0 < value.CompareTo(maxValue))
   {
    throw new ArgumentOutOfRangeException(name, value
     , string.Format("Argument {0}must be greater than or equal to {1} and less than or equal to {2}."
     , ArgumentValidation.PadWord(name), minValue, maxValue));
   }
  }

  public static void ArgumentOutOfRangeExclusiveCheck(T value, string name, T lowerBound, T upperBound) where T : IComparable
  {
   ArgumentValidation.ArgumentNullCheck(value, "value");
   ArgumentValidation.ArgumentDifferentTypeCheck(lowerBound, "lowerBound", typeof(T));
   ArgumentValidation.ArgumentDifferentTypeCheck(upperBound, "upperBound", typeof(T));

   if (lowerBound.CompareTo(upperBound) == 0)
   {
    throw new ArgumentException("Arguments lowerBound and upperBound cannot be equal.");
   }

   if (value.CompareTo(lowerBound) <= 0 || 0 <= value.CompareTo(upperBound))
   {
    throw new ArgumentOutOfRangeException(name, value,
     string.Format("Argument {0}must be greater than {1} and less than {2}."
      , ArgumentValidation.PadWord(name), lowerBound, upperBound));
   }
  }

  public static void ArgumentOutOfRangeIncludeMaxCheck(T value, string name, T lowerBound, T maxValue) where T : IComparable
  {
   ArgumentValidation.ArgumentNullCheck(value, "value");
   ArgumentValidation.ArgumentIncompatibleTypeCheck(lowerBound, "lowerBound", typeof(T));
   ArgumentValidation.ArgumentIncompatibleTypeCheck(maxValue, "maxValue", typeof(T));

   if (value.CompareTo(lowerBound) <= 0 || 0 < value.CompareTo(maxValue))
   {
    throw new ArgumentOutOfRangeException(name, value,
     string.Format("Argument {0}must be greater than {1} and less than or equal to {2}."
     , ArgumentValidation.PadWord(name), lowerBound, maxValue));
   }
  }

  public static void ArgumentOutOfRangeIncludeMinCheck(T value, string name, T minValue, T upperBound) where T : IComparable
  {
   ArgumentValidation.ArgumentNullCheck(value, "value");
   ArgumentValidation.ArgumentIncompatibleTypeCheck(minValue, "minValue", typeof(T));
   ArgumentValidation.ArgumentIncompatibleTypeCheck(upperBound, "upperBound", typeof(T));

   if (value.CompareTo(minValue) < 0 || 0 <= value.CompareTo(upperBound))
   {
    throw new ArgumentOutOfRangeException(name, value,
     string.Format("Argument {0}must be greater than or equal to {1} and less than {2}."
     , ArgumentValidation.PadWord(name), minValue, upperBound));
   }
  }
 }
}

3 comments:

Anonymous said...

Can you throw out a .zip link with the .cs and .xml doc? This thing is a goldmine!

IDisposable said...

Done, see here.

IDisposable said...

Updated to fix Enum bug