Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C#

Typesafe .NET Object Comparison with ZCompare

Rate me:
Please Sign up or sign in to vote.
3.71/5 (7 votes)
24 Nov 2017Ms-RL7 min read 12.7K   102   11   4
This tutorial will show how you can compare any .net objects automatically with ZCompare. ZCompare is full autonomous, typesafe .Net object comparison with high resolution control through extensibility and customisation. Compare any objects you want, how you want and easily get the results you need

Introduction

Autonomous .NET object comparison, with control and customisation is something I have never found a satisfactory solution to until now. I am going to introduce ZCompare, a powerful .Net object comparison component and take you through a few simple steps to help you get the results you want. 

Background

In my career of 20+ years as a software developer I have, on many occasion needed to compare one data object with another. I have been tasked numerous times to highlight these changes and act on them in one way or another. Implementing interfaces and hand coding comparison routines to do the job can readily become cumbersome, error prone and inconsistent. ZCompare aims to overcome these problems, and more to provide powerful object comparison out of the box with no need to hand crank it to get results.

This tutorial will take you through the basic syntax and results of ZCompare through to the extensibility and customisation features.

Whilst you can read through this and get a good understanding quickly, you may want to download the demo (ZCee) project. This is a very simple C# WPF project which helps see ZCompare in action and the results it gives. Don't worry if you are not familiar with WPF, you only need to modify a simple file - ResultsViewModel.cs

The latest version can be found on the ZCompare project site http://www.zcompare.net

The tutorial uses sample data from the Zaybu.Compare.Data assembly which is included in the ZCee Project.

Contents

Step 1 - Compare 2 simple objects

Step 2 - Understanding the compare results

Step 3 - Query the results

Step 4 - Display results

Step 5 - Customisation - Ignore properties, types and namespaces

Step 6 - Extensibility

Step 7 - Key Properties

Requirements

.NET Framework 4.5 or greater
ZCompare assemblies download from nuget here
or
install the nuget package via Package Manager Console in Visual Studio

PM> Install-Package Zaybu.ZCompare

Step 1 - Compare two simple objects

We are going to compare two products, productA and productB. Here is the definition of a Product

public class Product
{
    private ProductCode _code;
    public int ID { get; set; }
    public string Description { get; set; }
    public float Price { get; set; }
    public Guid InventoryID { get; set; }
    public ProductCode Code { get { return _code; } set { _code = value; } }
    public byte[] ImageData { get; set; }
}
    
public struct ProductCode
{
    public int ProductID;
    public char Category;
}

And here is the code used to compare two instances of a Product

using Zaybu.Compare.Data;

Product productA = SampleData.CreateProduct(1);
Product productB = SampleData.CreateProduct(1);

productB.Description = "Product B"
productB.ImageData[1] = 34;

var results = ZCompare.Compare(productA, productB);

SampleData.CreateProduct(1); creates a Product with ID = 1 and populates it with some data. productA and productB are two separate instances with the same data. We modify productB by changing the Description and some of it's ImageData. Pass both objects to the ZCompare.Compare() static method and assign the returned results.

Step 2 - Understanding the compare results

Image 1

This looks promising, we have an Identical property which is false and a NumberOfDifferences

field correctly displaying 2. The Root is really what is useful though. This is a root tree node and it reflects the object graph tree structure of a Product

Let's expand it and see what's inside.

Image 2

Hopefully most of these properties are self explanatory.

ChangedToValue
Object reference to productB
ChangedToValueAsString
Display friendly representation of productB
Children
Child result nodes
IsLeaf
Flag indicating if this is the last object in the branch
OriginalValue
Object reference to productA
OriginalValueAsString
Display friendly representation of productA
Parent
Reference to any parent node
PropertyName
The name of the property being compared
Status
Comparison result status indicating that the object has changed

If we expand the Children node we will see how the results reflect the structure of a Product

Image 3

There is a node for each public property of the Product object.

Looking at the Description node we can see how that property has changed.

Image 4

Step 3 - Query the results

Quite often we find ourselves dealing with large, deep object trees. In these instances traversing the results tree to find the result you want would be tedious. We can probe and query the results for specific objects we are interested in by using

ZCompare.GetResult()

As the Product class is a little simple, now is a good time to introduce a more complex object, Supplier. The Supplier has a List<Product> as one of its properties.

public class Supplier
{
    public int ID { get; set; }
    public string Name { get; set; }
    public List<Product> Products { get; set; }
    public Dictionary<string, Address> Addresses { get; set; }
    public DateTime Created { get; set; }
    public SupplierStatus Status { get; set; }
    public Array Contacts { get; set; }
    public object Logo { get; set; }
}

