arame3333
arame3333

Reputation: 10193

Comparing the properties of 2 objects with the same interface

I have a method where I compare the properties of 2 classes that share the same interface. There are a number of properties involved and rather than check each one, I would rather iterate through them instead, saving repetitive coding. I thought it might be possible to do this with reflection, but I can't work out how. This code failed as I did not understand what I was doing.

    public void IsEqual(IPropertyComparer src, IPropertyComparer property)
    {
        PropertyInfo[] srcPI = src.GetType().GetPublicProperties();
        PropertyInfo[] propertyPI = property.GetType().GetPublicProperties();
        var numberOfProperties = srcPI.Count();
        for (var i = 0; i < numberOfProperties; i++)
        {
            var srcValue = srcPI[i].GetValue(src, null);
            var propertyValue = propertyPI[i].GetValue(property, null);
            var propertyName = srcPI[i].Name;
            if(srcValue.Equals(propertyValue) == false)
            {
                this.Action = "ValidateExistingPropertyFailOn" + propertyName;
                this.Data.Error = true;
                this.Data.ErrorMessage =
                    string.Format(this.Template, this.UPRN, propertyName, srcValue, propertyValue);
                return;
            }
        }

There is an extension method in the code. The extension method fails as the Type is not recognised as an interface;

public static PropertyInfo[] GetPublicProperties(this Type type)
{
    if (type.IsInterface)
    {
        var propertyInfos = new List<PropertyInfo>();

        var considered = new List<Type>();
        var queue = new Queue<Type>();
        considered.Add(type);
        queue.Enqueue(type);
        while (queue.Count > 0)
        {
            var subType = queue.Dequeue();
            foreach (var subInterface in subType.GetInterfaces())
            {
                if (considered.Contains(subInterface))
                {
                    continue;
                }
                considered.Add(subInterface);
                queue.Enqueue(subInterface);
            }

            var typeProperties = subType.GetProperties(
                BindingFlags.FlattenHierarchy
                | BindingFlags.Public
                | BindingFlags.Instance);

            var newPropertyInfos = typeProperties
                .Where(x => !propertyInfos.Contains(x));

            propertyInfos.InsertRange(0, newPropertyInfos);
        }

        return propertyInfos.ToArray();
    }

    return type.GetProperties(BindingFlags.FlattenHierarchy
        | BindingFlags.Public | BindingFlags.Instance);
}

Upvotes: 0

Views: 1600

Answers (3)

Ian Robertson
Ian Robertson

Reputation: 2814

If your needs for comparing two instances of objects grow more complicated, you should check out this package on NuGet:

https://www.nuget.org/packages/CompareNETObjects/

And codeplex page:

http://comparenetobjects.codeplex.com/

Its very good at giving you a "difference", which can some times be what you are really looking for...

UPDATE - A Generic, reusable way to check for Equality, based on fields within an object.

I stumbled upon the ValueObject<T> class written by Jimmy Bogard which might interest some folk. Its original purpose is well suited to DDD where objects of equal properties are considered equal, regardless of their reference type based hash code.

http://grabbagoft.blogspot.com/2007/06/generic-value-object-equality.html

Here is a slightly updated version using Linq to improve readability and a better way to process precondition checks - eg x.ForNull(nameof(firstInCompare));

public abstract class ValueObject<T> : IEquatable<T> where T : ValueObject<T>
{
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        var other = obj as T;

        return Equals(other);
    }

    public override int GetHashCode()
    {
        var fields = GetFields();

        const int startValue = 17;
        const int multiplier = 59;

        return fields
            .Select(field => field.GetValue(this))
            .Where(value => value != null)
            .Aggregate(startValue, (current, value) => current * multiplier + value.GetHashCode());
    }

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

        var t = GetType();
        var otherType = other.GetType();

        if (t != otherType)
            return false;

        var fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

        foreach (var field in fields)
        {
            var value1 = field.GetValue(other);
            var value2 = field.GetValue(this);

            if (value1 == null)
            {
                if (value2 != null)
                    return false;
            }
            else if (!value1.Equals(value2))
                return false;
        }

        return true;
    }

    private IEnumerable<FieldInfo> GetFields()
    {
        var t = GetType();

        t.ForNull("this");

        var fields = new List<FieldInfo>();

        while (t != typeof(object))
        {
            if (t == null) continue;
            fields.AddRange(t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));

            t = t.BaseType;
        }

        return fields;
    }

    public static bool operator ==(ValueObject<T> firstInCompare, ValueObject<T> secondInCompare)
    {
        firstInCompare.ForNull(nameof(firstInCompare));
        secondInCompare.ForNull(nameof(secondInCompare));

        return firstInCompare?.Equals(secondInCompare) ?? false;
    }

    public static bool operator !=(ValueObject<T> firstInCompare, ValueObject<T> secondInCompare)
    {
        return !(firstInCompare == secondInCompare);
    }
}

The precondition guards can be found in a static helper class like this

public static class Guard
{
    public static void ForLessEqualZero(this int value, string parameterName)
    {
        if (value <= 0)
        {
            throw new ArgumentOutOfRangeException(parameterName);
        }
    }

    public static void ForPrecedesDate(this DateTime value, DateTime dateToPrecede, string parameterName)
    {
        if (value >= dateToPrecede)
        {
            throw new ArgumentOutOfRangeException(parameterName);
        }
    }

    public static void ForNullOrEmpty(this string value, string parameterName)
    {
        if (string.IsNullOrEmpty(value))
        {
            throw new ArgumentOutOfRangeException(parameterName);
        }
    }

    public static void ForNull<T>(this T value, string parameterName)
    {
        ForValueType<T>(parameterName);

        if (value == null)
        {
            throw new ArgumentNullException(parameterName);
        }
    }

    private static void ForValueType<T>(string parameterName)
    {
        if (typeof(T).IsValueType)
        {
            throw new ArgumentException("parameter should be reference type, not value type", parameterName);
        }
    }
}

To check for equality in a generic, reusable way you could wrap your objects in the ValueObject<T>.

Or you could steal the implementation and do it in a more specific way.

Upvotes: 1

Jonny Piazzi
Jonny Piazzi

Reputation: 3784

If I understand correctly, you're trying compare two objects that implement an interface in common, and to this comparison only the properties defined in the interface will be use to compare.

If this is the case, try this:

EDITED

To prevent null exception I edited the code:

bool IsEqual(IPropertyComparer o1, IPropertyComparer o2)
{
    var props = typeof(IPropertyComparer).GetProperties();

    foreach(var prop in props)
    {
        var v1 = prop.GetValue(o1);
        var v2 = prop.GetValue(o2);

        if(v1 == null)
        {
            if(v2 != null) return false;
        }
        else
        {
            if(!v1.Equals(v2)) return false;
        }
    }

    return true;
}

Upvotes: 2

Emmanuel M.
Emmanuel M.

Reputation: 441

I've been looking for something like this some time ago, and finally managed to write this function. It should work for your interface too.

bool IsEqual(object obj)
    {
        var type = this.GetType();
        bool SameObj = true;
        //for each public property from your class
        type.GetProperties().ToList().ForEach(prop =>
        {
            //dynamically checks there equals
            if (!prop.GetValue(this, null).Equals(prop.GetValue(obj, null)))
            {
                SameObj = false;
            }
        });
        return SameObj;
    }

Upvotes: 0

Related Questions