HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Generic equality checker

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
genericequalitychecker

Problem

I use this method to check if two reference types are equal

public static bool AreEquals(T source, object obj) where T : class
  {
     if (ReferenceEquals(source, obj))
        return true;

     var convertedTarget = obj as T;

     if (ReferenceEquals(convertedTarget, null))
        return false;

     List equalityMembers = typeof (T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(p => p.GetCustomAttribute() != null).ToList();
     equalityMembers.AddRange(typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(p => p.GetCustomAttribute() != null).ToList());

     if (equalityMembers.Count == 0)
        return false; //The references were not equals and there is nothing to compare..

     var enumerator = equalityMembers.GetEnumerator();
     bool areEquals = true;

     while (enumerator.MoveNext() && areEquals)
     {
        var current = enumerator.Current;

        //On sait que FieldInfo et PropertyInfo ont la méthode GetValue
        var methodInfo = current.GetType().GetMethod("GetValue", new [] {typeof (object)});

        object valueSource = methodInfo.Invoke(current, new object[] {source});
        object valueObj = methodInfo.Invoke(current, new object[] {convertedTarget});

        areEquals = valueSource.Equals(valueObj);
     }
     return areEquals;
  }


I use attributes on properties/fields to find which of them are used to decide if the two objects are equals ex :

public class MyClass
{
    [EqualityMember]
    public int ID{get;set;}

    public override Equals(object obj)
    {
        return MyStaticClass.AreEquals(this,obj);
    }
}


It works very well but I am bothered with how I get the properties and fields in a list and use reflection to invoke the GetValue method, would you have any other alternatives? Or is there anything that seems flawed in my method?

Solution

I took a shot at this as well. I think @Hangy had a few good ideas but ended up with a rather complex solution. I haven't added any caching so you might want to add that yourself afterwards.

What I changed:

  • Different check for null which is clearer in my opinion.



  • Braces for one-line statements.



  • Extracted member parsing to a separate method.



  • Instead of holding all data of PropertyInfo and FieldInfo, I simply use a Dictionary which translates to `.



  • Unused properties and fields data is eligible for GC as soon as you extracted the needed information.



  • Got rid of the Enumerator` and added some juicy LINQ.



The end result:

public static class EqualityAttribute
{
    public static bool AreEquals(T source, object obj) where T : class
    {
        if (ReferenceEquals(source, obj))
        {
            return true;
        }

        var convertedTarget = obj as T;
        if (convertedTarget == null)
        {
            return false;
        }

        var obj1Members = GetFieldEqualityMembers(source);
        var obj2Members = GetFieldEqualityMembers(obj);

        var noMembersToCompare = obj1Members.Values.Count == 0 && obj2Members.Values.Count == 0;
        if (noMembersToCompare)
        {
            return false;
        }

        if (obj1Members.Keys.Any(key => !obj1Members[key].Equals(obj2Members[key])))
        {
            return false;
        }

        return true;
    }

    private static Dictionary GetFieldEqualityMembers(T obj)
    {
        var properties = obj.GetType().GetProperties(BindingFlags.Public |
                                                     BindingFlags.NonPublic |
                                                     BindingFlags.Instance)
            .Where(x => x.GetCustomAttribute() != null)
            .ToDictionary(x => x.Name, x => x.GetValue(obj));

        var fields = obj.GetType().GetFields(BindingFlags.Public |
                                             BindingFlags.NonPublic |
                                             BindingFlags.Instance)
            .Where(x => x.GetCustomAttribute() != null)
            .ToDictionary(x => x.Name, x => x.GetValue(obj));

        properties.ToList().ForEach(x => fields.Add(x.Key, x.Value));
        return fields;
    }
}

Code Snippets

public static class EqualityAttribute
{
    public static bool AreEquals<T>(T source, object obj) where T : class
    {
        if (ReferenceEquals(source, obj))
        {
            return true;
        }

        var convertedTarget = obj as T;
        if (convertedTarget == null)
        {
            return false;
        }

        var obj1Members = GetFieldEqualityMembers(source);
        var obj2Members = GetFieldEqualityMembers(obj);

        var noMembersToCompare = obj1Members.Values.Count == 0 && obj2Members.Values.Count == 0;
        if (noMembersToCompare)
        {
            return false;
        }

        if (obj1Members.Keys.Any(key => !obj1Members[key].Equals(obj2Members[key])))
        {
            return false;
        }

        return true;
    }

    private static Dictionary<string, object> GetFieldEqualityMembers<T>(T obj)
    {
        var properties = obj.GetType().GetProperties(BindingFlags.Public |
                                                     BindingFlags.NonPublic |
                                                     BindingFlags.Instance)
            .Where(x => x.GetCustomAttribute<EqualityMemberAttribute>() != null)
            .ToDictionary(x => x.Name, x => x.GetValue(obj));

        var fields = obj.GetType().GetFields(BindingFlags.Public |
                                             BindingFlags.NonPublic |
                                             BindingFlags.Instance)
            .Where(x => x.GetCustomAttribute<EqualityMemberAttribute>() != null)
            .ToDictionary(x => x.Name, x => x.GetValue(obj));

        properties.ToList().ForEach(x => fields.Add(x.Key, x.Value));
        return fields;
    }
}

Context

StackExchange Code Review Q#60054, answer score: 5

Revisions (0)

No revisions yet.