In the code below we create two Supplier objects. They have seeded sample data populated for them by the CreateSupplier() method.

Supplier supplierA = SampleData.CreateSupplier(1);
Supplier supplierB = SampleData.CreateSupplier(2);

var results = ZCompare.Compare(supplierA, supplierB);

var productResults = results.GetResult(supplierA.Products);

productResults above points to the Products result node.

Another example

var productResult = results.GetResult(supplierA.Products[1]);

productResult contains the results just for the product at location 1 in the list

Image 5

And finally, to probe for specific value type properties you can pass in the property name.

var productDescriptionResult = results.GetResult(supplierA.Products[0], "Description");

Image 6

Step 4 - Display results

There is a useful method, GetSummary() on every result node

var productResult = results.GetResult(supplierA.Products[1]);
string productSummary = productResult.GetSummary(); 

This gives a simple string output similar to this.

ListItem Changed
ID Changed - from '2' to '7'
Description Changed - from 'Chocolate Volt Pencil' to 'Emergency Electromagnetic Retrostinker'
Price Changed - from '2.25' to '7.86'
InventoryID Changed - from '22222222-2222-222222222222' to '77777777-7777-777777777777'
Code Changed
	ProductID Changed - from '2' to '7'
	Category Changed - from 'B' to 'G'
ImageData Changed - from '71-93-C6-E3-6F-7C-38-98' to '4B-63-04-E1-68-FF-41-99-EA'

ZCee Viewer

For full interactive results viewing and validation, the ZCee Project is highly recommended. It displays the results in a tree view, highlighting changes and displays summaries. Source code is included so you can easily get your own objects in there for comparison.

Image 7

Step 5 - Customisation - Ignore properties, types and namespaces

This step shows us how to specify what gets compared. Large, complex object graphs will potentially produce large, complex comparison results. This obviously has a direct impact on manageability and efficiency. The good news is that ZCompare allows powerful customisation to exclude properties, types and namespaces from the comparison. Let's take another look at the Product type again and it's ImageData property.

...
ImageData Changed - from '71-93-C6-E3-6F-7C-38-98' to '4B-63-04-E1-68-FF-41-99-EA'
...

Let's assume that we are not really interested in whether it has changed or not, we want to ignore it. We have two options.

1) Ignore a single property declaratively

Use the Zaybu.Compare.IgnorePropertyAttribute

public class Product
{
    [IgnoreProperty]
    public byte[] ImageData { get; set; }

Or

2) Inject at runtime

This is really useful for objects you don't have source control over!

ZCompare.IgnoreProperty(typeof(Product), "ImageData", typeof(byte[]));

Similarly we have the ability to ignore types and complete namespaces.

// Exclude a type from being compared            
ZCompare.IgnoreType(typeof(byte[]));

// Exclude an entire namespace from being compared            
ZCompare.IgnoreNamespace("System.Runtime");

Step 6 - Extensibility

This step concentrates on how objects and properties get compared. Again this is very powerful and allows complete control over the comparison operation.

Let's assume that we want to take control over how our Supplier class is compared, and that actually we only need to do a much simpler comparison.

We create our own custom comparitor by implementing...

Zaybu.Compare.Comparitors.ComparitorBase<T>

... and overriding the Compare<T>() method like so...

public class SupplierCustomComparitor : ComparitorBase<Supplier>
{
    public override void Compare(Supplier originalObject, Supplier compareToObject, Results results)
    {
        /* In here we have complete control over what and how things are compared */ 
        if (originalObject.ID != compareToObject.ID)
        {
            results.AddResult("Supplier ID's are different", ResultStatus.Changed, originalObject.ID, compareToObject.ID);
        }
    }
}

...and registering the custom comparitor with ZCompare.

ZCompare.RegisterComparitor(new SupplierCustomComparitor());
var results = ZCompare.Compare(supplierA, supplierB);

Taking a look at results.Root.GetSummary()

Zaybu.Compare.Data.Supplier Changed
Supplier ID's are different Changed - from '5' to '6'

This demonstrates that we can simplify the comparison operation by implementing a simple rule, if the id's are different then assume the objects are to be different regardless of if the object data is exactly the same.

Note, you can still use ZCompare within the Compare() override

