GetHashCode возвращает одно и то же значение для разных объектов. Есть ли способ идентифицировать объект по определенным свойствам? - PullRequest
0 голосов
/ 05 января 2019

Я пытаюсь создать метод хэш-кода. У меня есть код, как показано ниже:

    private static object GetValue<T>(object item, string propertyName)
    {
        ParameterExpression arg = Expression.Parameter(item.GetType(), "x");
        Expression expr = Expression.Property(arg, propertyName);
        UnaryExpression unaryExpression = Expression.Convert(expr, typeof(object));
        var propertyResolver = Expression.Lambda<Func<T, object>>(unaryExpression, arg).Compile();
        return propertyResolver((T)item);
    }


    private static int GetHashCode<T>(T obj, List<string> columns)
    {
        unchecked
        {
            int hashCode = 17;

            for (var i = 0; i < columns.Count; i++)
            {
                object value = GetValue<T>(obj, columns[i]);
                var tempHashCode = value == null ? 0 : value.GetHashCode();
                hashCode = (hashCode * 23) + tempHashCode;
            }

            return hashCode;
        }
    }

    private static void TestHashCode()
    {
        var t1 = new { ID = (long)2044716, Type = "AE", Method = (short)1022, Index = 3 };
        var t2 = new { ID = (long)12114825, Type = "MEDAPE", Method = (short)1700, Index = 2 };

        var e1 = t1.GetHashCode();
        var e2 = t2.GetHashCode();

        var columns = new[] { "ID", "Type", "Method", "Index" }.ToList();
        var k1 = GetHashCode(t1, columns);
        var k2 = GetHashCode(t2, columns);
    }

Значение e1 равно -410666035, значение e2 равно 101205027. Значение k1 составляет 491329214. Значение k2 составляет 491329214.

Шаги HashCode:

hashCode = 17
tempHashCode = 2044716
хэш-код = 2045107
tempHashCode = 1591023428
хэш-код = 1638060889
tempHashCode = 66978814
хэш-код = -912326403
tempHashCode = 3
хэш-код = 491329214

Как k1 и k2 могут быть одним и тем же значением? Потому что метод .net gethashcode по умолчанию дает два разных значения. Я хочу создать метод хеш-кода, который может получить список столбцов. Я хочу создать хэш-код по определенным свойствам. Я пытаюсь получить уникальное значение для объекта по определенным свойствам.

Как я могу идентифицировать объект по определенным свойствам, если GetHashCode не гарантирует уникальное значение?

Ответы [ 3 ]

0 голосов
/ 05 января 2019

GetHashCode возвращает значение, зависящее от реализации. Его особый дизайн подходит для «стандартного» использования и имеет смысл только в течение срока службы приложения. Алгоритм по умолчанию не предназначен для предотвращения столкновений.

Метод GetHashCode не предназначен для того, чтобы быть уникальным для каждого экземпляра.

Ваш подход основан на составлении хеша каждого столбца. Хеш-код должен удовлетворять определенным требованиям, например, распространение в домене. Тем не менее, не гарантируется, что композиция сохраняет такие свойства и требования: чем больше столбцов вы добавляете, тем «страннее» могут быть коллизии.

Кроме того, вы вызываете value.GetHashCode(), который препятствует операции бокса. Как предлагает johey, вам следует использовать метод RuntimeHelpers.GetHashCode(), поскольку он интерпретирует объект как значение перед вычислением хэша.

Структуры данных .NET предназначены для внутренней обработки коллизий, например, IDictionary использует хеш для выбора сегмента, а затем последовательно сканирует сегмент.

0 голосов
/ 09 января 2019

Я хочу написать здесь свое решение. Все сказанное верно, но не совсем. Я хочу собрать тему здесь.

GetHashCode всегда дает одинаковые значения для одинаковых объектов. Значения GetHashCode всегда могут не принадлежать разным объектам.

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

Я создал IEqualityComparer.

private class CustomEqualityComparer<T> : IEqualityComparer<T>
    {

        private readonly List<string> _columns;
        private readonly bool _enableHashCode;
        private readonly ConcurrentDictionary<string, Func<T, object>> _cache;
        public CustomEqualityComparer(List<string> columns, ConcurrentDictionary<string, Func<T, object>> cache, bool enableHashCode = false)
        {
            _columns = columns;
            _enableHashCode = enableHashCode;
            _cache = cache;
        }

        public bool Equals(T x, T y)
        {
            for (var i = 0; i < _columns.Count; i++)
            {
                object value1 = GetValue(x, _columns[i], _cache);
                object value2 = GetValue(y, _columns[i], _cache);
                if (!value1.Equals(value2)) return false;
            }

            return true;
        }

        public int GetHashCode(T obj)
        {
            return _enableHashCode ? GetHashCode(obj, _columns, _cache) : 0;
        }

        private object GetValue(object item, string propertyName, ConcurrentDictionary<string, Func<T, object>> cache)
        {
            if (!cache.TryGetValue(propertyName, out Func<T, object> propertyResolver))
            {
                ParameterExpression arg = Expression.Parameter(item.GetType(), "x");
                Expression expr = Expression.Property(arg, propertyName);
                UnaryExpression unaryExpression = Expression.Convert(expr, typeof(object));
                propertyResolver = Expression.Lambda<Func<T, object>>(unaryExpression, arg).Compile();
                cache.TryAdd(propertyName, propertyResolver);
            }

            return propertyResolver((T)item);
        }

        private int GetHashCode(T obj, List<string> columns, ConcurrentDictionary<string, Func<T, object>> cache)
        {
            unchecked
            {
                var hashCode = 17;

                for (var i = 0; i < columns.Count; i++)
                {
                    object value = GetValue(obj, columns[i], cache);
                    var tempHashCode = value == null ? 0 : value.GetHashCode();
                    hashCode = hashCode * 23 + tempHashCode;
                }

                return hashCode;
            }
        }
    }
0 голосов
/ 05 января 2019

Я подозреваю, что проблема вызвана value.GetHashCode() в вашем GetHashCode<T> методе. Эта переменная-значение является объектом, я думаю, GetHashCode() там не возвращает то, что вы ожидаете. Попробуйте отладить, чтобы узнать, что происходит.

Возможно, вы захотите сохранить свой код, но вместо Object.GetHashCode() используйте RuntimeHelpers.GetHashCode() (из пространства имен System.Runtime.CompilerServices).

Полная ссылка здесь: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.runtimehelpers.gethashcode?redirectedfrom=MSDN&view=netframework-4.7.2#System_Runtime_CompilerServices_RuntimeHelpers_GetHashCode_System_Object_

Удачи!

...