Thursday, February 8, 2007

Object comparer

With this post i want to show a compare for objects.
This class compares 2 objects with eachother (custom objects)
With the possibility to do a deep compare.

You can off course implement a IComparable interface and do it manually,
but i find it a little more useful to do it with an utility, so that i can use it on no-mather which class.

If you have comments or can make it better, don't hesitate to post a comment.

There are probably other ways to do it, but i find this method a fast-enough method for most of the applications.


   1: /// <summary>
   2: /// Compares 2 objects with each other.
   3: /// </summary>
   4: public class Comparer
   5: {
   6:     #region Declarations
   7:  
   8:     private static readonly Type _stringType;
   9:     private delegate int ILCompareHandler(object o1, object o2);
  10:     private static ILCompareHandler _ILComparer;
  11:  
  12:     #endregion
  13:  
  14:     #region Static Constructor
  15:  
  16:     static Comparer()
  17:     {
  18:         _stringType = typeof(string);
  19:     }
  20:  
  21:     #endregion
  22:  
  23:     #region Public Methods
  24:  
  25:     /// <summary>
  26:     /// Checks wether 2 objects are equal.
  27:     /// </summary>
  28:     /// <param name="o1">Object 1</param>
  29:     /// <param name="o2">Object 2</param>
  30:     /// <param name="valueTypesOnly">Only check the value-types? (false = also check the associated-classes)</param>
  31:     /// <returns>Wether the 2 objects are equal in VALUE</returns>
  32:     public static bool CompareTo(object o1, object o2, bool valueTypesOnly)
  33:     {
  34:         Type to1 = o1.GetType();
  35:         Type to2 = o1.GetType();
  36:         if (to1.FullName.CompareTo(to2.FullName) != 0)
  37:             return false;
  38:         PropertyInfo[] props = to1.GetProperties();
  39:         // Types are equal, compare each value of each object
  40:         if (valueTypesOnly)
  41:         {
  42:             return TestOnValuesOnly(props, o1, o2);
  43:         }
  44:         else
  45:         {
  46:             bool innervaluesAreEqual = TestRecursiveValues(props, o1, o2, false);
  47:             return innervaluesAreEqual;
  48:         }
  49:     }
  50:  
  51:     #endregion
  52:  
  53:     #region Private Methods
  54:  
  55:     private static bool TestRecursiveValues(PropertyInfo[] props, object o1, object o2, bool inside)
  56:     {
  57:         if (o1 == null && o2 == null)
  58:             return true;
  59:         if ((o1 != null && o2 == null) || (o1 == null && o2 != null))
  60:             return false;
  61:  
  62:         bool classOK = true;
  63:         for (int i = 0; i < props.Length; i++)
  64:         {
  65:             PropertyInfo pi = props[i];
  66:             if (pi.PropertyType.IsClass && pi.PropertyType != _stringType)
  67:             {
  68:                 object object1 = pi.GetValue(o1, null);
  69:                 object object2 = pi.GetValue(o2, null);
  70:                 if (object1 is IEnumerable)
  71:                 {
  72:                     classOK = false;
  73:                     IEnumerator enumerator1 = (object1 as IEnumerable).GetEnumerator();
  74:                     IEnumerator enumerator2 = (object2 as IEnumerable).GetEnumerator();
  75:                     while (enumerator1.MoveNext() && enumerator2.MoveNext())
  76:                     {
  77:                         classOK = TestRecursiveValues(enumerator1.Current.GetType().GetProperties(), enumerator1.Current, enumerator2.Current, true);
  78:                         if (!classOK)
  79:                             return classOK;
  80:                     }
  81:                     if (!classOK)
  82:                         return classOK;
  83:                 }
  84:                 else
  85:                 {
  86:                     classOK = TestRecursiveValues(pi.PropertyType.GetProperties(), object1, object2, true);
  87:                     if (!classOK)
  88:                         return classOK;
  89:                 }
  90:             }
  91:             else
  92:             {
  93:                 if (inside)
  94:                 {
  95:                     if (o1.GetType().IsValueType || o1 is string)
  96:                     {
  97:                         if (o1.GetType().IsValueType)
  98:                             if (ILCompareValueTypes(o1, o2) != 0)
  99:                                 return false;
 100:                         if (o1 is string && o1 != o2)
 101:                             return false;
 102:                     }
 103:                     else
 104:                     {
 105:                         // We have an inside class , check our values
 106:                         classOK = TestOnValuesOnly(props, o1, o2);
 107:                         if (!classOK)
 108:                             return classOK;
 109:                     }
 110:                 }
 111:             }
 112:         }
 113:         return classOK;
 114:     }
 115:  
 116:     private static bool TestOnValuesOnly(PropertyInfo[] props, object o1, object o2)
 117:     {
 118:         if (o1 == null && o2 == null)
 119:             return true;
 120:         if ((o1 != null && o2 == null) || (o1 == null && o2 != null))
 121:             return false;
 122:         for (int i = 0; i < props.Length; i++)
 123:         {
 124:             PropertyInfo pi = props[i];
 125:             if (pi.PropertyType.IsValueType && !pi.PropertyType.IsArray)
 126:             {
 127:                 object so1 = pi.GetValue(o1, null) ?? string.Empty;
 128:                 object so2 = pi.GetValue(o2, null) ?? string.Empty;
 129:  
 130:                 if (ILCompareValueTypes(so1, so2) != 0)
 131:                     return false;
 132:             }
 133:             if (pi.PropertyType == _stringType)
 134:             {
 135:                 object so1 = pi.GetValue(o1, null) as string ?? string.Empty;
 136:                 object so2 = pi.GetValue(o2, null) as string ?? string.Empty;
 137:  
 138:                 if (so1 != so2)
 139:                     return false;
 140:             }
 141:         }
 142:         return true;
 143:     }
 144:  
 145:     private static int ILCompareValueTypes(object value1, object value2)
 146:     {
 147:         if (_ILComparer == null)
 148:         {
 149:             Type[] _argTypes = { typeof(object), typeof(object) };
 150:  
 151:             DynamicMethod dynam =
 152:                 new DynamicMethod(
 153:                 "ILComparer",
 154:                 typeof(int),
 155:                 _argTypes,
 156:                 typeof(Comparer));
 157:             ILGenerator il = dynam.GetILGenerator();
 158:  
 159:             il.Emit(OpCodes.Ldarg_0);
 160:             il.Emit(OpCodes.Ldarg_1);
 161:             il.Emit(OpCodes.Ceq);
 162:             il.Emit(OpCodes.Ret);
 163:             
 164:             _ILComparer = dynam.CreateDelegate(typeof(ILCompareHandler)) as ILCompareHandler;
 165:         }
 166:         return _ILComparer(value1, value2);
 167:     }
 168:  
 169:     #endregion
 170: }

2 comments:

Geert Verhoeven said...

Hi Filip,

Nice solution for comparing objects.

Take into account that using reflection decreases performance.

For comparing only a few values, there is no problem but when comparing multiple values, it can give a big difference. When performance is critical or the number of comparisons is high, it is better to implement the IComparable interface as you mentioned.

Greetz,

Geert

Whizzo said...

Hello Geert,

Thanks for your input,
Indeed for a lot of values it could be wise to use the IComparable interface, altough, this method is pretty fast, the IL makes a lot good.

I use this class sometimes as base-class, with also the IComparable implemented, that way I just have to inherit and call the CompareTo without writing my own compares (call it responsable lazy ;)) when it's to slow, i override the CompareTo and to it manually.

For the most common simple domain objects it's very very very handy, and saves a lot of time.

Greets,

Filip