public class SupplierCustomComparitor : ComparitorBase<Supplier>
{
    public override void Compare(Supplier originalObject, Supplier compareToObject, Results results)
    {
        /* In here we have complete control over what and how things are compared */ 
        if (originalObject.ID != compareToObject.ID)
        {
            results.AddResult("Supplier ID's are different", ResultStatus.Changed, originalObject.ID, compareToObject.ID);
        }
        var productResults = ZCompare.Compare(originalObject.Products, compareToObject.Products);
    }
}

Another method to override is the GetStringValue() method. This method determines the display format of the properties being compared. For example, we can create a custom comparitor for the DateTime system type.

public class DateTimeComparitor : ComparitorBase<DateTime>
{
    public override string GetStringValue(DateTime value)
    {
        /* I want my dates displayed in RFC1123 format */
        return String.Format("{0:r}", value);
    }

    public override void Compare(DateTime baseProperty, DateTime compareToProperty, ZCompareResults results)
    {
        /* I want my dates to ignore any time component and just compare on the date value */
        if (baseProperty.ToShortDateString() != compareToProperty.ToShortDateString())
        { 
            results.CurrentResult.Status = ResultStatus.Changed;
        }   
    }
}

Looking at the results below, we can see the format of the Created property is displayed as we specified. Also the Status is NoChange despite the fact that the actual values will differ by a few milliseconds, which we chose to ignore.

var results = ZCompare.Compare(supplierA, supplierB);
var createdResult = results.GetResult(supplierA, "Created";

Image 8

Another simple example to handle string comparisons how you want.

public class StringCustomComparitor : ComparitorBase<string>
{
    public override void Compare(string originalObject, string compareToObject, Results results)
    {
        /* I want to ignore case for my string comparison */ 
        if (!String.Equals(originalObject, compareToObject, StringComparison.OrdinalIgnoreCase)
        {
            results.AddResult("My strings are different!", ResultStatus.Changed, originalObject, compareToObject);
        }
    }
}

Step 7 - Key Properties

When dealing with arrays, collections and lists etc, you may want to compare items in one with items in the other regardless of if they have changed position.

public class Fruit
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Colour { get; set; }
}

List<Fruit> originalFruits = new List<Fruit>();
originalFruits.Add(new Fruit { ID = 1, Name = "Apple", Colour = "Green" });
originalFruits.Add(new Fruit { ID = 2, Name = "Banana", Colour = "Yellow" });
originalFruits.Add(new Fruit { ID = 3, Name = "Cherry", Colour = "Red" });

Let us assume that we have another list of fruits but in a different order

List<Fruit> newFruits = new List<Fruit>();
newFruits.Add(new Fruit { ID = 3, Name = "Cherry", Colour = "Red" });  
newFruits.Add(new Fruit { ID = 2, Name = "Banana", Colour = "Yellow" });
newFruits.Add(new Fruit { ID = 1, Name = "Apple", Colour = "Green" })

By default, ZCompare will compare the apple in originalFruits with the cherry in newFruits. This is because they are the first object in each of the lists. This may, or not make sense in your application. What you would like though is to have the choice. So for this example we want to compare our apples with another apple and our cherries with other cherries. This is where the KeyPropertyAttribute comes in.

public class Fruit
{    
    public int ID { get; set; }
    [KeyProperty]
    public string Name { get; set; }
    public string Colour { get; set; }
}

This can be declared at compile time as above or injected at runtime below. Again really useful for objects that you don't have source control over!

ZCompare.SetKeyProperty(typeof(Fruit), "Name");

You can also have more than one property to make a compound key. So, in the example below we are only going to compare a red apple with another red apple. If we had a green one then that would be treated as something different.

ZCompare.SetKeyProperties(typeof(Fruit), new List<Fruit> { "Name", "Colour" });

/* And of course declaratively */
public class Fruit
{    
    public int ID { get; set; }
    [KeyProperty]
    public string Name { get; set; }
    [KeyProperty]
    public string Colour { get; set; }
}

License

This article, along with any associated source code and files, is licensed under Microsoft Reciprocal License


Written By
Technical Lead
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionAwesome Pin
Hamed Fathi26-Jan-17 2:29
Hamed Fathi26-Jan-17 2:29 
AnswerRe: Awesome Pin
Zaybu26-Jan-17 22:32
Zaybu26-Jan-17 22:32 
GeneralMy vote of 4 Pin
CarelAgain25-Jan-17 8:53
professionalCarelAgain25-Jan-17 8:53 
GeneralRe: My vote of 4 Pin
Zaybu25-Jan-17 22:24
Zaybu25-Jan-17 22:24 

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.