Страницы

Поиск по вопросам

четверг, 5 декабря 2019 г.

Универсальный метод для проверки объектов на равенство C#

#c_sharp #net


Есть ли функция на подобие Equals, которая вместо ссылок сравнивает все значения
объекта, а также значения вложенных объектов? Я не нашел ничего нужного и попытался
реализовать эту функцию самостоятельно, получилось так:

public static class EqualsUtil
{
    public static bool Equals(object value, object value2)
    {
        if (value == value2)
            return true;

        if (value == null || value2 == null)
            return false;

        Type type = value.GetType();

        if (type == typeof(int))
        {
            return (int)value == (int)value2;
        }

        if (type == typeof(string))
        {
            return String.CompareOrdinal((string) value, (string) value2) == 0;
        }

        if (type == typeof(byte))
        {
            return (byte)value == (byte)value2;
        }

        if (type == typeof(byte?))
        {
            return (byte?)value == (byte?)value2;
        }

        if (type == typeof(sbyte))
        {
            return (sbyte)value == (sbyte)value2;
        }

        if (type == typeof(sbyte?))
        {
            return (sbyte?)value == (sbyte?)value2;
        }

        if (type == typeof(uint))
        {
            return (uint)value == (uint)value2;
        }

        if (type == typeof(uint?))
        {
            return (uint?)value == (uint?)value2;
        }

        if (type == typeof(short))
        {
            return (short)value == (short)value2;
        }

        if (type == typeof(short?))
        {
            return (short?)value == (short?)value2;
        }

        if (type == typeof(ushort))
        {
            return (ushort)value == (ushort)value2;
        }

        if (type == typeof(ushort?))
        {
            return (ushort?)value == (ushort?)value2;
        }

        if (type == typeof(long))
        {
            return (long)value == (long)value2;
        }

        if (type == typeof(long?))
        {
            return (long?)value == (long?)value2;
        }

        if (type == typeof(ulong))
        {
            return (ulong)value == (ulong)value2;
        }

        if (type == typeof(ulong?))
        {
            return (ulong?)value == (ulong?)value2;
        }

        if (type == typeof(float))
        {
            return (float)value == (float)value2;
        }

        if (type == typeof(float?))
        {
            return (float?)value == (float?)value2;
        }

        if (type == typeof(double))
        {
            return (double)value == (double)value2;
        }

        if (type == typeof(double?))
        {
            return (double?)value == (double?)value2;
        }

        if (type == typeof(char))
        {
            return (char)value == (char)value2;
        }

        if (type == typeof(char?))
        {
            return (char?)value == (char?)value2;
        }

        if (type == typeof(bool))
        {
            return (bool)value == (bool)value2;
        }

        if (type == typeof(bool?))
        {
            return (bool?)value == (bool?)value2;
        }

        if (type == typeof(decimal))
        {
            return (decimal)value == (decimal)value2;
        }

        if (type == typeof(decimal?))
        {
            return (decimal?)value == (decimal?)value2;
        }

        if (type.IsClass)
        {
            var filds = type.GetFields();
            foreach (var field in filds)
            {
                var v1 = field.GetValue(value);
                var v2 = field.GetValue(value2);
                if (!EqualsUtil.Equals(v1, v2))
                {
                    return false;
                }
            }
            var properties = type.GetProperties();
            foreach (var property in properties)
            {
                var v1 = property.GetValue(value);
                var v2 = property.GetValue(value2);
                if (!EqualsUtil.Equals(v1, v2))
                {
                    return false;
                }
            }
            return true;
        }

        throw new Exception("ошибка");
    }


Есть ли готовые реализации нужной мне функции? Если нету, то как можно улучшить мою
функцию?
    


Ответы

Ответ 1



Сама по себе идея плоха. Вы не можете вслепую, основываясь на лишь значениях полей, установить равенство объектов. Семантика равенства намного сложнее. Для начала, объекты, в отличие от структур, обладают самостоятельным смыслом, который не есть просто суммарный смысл их полей. Чтобы прояснить эту загадочную фразу, вот вам пример. Если у нас есть два человека, которых зовут Игорь Сергеев, мы ведь не можем на основании лишь этих данных утверждать, что это один и тот же человек? Точно так же и объекты с одинаковыми полями не обязательно равны: два экземпляра класса Car, одного производителя и года выпуска, с одним типом мотора и одинаково оснащённые — это не один и тот же автомобиль. Затем, объекты могут оказаться равными даже если у них не одинаковые значения полей. Возьмём к примеру объект «рациональная дробь». Дроби 3/7 и 6/14 равны, хотя ни их числители, ни знаменатели не равны. Затем, объект имеет право держать свои данные в самых неожиданных местах. Например, в WPF объекты держат свои многочисленные DependencyProperty не внутри объекта, а во внешнем словаре. А какой же выход? Очень простой. Если объект хочет уметь сравниваться с другими объектами, он должен не лениться и реализовать интерфейс IEquatable. Только он сам знает, как сравнивать себя с остальными. Кстати, все числовые типы именно так и делают, так что делать огромный switch по их типам вовсе не обязательно. А вместо универсальной функции сравнения возьмите EqualityComparer.Default. Она делает именно то, что надо: запрашивает IEquatable, а если его нет, использует виртуальный Equals. В C# объекты, которые есть не что иное, как значение своих полей (то есть, равенство полей означает равенство объектов), обычно оформляются в виде структур, а не классов. Операция сравнения для структур делает в точности то, что вы пытаетесь реализовать: сравнивает все поля. При этом если все поля рекурсивно вниз являются полями-структурами, то операция сравнения просто сравнивает объекты побитно, что, конечно, гораздо эффективнее. Но если структура требует кастомной логики сравнения, она всё равно должна реализовать её самостоятельно, через IEquatable. Дополнительное чтение по теме: Чем отличаются оператор == и вызов метода object.Equals в C#?

Ответ 2



Ваша функция выглядит странно для строго типизированного языка. Может стоит подумать над изменением логики программы? По поводу вашей функции. Вместо всех этих проверок над стандартными структурами: if (type == typeof(ushort)) { return (ushort)value == (ushort)value2; } Вы можете просто написать вначале: if (value.Equals(value2)) return true; Потому что эти структуры переопределяют метод Equals. Например: object x = (short)-1; object y = (short)-1; Console.WriteLine(x.Equals(y)); Покажет true. Рекурсивная проверка полей/свойств классов упадет с исключением, если будут циклические ссылки. Например для такого класса: class Test { public Test Field; } var test = new Test(); test.Field = test;

Комментариев нет:

Отправить комментарий