158

I have two complex objects like Object1 and Object2. They have around 5 levels of child objects.

I need the fastest method to say if they are same or not.

How could this be done in C# 4.0?

Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197
NoWar
  • 36,338
  • 80
  • 323
  • 498

18 Answers18

130

Implement IEquatable<T> (typically in conjunction with overriding the inherited Object.Equals and Object.GetHashCode methods) on all your custom types. In the case of composite types, invoke the contained types’ Equals method within the containing types. For contained collections, use the SequenceEqual extension method, which internally calls IEquatable<T>.Equals or Object.Equals on each element. This approach will obviously require you to extend your types’ definitions, but its results are faster than any generic solutions involving serialization.

Edit: Here is a contrived example with three levels of nesting.

For value types, you can typically just call their Equals method. Even if the fields or properties were never explicitly assigned, they would still have a default value.

For reference types, you should first call ReferenceEquals, which checks for reference equality – this would serve as an efficiency boost when you happen to be referencing the same object. It would also handle cases where both references are null. If that check fails, confirm that your instance's field or property is not null (to avoid NullReferenceException) and call its Equals method. Since our members are properly typed, the IEquatable<T>.Equals method gets called directly, bypassing the overridden Object.Equals method (whose execution would be marginally slower due to the type cast).

When you override Object.Equals, you’re also expected to override Object.GetHashCode; I didn’t do so below for the sake of conciseness.

public class Person : IEquatable<Person>
{
    public int Age { get; set; }
    public string FirstName { get; set; }
    public Address Address { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Person);
    }

    public bool Equals(Person other)
    {
        if (other == null)
            return false;

        return this.Age.Equals(other.Age) &&
            (
                object.ReferenceEquals(this.FirstName, other.FirstName) ||
                this.FirstName != null &&
                this.FirstName.Equals(other.FirstName)
            ) &&
            (
                object.ReferenceEquals(this.Address, other.Address) ||
                this.Address != null &&
                this.Address.Equals(other.Address)
            );
    }
}

public class Address : IEquatable<Address>
{
    public int HouseNo { get; set; }
    public string Street { get; set; }
    public City City { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Address);
    }

    public bool Equals(Address other)
    {
        if (other == null)
            return false;

        return this.HouseNo.Equals(other.HouseNo) &&
            (
                object.ReferenceEquals(this.Street, other.Street) ||
                this.Street != null &&
                this.Street.Equals(other.Street)
            ) &&
            (
                object.ReferenceEquals(this.City, other.City) ||
                this.City != null &&
                this.City.Equals(other.City)
            );
    }
}

public class City : IEquatable<City>
{
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as City);
    }

    public bool Equals(City other)
    {
        if (other == null)
            return false;

        return
            object.ReferenceEquals(this.Name, other.Name) ||
            this.Name != null &&
            this.Name.Equals(other.Name);
    }
}

Update: This answer was written several years ago. Since then, I've started to lean away from implementing IEquality<T> for mutable types for such scenarios. There are two notions of equality: identity and equivalence. At a memory representation level, these are popularly distinguished as “reference equality” and “value equality” (see Equality Comparisons). However, the same distinction can also apply at a domain level. Suppose that your Person class has a PersonId property, unique per distinct real-world person. Should two objects with the same PersonId but different Age values be considered equal or different? The answer above assumes that one is after equivalence. However, there are many usages of the IEquality<T> interface, such as collections, that assume that such implementations provide for identity. For example, if you're populating a HashSet<T>, you would typically expect a TryGetValue(T,T) call to return existing elements that share merely the identity of your argument, not necessarily equivalent elements whose contents are completely the same. This notion is enforced by the notes on GetHashCode:

