Пользовательские EqualityComparer используя компаратор IEqualityComparer<> интерфейс


У меня есть обычай PropertiesByValueComparer , и я довольно доволен тем, как он ведет себя на простые классы. Я не включенными сравнение по полям еще. Есть ли что-нибудь, что явно не об этом, или у вас есть другие рекомендации?

public class PropertiesByValueComparer<T> : IEqualityComparer<T> where T : class
{
    private List<PropertyInfo> properties;
    private List<FieldInfo> fieldInfos;

    public PropertiesByValueComparer()
    {
        Type t = typeof(T);

        this.properties = t.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).ToList();
        this.fieldInfos = t.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).ToList();
    }

    public bool Equals(T x, T y)
    {
        var pool = new List<object>();

        return this.Equals(x, y, pool);
    }

    public bool Equals(T x, T y, List<object> pool)
    {
        if(pool.Contains(x) && pool.Contains(y))
        {
            return true;
        }

        if ((x == null && y == null) || ReferenceEquals(x, y))
        {
            pool.Add(x);
            pool.Add(y);

            return true;
        }

        if (x == null || y == null)
        {
            return false;
        }

        var xList = (x as IList);
        var yList = (y as IList);

        if(xList != null && yList != null)
        {
            var result = CompareCollectionIgnoreOrder(xList, yList, pool);
            if(result)
            {
                pool.Add(xList);
                pool.Add(yList);
                return true;
            }
        }

        var valueProperties = GetValueProperties();

        foreach (var property in valueProperties)
        {
            if (!Equals(property.GetValue(x, null), property.GetValue(y, null)))
            {
                return false;
            }
        }

        var classProperties = properties.Where(p => p.PropertyType.IsClass && p.PropertyType != typeof(String));

        foreach (var classProperty in classProperties)
        {
            Type valueComparerType = typeof(PropertiesByValueComparer<>);

            Type typeArg = classProperty.PropertyType;

            Type constructed = valueComparerType.MakeGenericType(typeArg);

            if (classProperty.PropertyType.Namespace != null && classProperty.PropertyType.Namespace.Equals("System.Collections.Generic"))
            {
                var collectionX = classProperty.GetValue(x, null);
                var collectionY = classProperty.GetValue(y, null);

                if (collectionX == null && collectionY == null)
                {
                    continue;
                }

                var arrayX = (collectionX as IList);
                var arrayY = (collectionY as IList);

                if(!CompareCollectionIgnoreOrder(arrayX, arrayY, pool))
                {
                    return false;
                }

                continue;
            }
            else
            {
                object[] args = {classProperty.GetValue(x, null), classProperty.GetValue(y, null), pool};

                object o = Activator.CreateInstance(constructed);

                if (!(bool)constructed.InvokeMember("Equals", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null,o, args))
                {
                    return false;
                }
            }
        }

        pool.Add(x);
        pool.Add(y);
        return true;
    }

    private bool CompareCollectionIgnoreOrder(IList arrayX, IList arrayY, List<object> pool )
    {
        if ((arrayX == null && arrayY != null) || (arrayY == null && arrayX != null))
        {
            return false;
        }

        if (arrayX == null && arrayY == null)
        {
            return true;
        }

        if (arrayX.Count == 0 && arrayY.Count == 0)
        {
            return true;
        }

        if (arrayX.Count != arrayY.Count)
        {
            return false;
        }

        foreach (var itemX in arrayX)
        {
            foreach (var itemY in arrayY)
            {
                Type valueComparerType = typeof (PropertiesByValueComparer<>);

                Type typeX = itemX.GetType();

                if(typeX.IsValueType || typeX == typeof(String))
                {
                    if(Equals(itemX, itemY))
                    {
                        arrayY.Remove(itemY);
                        break;
                    }

                    continue;
                }

                Type innerConstructed = valueComparerType.MakeGenericType(typeX);

                object iO = Activator.CreateInstance(innerConstructed);

                var iArgs = new object[] { itemX, itemY, pool };

                if ((bool)innerConstructed.InvokeMember("Equals", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, iO, iArgs))
                {
                    arrayY.Remove(itemY);
                    break;
                }
                return false;
            }
        }

        if (arrayY.Count > 0)
        {
            return false;
        }

        return true;
    }

    public int GetHashCode(T obj)
    {
        var valueProperties = GetValueProperties();

        unchecked
        {
            int result = 0;

            foreach (var property in valueProperties)
            {
                var value = property.GetValue(obj, null);
                result = (result * 397) ^ (value != null ? value.GetHashCode() : 0);
            }

            return result;
        }
    }

    private IEnumerable<PropertyInfo> GetValueProperties()
    {
        var valueProperties = properties.Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(String));

        return valueProperties;
    }
}

