Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Comparing Two Complex or primitive objects of same class (Alternative)

3 Nov 2015 3  
This is an alternative for Compareing Two Complex objects of same class

Introduction

Compareing Two Complex Objects of same class[^]

This is an alternative to the cited tip. The title implies that this method will test for equality on complex classes, but it would always return false if a complex class included a datetime property.

My version also has only one exit point from the method, and it also stops comparing at the first non-equal property in a complex class. Beyond that I refactored it to only falll down into the complex class handling code if the objects being compared aren' primitives, strings, or datetimes (which require special handling anyway).

There may be other types that warrant special handling, but I leave that as an exercise for the programmer to both discover and implement.

Lastly, if you want to use a method like this, you may also want to consider checking non-public properties as well as public ones. To do this, simply uncomment the BindingFlags.NonPublic enumerator.

Using the code

public static bool CompareEquals<T>(this T objectFromCompare, T objectToCompare)
{
    bool result = (objectFromCompare == null && objectToCompare == null);
    PropertyInfo property;

    if (!result)
    {	
        try
        {
            Type fromType = objectFromCompare.GetType();
            if (fromType.IsPrimitive)
            {
                result = objectFromCompare.Equals(objectToCompare);
            }
            
            else if (fromType.FullName.Contains("System.String"))
            {
                result = ((objectFromCompare as string) == (objectToCompare as string));
            }
            
            else if (fromType.FullName.Contains("DateTime"))
            {
                result = (DateTime.Parse(objectFromCompare.ToString()).Ticks == DateTime.Parse(objectToCompare.ToString()).Ticks);
            }
            
            // stringbuilder handling here is optional, but doing it this way cuts down
            // on reursive calls to this method
            else if (fromType.FullName.Contains("System.Text.StringBuilder"))
            {
                result = ((objectFromCompare as StringBuilder).ToString() == (objectToCompare as StringBuilder).ToString());
            }
            
			else if (fromType.FullName.Contains("System.Collections.Generic.Dictionary"))
			{
				
				PropertyInfo countProp  = fromType.GetProperty("Count");
				PropertyInfo keysProp   = fromType.GetProperty("Keys");
				PropertyInfo valuesProp = fromType.GetProperty("Values");
				int fromCount = (int)countProp.GetValue(objectFromCompare, null);
				int toCount   =  (int)countProp.GetValue(objectToCompare, null);

				result = (fromCount == toCount);
				if (result && fromCount > 0)
				{
					var fromKeys = keysProp.GetValue(objectFromCompare, null);
					var toKeys = keysProp.GetValue(objectToCompare, null);
					result = CompareEquals(fromKeys, toKeys);
					if (result)
					{
						var fromValues = valuesProp.GetValue(objectFromCompare, null);
						var toValues = valuesProp.GetValue(objectToCompare, null);
						result = CompareEquals(fromValues, toValues);
					}
				}
			}

            // collections presented a unique problem in that the original code always returned
            // false when they're encountered. The following code was tested with generic
            // lists (of both primitive types and complex classes). I see no reason why an
            // ObservableCollection shouldn't also work here (unless the properties or
            // methods already used are not appropriate).
            else if (fromType.IsGenericType || fromType.IsArray)
            {
                string propName = (fromType.IsGenericType) ? "Count" : "Length";
                string methName = (fromType.IsGenericType) ? "get_Item" : "Get";
                PropertyInfo propInfo = fromType.GetProperty(propName);
                MethodInfo methInfo = fromType.GetMethod(methName);
                if (propInfo != null && methInfo != null)
                {
                    int fromCount = (int)propInfo.GetValue(objectFromCompare, null); 
                    int toCount = (int)propInfo.GetValue(objectToCompare, null); 
                    result = (fromCount == toCount);
                    if (result && fromCount > 0)
                    {
                        for (int index = 0; index < fromCount; index++) 
                        { 
                            // Get an instance of the item in the list object 
                            object fromItem = methInfo.Invoke(objectFromCompare, new object[] { index });
                            object toItem = methInfo.Invoke(objectToCompare, new object[] { index });
                            result = CompareEquals(fromItem, toItem);
                            if (!result)
                            {
                                break;
                            }
                        }
                    }
                }
                else
                {
                }
            }
            else
            {
                PropertyInfo[] props = fromType.GetProperties(BindingFlags.Public | BindingFlags.Instance );
                foreach (PropertyInfo prop in props)
                {
                    property = prop;
                    Type type = fromType.GetProperty(prop.Name).GetValue(objectToCompare, null).GetType();
                    object dataFromCompare = fromType.GetProperty(prop.Name).GetValue(objectFromCompare, null);
                    object dataToCompare = fromType.GetProperty(prop.Name).GetValue(objectToCompare, null);
                    result = CompareEquals(Convert.ChangeType(dataFromCompare, type), Convert.ChangeType(dataToCompare, type));
                    // no point in continuing beyond the first property that isn't equal.
                    if (!result)
                    {
                        break;
                    }
                }
            }
        }
        catch (Exception ex)
        {
        }
    }
    return result;

}    