In general, for mutable reference types, you should override GetHashCode() only if:

  • You can compute the hash code from fields that are not mutable; or
  • You can ensure that the hash code of a mutable object does not change while the object is contained in a collection that relies on its hash code.
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • I get this object via RIA Services... Can I use IEquatable for those objects and and get it under the WPF client? – NoWar May 04 '12 at 18:57
  • 1
    You mean that the classes are auto-generated? I haven’t used RIA Services, but I would assume that any generated classes would be declared as `partial` – in which case, yes, you can implement their `Equals` method through a manually-added partial class declaration which references fields/properties from the auto-generated one. – Douglas May 04 '12 at 19:02
  • What if "Address address" is actually "Address[] adresses", how would it be implemented ? – guiomie Nov 26 '12 at 14:28
  • 2
    You could call the LINQ [`Enumerable.SequenceEqual`](http://msdn.microsoft.com/en-us/library/bb348567.aspx) method on the arrays: `this.Addresses.SequenceEqual(other.Addresses)`. This would internally call your `Address.Equals` method for each pair of corresponding addresses, given that the `Address` class implements the `IEquatable
    ` interface.
    – Douglas Nov 26 '12 at 17:31
  • or I get wrong operator precedence or you mean `this.FirstName == other.FirstName ||( this.FirstName != null && this.FirstName.Equals(other.FirstName))` – giammin Sep 22 '14 at 15:03
  • @giammin: `&&` has higher precedence than `||`. See [C# Operators](http://msdn.microsoft.com/en-us/library/6a71f45d.aspx). – Douglas Sep 22 '14 at 17:12
  • @Douglas yes I know but I though you wanted to first check for equality with == operator and if it fails check if property is not null and then check with Equals() – giammin Sep 23 '14 at 07:44
  • @giammin: Yes, that's what happens. The parentheses you suggest would give exactly the same behaviour as the original code. – Douglas Sep 23 '14 at 16:30
  • 2
    Another category of comparison that a developer might check is "WorksLike". For me, this means that even though two instances might have some unequal property values, the program will produce the same result from processing the two instances. – John Kurtz Dec 07 '18 at 16:37
125

Serialize both objects and compare the resulting strings

JoelFan
  • 37,465
  • 35
  • 132
  • 205
  • This seems to be a good trick. Is there a performance penalty? – osiris May 08 '17 at 17:07
  • 3
    I don't see why there would be. Serialization is normally an optimized process and you need to access every property's value in any case. – JoelFan May 09 '17 at 00:15
  • 20
    There's a huge cost. You're generating a data stream, appending strings, and then testing string equality. Orders of magnitude, just in that. Not to mention serialization is going to be using reflection by default. – Jerome Haltom May 19 '17 at 15:06
  • 2
    Data stream is no big deal, I don't see why you would need to append strings... testing string equality is one of the most optimized operations out there.... you may have a point with reflection... but on the whole serialization will not be "orders of magnitude" worse than other methods. You should do benchmarks if you suspect performance problems... I have not experienced performance problems with this method – JoelFan May 21 '17 at 07:21
  • 16
    I am `+1` this simply because I have never thought about doing a values-based equality comparison this way. It's nice and simple. It'd be neat to see some benchmarks with this code. – Thomas May 22 '17 at 20:23
  • You could always serialise to byte arrays and compare those if string comparison is a worry, not sure it would be any quicker though but strings aren't the only option for serialisation (https://stackoverflow.com/a/4143614/192305) – Shawson Jul 31 '19 at 10:02
  • 1
    That is not a good solution as both serialization may go wrong in a similar way. For instance some properties of the source object may not have been serialized and when deserialized will get set to null in the destination object. In such a case, your test that compares strings will pass, but both objects are actually not the same! – stackMeUp Nov 26 '19 at 11:19
  • @stackMeUp well, obviously it should be well designed and tested – JoelFan Nov 29 '19 at 03:09
  • Well, if your test is comparing strings, that might be an issue though ;-) – stackMeUp Nov 29 '19 at 08:49
  • To follow up on @stackMeUp in my case I am ensuring that serialized key value pairs are not duplicated in the database. If I simply compare the strings they could evaluate as not duplicates even though if they were sorted they would evaluate as duplicates. – Slagmoth Feb 18 '20 at 16:44
  • It also depends on whether you want to lists with the same elements but different order to bo considered equal, if that is the case then this approach won't work. – havij Mar 12 '20 at 20:31
  • This can be an extremely straightforward approach to implement; before disregarding it because of performance concerns I would try out a quick POC and measure the actual runtime / memory overhead, then decide. – StayOnTarget Oct 31 '22 at 19:07
57

You can use extension method, recursion to resolve this problem:

public static bool DeepCompare(this object obj, object another)
{     
  if (ReferenceEquals(obj, another)) return true;
  if ((obj == null) || (another == null)) return false;
  //Compare two object's class, return false if they are difference
  if (obj.GetType() != another.GetType()) return false;

  var result = true;
  //Get all properties of obj
  //And compare each other
  foreach (var property in obj.GetType().GetProperties())
  {
      var objValue = property.GetValue(obj);
      var anotherValue = property.GetValue(another);
      if (!objValue.Equals(anotherValue)) result = false;
  }

  return result;
 }

public static bool CompareEx(this object obj, object another)
{
 if (ReferenceEquals(obj, another)) return true;
 if ((obj == null) || (another == null)) return false;
 if (obj.GetType() != another.GetType()) return false;

 //properties: int, double, DateTime, etc, not class
 if (!obj.GetType().IsClass) return obj.Equals(another);

 var result = true;
 foreach (var property in obj.GetType().GetProperties())
 {
    var objValue = property.GetValue(obj);
    var anotherValue = property.GetValue(another);
    //Recursion
    if (!objValue.DeepCompare(anotherValue))   result = false;
 }
 return result;
}

or compare by using Json (if object is very complex) You can use Newtonsoft.Json:

public static bool JsonCompare(this object obj, object another)
{
  if (ReferenceEquals(obj, another)) return true;
  if ((obj == null) || (another == null)) return false;
  if (obj.GetType() != another.GetType()) return false;

  var objJson = JsonConvert.SerializeObject(obj);
  var anotherJson = JsonConvert.SerializeObject(another);

  return objJson == anotherJson;
}
Jonathan
  • 1,017
  • 10
  • 21
  • 1
    First solution is great! I like that you don't have to json serialize or implement add any code to the objects themselves. Suitable when you are just comparing for unit tests. Might I suggest adding a simple compare in case objValue and anotherValue both equal null? This will avoid a NullReferenceException been thrown when trying to do null.Equals() // ReSharper disable once RedundantJumpStatement if (objValue == anotherValue) continue; //null reference guard else if(!objValue.Equals(anotherValue)) Fail(expected, actual); – Mark Conway Mar 17 '17 at 07:10
  • 7
    Is there any reason to use `DeepCompare` instead of simply calling `CompareEx` recursively? – apostolov Nov 28 '17 at 16:58
  • 4
    This may compare the entire structure unnecessarily. Replacing `result` with `return false` would make it more efficient. – Tim Sylvester Oct 16 '18 at 22:13
  • This was very helpful for a project I'm working on. I had to add `if (!obj.GetType().IsClass) return obj.Equals(another);` into the DeepCompare function to get this working. – Matthew MacFarland Feb 10 '22 at 15:27
30

If you don't want to implement IEquatable, you can always use Reflection to compare all the properties: - if they're value type, just compare them -if they are reference type, call the function recursively to compare its "inner" properties.

I'm not thinking about performace, but about simplicity. It depends, however on the exact design of your objects. It could get complicated depending on your objects shape (for example if there are cyclic dependencies between properties). There are, however, several solutions out there that you can use, like this one:

Another option is to serialize the object as text, for example using JSON.NET, and comparing the serialization result. (JSON.NET can handle Cyclic dependencies between properties).

I don't know if by fastest you mean the fastest way to implement it or a code that runs fast. You should not optimize before knowing if you need to. Premature optimization is the root of all evil

JotaBe
  • 38,030
  • 8
  • 98
  • 117
  • 1
    I hardly think that an `IEquatable` implementation qualifies as a case of premature optimization. Reflection will be drastically slower. The default implementation of `Equals` for custom value types does use reflection; Microsoft themselves recommend overriding it for performance: “Override the `Equals` method for a particular type to improve the performance of the method and more closely represent the concept of equality for the type.” – Douglas May 04 '12 at 19:54
  • 1
    It depends on how many times he's going to run the equals method: 1, 10, 100, 100, a million? That will make a big difference. If he can use a generic solution without implementing anything he will spare some precious time. If it's too slow, then it's time to implement IEquatable (and perhaps even trying to make a cacheable or intelligent GetHashCode) As far as the speed of Reflection I must agree it's slower... or much slower, depending on how you do it (i.e. reusing PropertyInfos Types and so on, or not). – JotaBe May 04 '12 at 20:01
  • @Worthy7 It does. Please, see the contents of the project. Tests are a good way of documenting by example. But, better than that, if you look for it, you'll find a .chm help file. So this project has much better documentation than most projects. – JotaBe Jun 28 '17 at 15:03
  • Sorry you're right, I totally missed the "wiki" tab. I'm used to everyone writing things in the readme. – Worthy7 Jun 29 '17 at 04:05
10

Serialize both objects and compare the resulting strings by @JoelFan

So to do this, create a static class like so and use Extensions to extend ALL objects (so you can pass anytype of object, collection, etc into the method)

using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;

public static class MySerializer
{
    public static string Serialize(this object obj)
    {
        var serializer = new DataContractJsonSerializer(obj.GetType());
        using (var ms = new MemoryStream())
        {
            serializer.WriteObject(ms, obj);
            return Encoding.Default.GetString(ms.ToArray());
        }
    }
}

Once you reference this static class in any other file, you can do this:

Person p = new Person { Firstname = "Jason", LastName = "Argonauts" };
Person p2 = new Person { Firstname = "Jason", LastName = "Argonaut" };
//assuming you have already created a class person!
string personString = p.Serialize();
string person2String = p2.Serialize();

Now you can simply use .Equals to compare them. I use this for checking if objects are in collections too. It works really well.

Andy
  • 2,124
  • 1
  • 26
  • 29
  • What if the contents of the objects are arrays of floating point numbers. It is very inefficient to convert those to strings, and the conversion is subject to transformations defined in `CultrureInfo`. This would only work if the internal data is mostly strings and integers. Otherwise it would be a disaster. – John Alexiou Sep 16 '16 at 18:38
  • 5
    What if a new director told you to rip out C# and replace it with Python. As developers, we need to learn that the what if questions have to stop somewhere. Solve the problem, on to the next. If you EVER get time, come back to it... – Andy Sep 19 '16 at 13:19
  • 3
    Python is more like MATLAB in syntax and usage. There must a really good reason to move from a static type safe language to a mishmash script like python. – John Alexiou Sep 22 '16 at 02:38
  • 1
    It's easy to use and reliable... But this is very CPU intensive. Watch out! – Daniel Ribeiro Jul 27 '23 at 01:13
9

You can now use json.net. Just go on Nuget and install it.

And you can do something like this:

public bool Equals(SamplesItem sampleToCompare)
{
    string myself = JsonConvert.SerializeObject(this);
    string other = JsonConvert.SerializeObject(sampleToCompare);

    return myself == other;
}

You could perhaps make a extension method for object if you wanted to get fancier. Please note this only compares the public properties. And if you wanted to ignore a public property when you do the comparison you could use the [JsonIgnore] attribute.

live2
  • 3,771
  • 2
  • 37
  • 46
ashlar64
  • 1,054
  • 1
  • 9
  • 24
  • 1
    If you have lists in your objects and those have lists then trying to traverse both objects are going to be a nightmare. If you serialize both and then compare you won't have to deal with that scenario. – ashlar64 Jul 01 '20 at 16:51
  • 1
    If your complex object has a Dictionary in them I do not believe the .net serializer can serialize it. The Json serializer can. – ashlar64 Jul 01 '20 at 16:51
8

Serialize both objects, then calculate Hash Code, then compare.

  • Note that calculating the hash code will require reading every character of both serializations, but just comparing the strings could terminate early, doing less work. – NetMage Jun 20 '22 at 20:03
8

If you have a requirement where you want the class which is immutable. I mean that none of the properties can be modified once it's been created. In that case, C# 9 have a feature which is called a record.

You can easily compare records by values and types is they are equal.

public record Person
{
    public string LastName { get; }
    public string FirstName { get; }

    public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

var person1 = new Person("Bill", "Wagner");
var person2 = new Person("Bill", "Wagner");

Console.WriteLine(person1 == person2); // true
Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197
  • Answer seems to do what was asked, though the question contains "How could this be done in C# 4.0?" so c# 9 feature would be no option. – Rand Random Mar 12 '21 at 08:57
  • 1
    @RandRandom yes, that is why I have mentioned the version in my answer. So if someone has the same query and they are using the same version or above in future, this answer will help them. – Vivek Nuna Mar 12 '21 at 09:32
6

I'll assume you are not referring to literally the same objects

Object1 == Object2

You might be thinking about doing a memory comparison between the two

memcmp(Object1, Object2, sizeof(Object.GetType())

But that's not even real code in c# :). Because all of your data is probably created on the heap, the memory is not contiguous and you can't just compare the equality of two objects in an agnostic manner. You're going to have to compare each value, one at a time, in a custom way.

Consider adding the IEquatable<T> interface to your class, and define a custom Equals method for your type. Then, in that method, manual test each value. Add IEquatable<T> again on enclosed types if you can and repeat the process.

class Foo : IEquatable<Foo>
{
  public bool Equals(Foo other)
  {
    /* check all the values */
    return false;
  }
}
payo
  • 4,501
  • 1
  • 24
  • 32
4

Based off a few answers already given here I decided to mostly back JoelFan's answer. I love extension methods and these have been working great for me when none of the other solutions would using them to compare my complex classes.

Extension Methods

using System.IO;
using System.Xml.Serialization;

static class ObjectHelpers
{
    public static string SerializeObject<T>(this T toSerialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

        using (StringWriter textWriter = new StringWriter())
        {
            xmlSerializer.Serialize(textWriter, toSerialize);
            return textWriter.ToString();
        }
    }

    public static bool EqualTo(this object obj, object toCompare)
    {
        if (obj.SerializeObject() == toCompare.SerializeObject())
            return true;
        else
            return false;
    }

    public static bool IsBlank<T>(this T obj) where T: new()
    {
        T blank = new T();
        T newObj = ((T)obj);

        if (newObj.SerializeObject() == blank.SerializeObject())
            return true;
        else
            return false;
    }

}

Usage Examples

if (record.IsBlank())
    throw new Exception("Record found is blank.");

if (record.EqualTo(new record()))
    throw new Exception("Record found is blank.");
Arvo Bowen
  • 4,524
  • 6
  • 51
  • 109
3

I would say that:

Object1.Equals(Object2)

would be what you're looking for. That's if you're looking to see if the objects are the same, which is what you seem to be asking.

If you want to check to see if all the child objects are the same, run them through a loop with the Equals() method.

PaulG
  • 6,920
  • 12
  • 54
  • 98
  • 3
    If and only if they provide a non-reference equality overload of Equals. – user7116 May 04 '12 at 18:55
  • 1
    Each class must implement its own way of comparison. If author's classes have no overrides for Equals() method, they will use basic method of System.Object() class, which will lead to errors in logic. – Dima May 04 '12 at 18:59
3

I found this below function for comparing objects.

 static bool Compare<T>(T Object1, T object2)
 {
      //Get the type of the object
      Type type = typeof(T);

      //return false if any of the object is false
      if (object.Equals(Object1, default(T)) || object.Equals(object2, default(T)))
         return false;

     //Loop through each properties inside class and get values for the property from both the objects and compare
     foreach (System.Reflection.PropertyInfo property in type.GetProperties())
     {
          if (property.Name != "ExtensionData")
          {
              string Object1Value = string.Empty;
              string Object2Value = string.Empty;
              if (type.GetProperty(property.Name).GetValue(Object1, null) != null)
                    Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString();
              if (type.GetProperty(property.Name).GetValue(object2, null) != null)
                    Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString();
              if (Object1Value.Trim() != Object2Value.Trim())
              {
                  return false;
              }
          }
     }
     return true;
 }

I am using it and it is working fine for me.

Akshay
  • 354
  • 3
  • 6
  • 1
    The first `if` means that `Compare(null, null) == false` which isn't what I would expect. – Tim Sylvester Oct 16 '18 at 22:16
  • `type.GetProperty(property.Name).GetValue...` should just be `property.GetValue...`. Also missing `type.GetFields()` and appropriate bindings. Could also simplify null checking with `Convert.ToString( property.GetValue(object1) )`. – drzaus Oct 14 '20 at 21:31
3

Thanks to the example of Jonathan. I expanded it for all cases (arrays, lists, dictionaries, primitive types).

This is a comparison without serialization and does not require the implementation of any interfaces for compared objects.

        /// <summary>Returns description of difference or empty value if equal</summary>
        public static string Compare(object obj1, object obj2, string path = "")
        {
            string path1 = string.IsNullOrEmpty(path) ? "" : path + ": ";
            if (obj1 == null && obj2 != null)
                return path1 + "null != not null";
            else if (obj2 == null && obj1 != null)
                return path1 + "not null != null";
            else if (obj1 == null && obj2 == null)
                return null;

            if (!obj1.GetType().Equals(obj2.GetType()))
                return "different types: " + obj1.GetType() + " and " + obj2.GetType();

            Type type = obj1.GetType();
            if (path == "")
                path = type.Name;

            if (type.IsPrimitive || typeof(string).Equals(type))
            {
                if (!obj1.Equals(obj2))
                    return path1 + "'" + obj1 + "' != '" + obj2 + "'";
                return null;
            }
            if (type.IsArray)
            {
                Array first = obj1 as Array;
                Array second = obj2 as Array;
                if (first.Length != second.Length)
                    return path1 + "array size differs (" + first.Length + " vs " + second.Length + ")";

                var en = first.GetEnumerator();
                int i = 0;
                while (en.MoveNext())
                {
                    string res = Compare(en.Current, second.GetValue(i), path);
                    if (res != null)
                        return res + " (Index " + i + ")";
                    i++;
                }
            }
            else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type))
            {
                System.Collections.IEnumerable first = obj1 as System.Collections.IEnumerable;
                System.Collections.IEnumerable second = obj2 as System.Collections.IEnumerable;

                var en = first.GetEnumerator();
                var en2 = second.GetEnumerator();
                int i = 0;
                while (en.MoveNext())
                {
                    if (!en2.MoveNext())
                        return path + ": enumerable size differs";

                    string res = Compare(en.Current, en2.Current, path);
                    if (res != null)
                        return res + " (Index " + i + ")";
                    i++;
                }
            }
            else
            {
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
                {
                    try
                    {
                        var val = pi.GetValue(obj1);
                        var tval = pi.GetValue(obj2);
                        if (path.EndsWith("." + pi.Name))
                            return null;
                        var pathNew = (path.Length == 0 ? "" : path + ".") + pi.Name;
                        string res = Compare(val, tval, pathNew);
                        if (res != null)
                            return res;
                    }
                    catch (TargetParameterCountException)
                    {
                        //index property
                    }
                }
                foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
                {
                    var val = fi.GetValue(obj1);
                    var tval = fi.GetValue(obj2);
                    if (path.EndsWith("." + fi.Name))
                        return null;
                    var pathNew = (path.Length == 0 ? "" : path + ".") + fi.Name;
                    string res = Compare(val, tval, pathNew);
                    if (res != null)
                        return res;
                }
            }
            return null;
        }

For easy copying of the code created repository

Alexey Obukhov
  • 834
  • 9
  • 18
2
public class GetObjectsComparison
{
    public object FirstObject, SecondObject;
    public BindingFlags BindingFlagsConditions= BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
}
public struct SetObjectsComparison
{
    public FieldInfo SecondObjectFieldInfo;
    public dynamic FirstObjectFieldInfoValue, SecondObjectFieldInfoValue;
    public bool ErrorFound;
    public GetObjectsComparison GetObjectsComparison;
}
private static bool ObjectsComparison(GetObjectsComparison GetObjectsComparison)
{
    GetObjectsComparison FunctionGet = GetObjectsComparison;
    SetObjectsComparison FunctionSet = new SetObjectsComparison();
    if (FunctionSet.ErrorFound==false)
        foreach (FieldInfo FirstObjectFieldInfo in FunctionGet.FirstObject.GetType().GetFields(FunctionGet.BindingFlagsConditions))
        {
            FunctionSet.SecondObjectFieldInfo =
            FunctionGet.SecondObject.GetType().GetField(FirstObjectFieldInfo.Name, FunctionGet.BindingFlagsConditions);

            FunctionSet.FirstObjectFieldInfoValue = FirstObjectFieldInfo.GetValue(FunctionGet.FirstObject);
            FunctionSet.SecondObjectFieldInfoValue = FunctionSet.SecondObjectFieldInfo.GetValue(FunctionGet.SecondObject);
            if (FirstObjectFieldInfo.FieldType.IsNested)
            {
                FunctionSet.GetObjectsComparison =
                new GetObjectsComparison()
                {
                    FirstObject = FunctionSet.FirstObjectFieldInfoValue
                    ,
                    SecondObject = FunctionSet.SecondObjectFieldInfoValue
                };

                if (!ObjectsComparison(FunctionSet.GetObjectsComparison))
                {
                    FunctionSet.ErrorFound = true;
                    break;
                }
            }
            else if (FunctionSet.FirstObjectFieldInfoValue != FunctionSet.SecondObjectFieldInfoValue)
            {
                FunctionSet.ErrorFound = true;
                break;
            }
        }
    return !FunctionSet.ErrorFound;
}
matan justme
  • 371
  • 3
  • 15
1

One way to do this would be to override Equals() on each type involved. For example, your top level object would override Equals() to call the Equals() method of all 5 child objects. Those objects should all override Equals() as well, assuming they are custom objects, and so on until the entire hierarchy could be compared by just performing an equality check on the top level objects.

goric
  • 11,491
  • 7
  • 53
  • 69
1

Use IEquatable<T> Interface which has a method Equals.

shA.t
  • 16,580
  • 5
  • 54
  • 111
Master Chief
  • 2,520
  • 19
  • 28
1

Generic Extension Method

public static class GenericExtensions
{
    public static bool DeepCompare<T>(this T objA, T objB)
    {
        if (typeof(T).IsValueType)
            return objA.Equals(objB);

        if (ReferenceEquals(objA, objB))
            return true;

        if ((objA == null) || (objB == null))
            return false;

        if (typeof(T) is IEnumerable)
        {
            var enumerableA = (IEnumerable<T>) objA;
            var enumerableB = (IEnumerable<T>) objB;

            if (enumerableA.Count() != enumerableB.Count())
                return false;

            using (var enumeratorA = enumerableA.GetEnumerator())
            using (var enumeratorB = enumerableB.GetEnumerator())
            {
                while (true)
                {
                    bool moveNextA = enumeratorA.MoveNext();
                    bool moveNextB = enumeratorB.MoveNext();

                    if (!moveNextA || !moveNextB)
                        break;

                    var currentA = enumeratorA.Current;
                    var currentB = enumeratorB.Current;

                    if (!currentA.DeepCompare<T>(currentB))
                        return false;
                }

                return true;
            }
        }

        foreach (var property in objA.GetType().GetProperties())
        {
            var valueA = property.GetValue(objA);
            var valueB = property.GetValue(objB);

            if (!valueA.DeepCompare(valueB))
                return false;
        }

        return true;
    }
}
PROMETHEUS
  • 29
  • 4
1

To return each property updated:

    public IEnumerable<string> GetPropsUpdated(T oldModel, T newModel)
    {
        var diff = new List<string>();

        foreach (var prop in oldModel.GetType().GetProperties())
        {
            var oldValue = prop.GetValue(oldModel);
            var newValue = prop.GetValue(newModel);

            if (oldValue == null && newValue == null)
                continue;

            if (oldValue == null && newValue != null 
                || oldValue != null && newValue == null)
            {
                diff.Add(prop.Name);
                continue;
            }

            var oldPropHashed = oldValue.GetHashCode();
            var newPropHashed = newValue.GetHashCode();

            if (!oldPropHashed.Equals(newPropHashed))
                diff.Add(prop.Name);
        }

        return diff;
    }