Я особенно чувствую, что метод GetHashCode() мог сделать с некоторым улучшением, потому что он не даст уникальные значения для объектов с различных ссылочных объектов вложенных дальше.



1449
8
c#
задан 23 июня 2011 в 02:06 Источник Поделиться
Комментарии
2 ответа

Некоторые замечания / предложения:


  • Принять свойства / fieldInfos поля статичны, они не меняются с каждой закрытой экземпляр типа PropertiesByValueComparer (т. е. для каждого Т перешла к нему), так что вам не нужно их инициализировать для каждого нового экземпляра компаратора

  • На равных(Т, Т, список), там нет необходимости, чтобы добавить X и y, если метод referenceequals возвращает значение true - они один и тот же объект, и ты только через бассейн, чтобы проверить для предварительного искали объекты
  • GetValueProperties реализуется как (однострочный) способ; чтобы получить "класс" свойства "" использовать лямбда-выражение рядный; код должны быть согласованы (либо сделать как где выражения встроенные, или как вспомогательные методы)

  • Призывы метод referenceequals, а равно должно предваряться объекта. и базы. соответственно, так что мы знаем, не глядя на остальных из класса, что эти методы из объекта, а не вспомогательный метод в классе

  • [несовершеннолетних] вместо использования classProperty.PropertyType.Пространства имен.Равна (Системы".Коллекции.Универсальный"), я удалить строку и использовать что-то вроде classProperty.PropertyType.Пространства имен.Равна(для вызова typeof>.Пространство имен)

  • Компаратор не обрабатывать словарьпоскольку ты только ищешь объекта IList; если вы начали искать интерфейс IEnumerable (и добавили специальный чехол для KeyValuePair) он будет обрабатывать словарях

  • Я думаю, что логика бассейн может быть нарушена; вы добавляете объекты, которые вы видите на бассейн, и если объекты находятся на бассейн, то они считаются одинаковыми. Это не удастся, если у вас есть два объекта типа A с трех свойств, как показано ниже:

  • Объекты:

    object 1 { prop1 = B, prop2 = C, prop3 = B }
    object 2 { prop1 = B, prop2 = C, prop3 = C }

    Компаратор проверяет, которых prop1 тот же (а добавить Б в бассейн), затем проверить, что prop2-же (и добавить С с бассейном), и при проверке prop3, хотя они разные, так как B и C в бассейн, компаратор будет считать их одинаковыми.

    6
    ответ дан 24 июня 2011 в 03:06 Источник Поделиться

    Одно маленькое дополнение к carlosfigueira'ы ответ. Я хотел бы также предложить извлечения ValueProperties над ClassProperties в конструкторе, нет смысла хранить свойства в этом случае на всех, и вы можете избежать выполнения отражения и LINQ снова и снова для каждого GetValueProperties звонок.

    Также непонятно, почему метод GetHashCode принимает только значение свойства во внимание. Хотя это, безусловно, работать, но выглядит немного странно. Может быть, вы должны добавить комментарий почему класс свойства игнорируются?

    3
    ответ дан 24 июня 2011 в 08:06 Источник Поделиться