Usage: Given the following sample classes...

public class AbcClass
{
    public StringBuilder Text { get; set; }
    public double Value { get; set; }

    public AbcClass()
    {
        this.Text = new StringBuilder("text");
        this.Value = 100d;
    }
}

public class XyzClass
{
    public string Str { get; set; }
    public int X { get; set; }
    public DateTime Date { get; set; }
    public Int64 Long { get; set; }
    public AbcClass ABC { get; set; }
    public List<int> IntList { get; set; }
    public int[] IntArray { get; set; }
    public List<AbcClass> AbcList { get; set; }
    public AbcClass[] AbcArray{ get; set; }
    public Dictionary<int, string> Dict {  get; set; }

    public XyzClass()
    {
        this.Str = "test";
        this.X = 7;
        this.Date = new DateTime(2015, 10, 4, 0, 0, 0);
        this.Long = 1234567890;
        this.ABC = new AbcClass();
        this.IntList = new List<int>(){1,2,3};
        this.IntArray = new int[]{4,5,6};
        this.AbcList = new List<AbcClass>(){ new AbcClass(), new AbcClass()};
        this.AbcArray = new AbcClass[]{new AbcClass()};
        this.Dict = new Dictionary<int,string>(){ {1,"One"}, {2, "Two"} };
    }
}

The following code illustrates usage.

int x = 5;
int y = 5;
XyzClass a = new XyzClass();
XyzClass b = new XyzClass();

// Both of these should return true
bool equals = x.CompareEquals(y);
equals = a.CompareEquals(b);

y=10;
b.Date = DateTime.Now;
b.Str="tester";

// both of these should return false
equals = x.CompareEquals(y);
// this one should return false after finding the first non-equal property
equals = a.CompareEquals(b);

Handy Overload (Update on 10 Nov 2015)

I was writing some code and used this method to compare two FileSystemInfo objects. I realized I could probably save time by simply comparing the property I was interested in (LastWriteTimeUtc) and ignoring the rest of the properties. So, I came up with a couple of overloads for this method.

The first one compares a single property (both objects being compared must still be of the same type. If the property doesn't exist, or if it exists but is not equal in both objects, the result is false. Otherwise it's true.

public static bool CompareEquals<T>(this T objectFromCompare, T objectToCompare, string propertyName)
{
    bool result = (objectFromCompare == null && objectToCompare == null);
    if (!result)
    {
        try
        {
            Type fromType = objectFromCompare.GetType();
            PropertyInfo prop = fromType.GetProperty(propertyName);
            if (prop != null)
            {
                Type type = prop.GetValue(objectToCompare).GetType();
                object dataFromCompare = prop.GetValue(objectFromCompare, null);
                object dataToCompare = prop.GetValue(objectToCompare, null);
                result = CompareEquals(Convert.ChangeType(dataFromCompare, type), Convert.ChangeType(dataToCompare, type));
            }
        }
        catch (Exception ex)
        {
        }
    }
    return result;
}

The second overload compares an array of properties. All specified properties must exist and be equal in order for this method to return true.

public static bool CompareEquals<T>(this T objectFromCompare, T objectToCompare, string[] propertyNames)
{
    bool result = (objectFromCompare == null && objectToCompare == null);
    if (!result)
    {
        try
        {
            foreach (string propertyName in propertyNames)
            {
                result = CompareEquals(objectFromCompare, objectToCompare, propertyNames);
                if (!result)
                {
                    break;
                }
            }
        }
        catch (Exception ex)
        {
        }
    }
    return result;
}

Points of Interest

I suspect you will find other objects that might force adjustments to this method. If you do, please make note of it in the comments section below.

You should be cognizant of the possibility of running out of stack space, becauyse this method supports nested complex classes, and depending on the size and depth of nesting, you could run into memory issues. Just a friendly warning...

History

  • 10 Nov 2015 - Update to include overloads that allow specific properties to be compared.
  • 05 Nov 2015 - Updated method to include support for Dictionaries.
  • 04 Nov 2015 - Updated method to include support for StringBuilder and collections, as well as fix support for embedded complex classes.
  • 03 Nov 2015 - Original posting

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here