Click here to Skip to main content
15,886,519 members
Articles / Programming Languages / C# 3.5
Alternative
Tip/Trick

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

Rate me:
Please Sign up or sign in to vote.
4.43/5 (4 votes)
3 Nov 2015CPOL2 min read 36.4K   9   37
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

C#
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...

C#
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.

C#
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.

C#
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.

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Paddedwall Software
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.

My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions

 
QuestionThoughts Pin
Jörgen Andersson10-Nov-15 22:02
professionalJörgen Andersson10-Nov-15 22:02 
AnswerRe: Thoughts Pin
#realJSOP23-Sep-16 2:03
mve#realJSOP23-Sep-16 2:03 
QuestionComparing Sequences Pin
Jörgen Andersson10-Nov-15 8:17
professionalJörgen Andersson10-Nov-15 8:17 
AnswerRe: Comparing Sequences Pin
#realJSOP10-Nov-15 9:22
mve#realJSOP10-Nov-15 9:22 
GeneralRe: Comparing Sequences Pin
Jörgen Andersson10-Nov-15 9:30
professionalJörgen Andersson10-Nov-15 9:30 
GeneralRe: Comparing Sequences Pin
#realJSOP11-Nov-15 0:05
mve#realJSOP11-Nov-15 0:05 
QuestionAdded Support Pin
#realJSOP5-Nov-15 0:22
mve#realJSOP5-Nov-15 0:22 
AnswerRe: Added Support Pin
Graeme_Grant5-Nov-15 3:26
mvaGraeme_Grant5-Nov-15 3:26 
GeneralRe: Added Support Pin
#realJSOP5-Nov-15 4:25
mve#realJSOP5-Nov-15 4:25 
GeneralRe: Added Support Pin
Graeme_Grant5-Nov-15 11:06
mvaGraeme_Grant5-Nov-15 11:06 
GeneralRe: Added Support PinPopular
#realJSOP10-Nov-15 5:37
mve#realJSOP10-Nov-15 5:37 
GeneralRe: Added Support Pin
#realJSOP10-Nov-15 5:58
mve#realJSOP10-Nov-15 5:58 
BugFound a bug - ref: mppruthviraj Pin
Graeme_Grant4-Nov-15 5:18
mvaGraeme_Grant4-Nov-15 5:18 
GeneralRe: Found a bug - ref: mppruthviraj Pin
#realJSOP4-Nov-15 5:27
mve#realJSOP4-Nov-15 5:27 
SuggestionDateTime handling Pin
Graeme_Grant4-Nov-15 4:43
mvaGraeme_Grant4-Nov-15 4:43 
GeneralRe: DateTime handling Pin
#realJSOP4-Nov-15 5:13
mve#realJSOP4-Nov-15 5:13 
GeneralRe: DateTime handling Pin
Graeme_Grant4-Nov-15 5:21
mvaGraeme_Grant4-Nov-15 5:21 
SuggestionRe: DateTime handling Pin
Graeme_Grant5-Nov-15 3:30
mvaGraeme_Grant5-Nov-15 3:30 
QuestionVB Version Pin
georani3-Nov-15 6:37
georani3-Nov-15 6:37 
AnswerRe: VB Version Pin
#realJSOP3-Nov-15 6:39
mve#realJSOP3-Nov-15 6:39 
GeneralRe: VB Version Pin
georani3-Nov-15 9:42
georani3-Nov-15 9:42 
GeneralRe: VB Version Pin
Graeme_Grant4-Nov-15 4:45
mvaGraeme_Grant4-Nov-15 4:45 
GeneralRe: VB Version Pin
#realJSOP4-Nov-15 23:46
mve#realJSOP4-Nov-15 23:46 
GeneralRe: VB Version Pin
Graeme_Grant5-Nov-15 3:31
mvaGraeme_Grant5-Nov-15 3:31 
AnswerRe: VB Version Pin
Graeme_Grant4-Nov-15 4:53
mvaGraeme_Grant4-Nov-15 4:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.