1

Building upon this answer for comparing objects in C# Comparing object properties in c#

Knowing this is a complex topic, I wanted to handle a few more specific structures.

While this code will match properties if they are simple value types such as this object:

public class BasicStuff
{
    public int anInt { get; set; }
    public bool aBool { get; set; }
}

But as soon as it gets any more complex, this code fails.

So what I would like to do is make it a bit more usable for nested objects of the above, such as:

public class NestedStuff
{
    public BasicStuff theBasic { get; set; }
}

Any array of the above, such as:

public class ArrayStuff
{
    public BasicStuff[] theBasicArray { get; set; }
}

And finally any combination of the above:

public class AllTheStuff
{
    public int anInt { get; set; }
    public bool aBool { get; set; }
    public BasicStuff theBasic { get; set; }
    public BasicStuff[] theBasicArray { get; set; }
}

So what I came up with was the following:

    public static bool AllPublicPropertiesEqual<T>(T AObj, T BObj, params string[] ignore) where T : class
    {
        if (AObj != null && BObj != null)
        {
            Type type = typeof(T);
            List<string> ignoreList = new List<string>(ignore);
            foreach (PropertyInfo pInfo in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (!ignoreList.Contains(pInfo.Name))
                {
                    if (pInfo.PropertyType.IsArray)
                    {
                        object AValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
                        object BValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
                        string t = AValue.GetType().ToString();

                        if (!AllPublicPropertiesEqual<object>(AValue, BValue))
                            return false;
                    }
                    else if (!pInfo.PropertyType.IsValueType && !pInfo.PropertyType.IsPrimitive && !pInfo.PropertyType.IsEnum && pInfo.PropertyType != typeof(string)) 
                    //else if (Convert.GetTypeCode(pInfo.PropertyType) == TypeCode.Object)
                    {
                        object AValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
                        object BValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);

                        if (!AllPublicPropertiesEqual<object>(BValue, AValue))
                            return false;
                    }
                    else
                    {
                        object selfValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
                        object toValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);

                        if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
                            return false;
                    }
                }
            }
            return true;
        }
        return AObj == BObj;
    }

Only this fails because when recursively calling AllPublicPropertiesEqual, I need to pass the specific values type rather than just a generic object type. But I dont know how to do this.... Any ideas are greatly appreciated...

Community
  • 1
  • 1
axa
  • 428
  • 6
  • 19

3 Answers3

0

Your method does not have to be generic. You can change the beginning of the method to:

public static bool AllPublicPropertiesEqual(object AObj, object BObj, params string[] ignore) 
{
    if (AObj != null && BObj != null)
    {
        Type type = AObj.GetType();

        if (BObj.GetType() != type)
            throw new Exception("Objects should be of the same type");

        ....
    }

    ....
}
Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • Would this not merely check if the 2 objects are of the same type? I was looking if all the property values are the same. – axa Sep 05 '15 at 00:30
  • The idea of my answer is that instead of using T, use object. You can delete the if statement that checks if the objects of the same type it if you want. Please note that in my answer I am not suggesting a full replacement of the method, just a replacement of the first few lines of code. There is no need for generics. You just used generics to make sure that the two objects are of the same type, nothing more. I just replaced this with an if statement. – Yacoub Massad Sep 05 '15 at 00:37
  • Yes, using this idea, decoupling from any specific type, allowed me to come up with an answer and use recursive calls using only object. Novice programmers like me really appreciate the help. Ive been at this problem for days... – axa Sep 06 '15 at 04:55
  • That said, i still did use Generics as it helped to serve my purpose of a strong typed object comparison as in my updated solution. – axa Sep 06 '15 at 05:14
0

Well first thing what is that you should create Interface for those classes because from what I see you can combine those classes but all of them will have the same properties. Second option is create some Base abstract class with those properties and inherit from it. It is up to you what you choose. Than create in this Base class or Interface Equals functions where you will equal directly the shared properties it is much easier and I think more efficient :-)

public abstract class BaseStaff {
  public int anInt { get; set; }
  public bool aBool { get; set; }
  public abstract bool Equals(BaseStaff anotherStaff);
}

Then you can use this class and create your BasicStaff class and AllTheStaff class too in BasicStaff implement Equals method like this.

public override bool Equals(BaseStaff staff) {
   this.anInt == staff.anInt &&
   this.aBool == staff.aBool;
}

In AllTheStaff u can than override this method like this

public override bool Equals(BaseStaff staff) {
  bool baseEquals = base.Equals(staff);
  bool basicStaffEquals = this.BasicStaff.Equals(staff);
  return baseEquals || basicStaffEquals;    
}

This is just core idea and maybe I dont understand you well what you really want to achieve but hope it helps you :)

Peter
  • 1
  • 1
  • One more thing is that I think you can use common C# .Equals method only thing is that you must guarantee that your arrays have some order property because common Equals method can not handle equality of two arrays if their records have different indexes. So if you order array by some key I think you can use Common .Equals but maybe I am mistaken there. Can somebody confirm or reject this. – Peter Sep 05 '15 at 02:03
  • I thought that common Equals method will evaluate equal only if objects have same reference? This is a confusing issue for me... thanks to help. – axa Sep 07 '15 at 04:43
  • yeah you are right sorry for confusing. I wrote it in the middle of the night so I did not realize that the reference must be equal sorry for confusing :-) but still previous is still valid :-) – Peter Sep 08 '15 at 15:41
0

tested Solution

        public static bool AllPublicPropertiesEqual<T>(T AObj, T BObj, params string[] ignore) where T : class
    {
        if (AObj != null && BObj != null)
        {
            Type type = typeof(T);
            List<string> ignoreList = new List<string>(ignore);
            foreach (PropertyInfo pInfo in type.GetCType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (!ignoreList.Contains(pInfo.Name))
                {
                    if (pInfo.PropertyType.IsArray)
                    {
                        object aElementValues = (type.GetProperty(pInfo.Name).GetValue(AObj, null));
                        object bElementValues = (type.GetProperty(pInfo.Name).GetValue(BObj, null));

                        if (aElementValues != null && bElementValues != null)
                        {
                            List<object> AListValues = new List<object>();
                            List<object> BListValues = new List<object>();

                            foreach (var v in (IEnumerable)aElementValues)
                                AListValues.Add(v);

                            foreach (var v in (IEnumerable)bElementValues)
                                BListValues.Add(v);

                            if (AListValues.Count == BListValues.Count)
                            {
                                object[] aArray = AListValues.ToArray();
                                object[] bArray = BListValues.ToArray();

                                for (int i = 0; i < aArray.Length; i++)
                                {
                                    if (!AllPublicPropertiesEqual(aArray[i], bArray[i]))
                                        return false;
                                }
                            }
                            else
                                return false;
                        }
                        else if ((aElementValues == null) != (bElementValues == null))
                            return false;
                    }
                    else if (!pInfo.PropertyType.IsValueType && !pInfo.PropertyType.IsPrimitive && !pInfo.PropertyType.IsEnum && pInfo.PropertyType != typeof(string))
                    //else if (Convert.GetTypeCode(pInfo.PropertyType) == TypeCode.Object)
                    {
                        object AObjectValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
                        object BObjectValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);

                        if (!AllPublicPropertiesEqual(BObjectValue, AObjectValue))
                            return false;
                    }
                    else
                    {
                        object aValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
                        object bValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);

                        if (aValue != bValue && (aValue == null || !aValue.Equals(bValue)))
                            return false;
                    }
                }
            }
            return true;
        }
        return AObj == BObj;
    }
}
axa
  • 428
  • 6
  • 19