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

Compare .NET Objects Tutorial

0.00/5 (No votes)
4 Apr 2014 1  
A tutorial to compare .NET objects

Introduction

There is often a need to compare two complex objects. Particular examples include comparing expected vs. actual objects in unit tests, comparing complex data models, and many others which I currently can't think of. All in all, if you don't want to invent your own bicycle, searching for .NET object comparers will yield 'Compare .NET objects' library as one of the first results. It's free and open-source, feel free to get via NuGet.

Basic Usage

Let's say we have a class, which we'll call a ComplexClass:

class ComplexClass
{
    public int ID { get; set; }

    public string Name { get; set; }

    public DateTime CreatedOn { get; set; }

    public string OtherStringField { get; set; }

    public StatusEnum Status { get; set; }
}

The StatusEnum looks like this:

    enum StatusEnum
    {
        Inappropriate = 0,

        [Description("Slightly Inappropriate")]
        SlightlyInappropriate,

        Normal,

        [Description("Well-off")]
        WellOff
    }

Now, let's create 2 different class instances and perform a full comparison of them, getting all the properties that are different:

StringBuilder sb = new StringBuilder();
int propertyCount = typeof(ComplexClass).GetProperties().Length;
ComplexClass cc1 = new ComplexClass() { CreatedOn = DateTime.Now, ID = 3, 
Name = "the first", OtherStringField = "other", Status = StatusEnum.Inappropriate };
ComplexClass cc2 = new ComplexClass() { CreatedOn = DateTime.Now.AddDays(-1), ID = 5, 
Name = "the second", OtherStringField = "osf", Status = StatusEnum.Normal };

CompareLogic basicComparison = new CompareLogic() 
{ Config = new ComparisonConfig() { MaxDifferences = propertyCount } };
List<Difference> diffs = basicComparison.Compare(cc1, cc2).Differences;

foreach (Difference diff in diffs)
{
    sb.AppendLine("Property name:" + diff.PropertyName);
    sb.AppendLine("Old value:" + diff.Object1Value);
    sb.AppendLine("New value:" + diff.Object2Value + "\n");
}

using (StreamWriter outfile = new StreamWriter(path + @"\res_cmp.txt"))
{
    outfile.Write(sb.ToString());
}

Note that as we want to get all the differences, we get the property count of the class, and set MaxDifferences parameter, which defaults to 1, to the number of properties in ComplexClass class. If you just need to know if the objects are equal, and don't need all the details (e.g. in unit tests) you can leave it as 1.

If you run the piece of code above, you should get the "res_cmp.txt" file content similar to this:

Property name:.ID
Old value:3
New value:5

Property name:.Name
Old value:the first
New value:the second

Property name:.CreatedOn
Old value:4/3/2014 22:44:04
New value:4/2/2014 22:44:04

Property name:.OtherStringField
Old value:other
New value:osf

Property name:.Status
Old value:Inappropriate
New value:Normal

Advanced Usage

Custom Comparers

There are times when you'd like comparer to behave differently. For example, default enum comparer outputs enum's string value to diff list. You may want to force it to use numbers, or enum's description attribute values (e.g. you want to show output to user).

Let's do just that, and output descriptions instead of stringified enum members. Here is the custom comparer:

class CustomEnumComparer : BaseTypeComparer
{
    public CustomEnumComparer() : base(RootComparerFactory.GetRootComparer()) { }

    public override void CompareType(KellermanSoftware.CompareNetObjects.ComparisonResult result, 
        object object1, object object2, string breadCrumb)
    {
        if (object1.ToString() != object2.ToString())
        {
            Difference difference = new Difference
            {
                PropertyName = breadCrumb,
                Object1Value = EnumHelper.GetDescription(object1),
                Object2Value = EnumHelper.GetDescription(object2),
                Object1 = new WeakReference(object1),
                Object2 = new WeakReference(object2)
            };

            AddDifference(result, difference);
        }
    }

    public override bool IsTypeMatch(Type type1, Type type2)
    {
        return TypeHelper.IsEnum(type1) && TypeHelper.IsEnum(type2);
    }
}

We'll use a helper class to extract the attributes we need:

static class EnumHelper
{
    public static string GetDescription(object enumMember)
    {
        FieldInfo fi = enumMember.GetType().GetField(enumMember.ToString());
        IEnumerable<DescriptionAttribute> attrs = 
            fi.GetCustomAttributes<DescriptionAttribute>(false);
        return attrs.Any() ? attrs.ElementAt(0).Description : enumMember.ToString();
    }
}

And the updated method call:

StringBuilder sb = new StringBuilder();
int propertyCount = typeof(ComplexClass).GetProperties().Length;

ComplexClass cc1 = new ComplexClass() { CreatedOn = DateTime.Now, ID = 3, 
Name = "the new first", OtherStringField = "other", 
Status = StatusEnum.SlightlyInappropriate };
ComplexClass cc2 = new ComplexClass() { CreatedOn = DateTime.Now.AddDays(-1), ID = 3, 
Name = "the new second", OtherStringField = "other", Status = StatusEnum.WellOff };

CompareLogic advanedComparison = new CompareLogic()
{
    Config = new ComparisonConfig()
    {
        MaxDifferences = propertyCount,
        CustomComparers = new List<BaseTypeComparer>() { new CustomEnumComparer() }
    }
};
List<Difference> diffs = advanedComparison.Compare(cc1, cc2).Differences;
foreach (Difference diff in diffs)
{
    sb.AppendLine("Property name:" + diff.PropertyName);
    sb.AppendLine("Old value:" + diff.Object1Value);
    sb.AppendLine("New value:" + diff.Object2Value + "\n");
}

using (StreamWriter outfile = new StreamWriter(path + @"\res_cmp_adv.txt"))
{
    outfile.Write(sb.ToString());
}

You just have to specify your custom comparer in the config. It will always take precedence over the standard one. Results should be as follows:

Property name:.Name
Old value:the new first
New value:the new second

Property name:.CreatedOn
Old value:4/3/2014 23:38:00
New value:4/2/2014 23:38:00

Property name:.Status
Old value:Slightly Inappropriate
New value:Well-off

Summary

There have been few things to write about the advanced usage of 'Compare .NET Objects', but I really don't want to bloat the article. If this topic is popular, I may write part II, but I believe I've covered the most ways you'll use the 'Compare .NET Objects' lib. Happy programming.

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