Реализация IEquatable <T>в изменяемом типе - PullRequest
3 голосов
/ 13 февраля 2012

У меня есть класс, который представляет собой внешнее физическое измерительное устройство. Упрощенная версия выглядит так:

public class Device {
    public string Tag { get; set; }
    public int Address { get; set; }
}

Tag - это определяемое пользователем значение для идентификации устройства. Address - это значение, используемое адаптером для связи с устройством. Если два экземпляра Device имеют одинаковый Address, то будет использоваться один и тот же внешний измерительный прибор.

Я хотел бы имитировать это поведение в коде (для использования таких методов, как Contains и Distinct) путем переопределения Equals и реализации IEquatable<T>:

public class Device : IEquatable<Device> {
    public string Tag { get; set; }
    public int Address { get; set; }

    public override bool Equals(object obj) {
        return Equals(obj as Device);
    }
    public bool Equals(Device other) {
        if (null == other) return false;
        if (ReferenceEquals(this, other)) return true;
        return Address.Equals(other.Address);
    }
}

Как видите, я игнорирую свойство Tag в реализации Equals.

Итак, мой вопрос: Должен ли я игнорировать свойство Tag в реализации Equals? Делает ли это таким образом код сложнее для понимания? Есть ли лучший способ сделать то, что я пытаюсь сделать? Мне нужно свойство Tag, потому что часто пользователь не будет знать Address, и даже не имеет ли Device значение Address (о котором говорится в файле App.config, и пользователь будет иметь дело с интерфейсом IDevice, который не имеет свойства Address).

Обновление:

Спасибо всем за ответы.

Итак, я понял, что должен использовать кастом IEqualityComparer. Есть ли у вас какие-либо рекомендации, как это сделать, если мой реальный код будет выглядеть примерно так?

public interface IDevice {
    string Tag { get; set; }
    double TakeMeasurement();
}
internal class Device : IDevice {
    public string Tag { get; set; }
    public int Address { get; set; }
    public double TakeMeasurement() {
        // Take a measurement at the device's address...
    }
}

Должен ли я проверить тип устройства в моем IEqualityComparer?

public class DeviceEqualityComparer : IEqualityComparer<IDevice> {
    public bool Equals(IDevice x, IDevice y) {
        Contract.Requires(x != null);
        Contract.Requires(y != null);
        if ((x is Device) && (y is Device)) {
            return x.Address.Equals(y.Address);
        }
        else {
            return x.Equals(y);
        }
    }

    public int GetHashCode(IDevice obj) {
        Contract.Requires(obj != null);
        if (obj is Device) {
            return obj.Address.GetHashCode();
        }
        else {
            return obj.GetHashCode();
        }
    }
}

Ответы [ 5 ]

4 голосов
/ 13 февраля 2012

Прежде всего вы забыли переопределить GetHashCode(), поэтому ваш код не работает.

ИМО, вы должны переопределить Equals только в том случае, если два объекта эквивалентны для всех целей.И объекты с разными Tag кажутся разными для некоторых целей.

Я бы вообще не переопределил Equals для этих объектов.Я бы предпочел реализовать пользовательский компаратор IEqualityComparer<T> и использовать его там, где это необходимо.Большинство методов, которые имеют понятие равенства, принимают IEqualityComparer<T> в качестве необязательного параметра.

Я бы не запретил null параметры, но обработал бы их.Я также добавил заблаговременно для ссылочного равенства.

public class DeviceByAddressEqualityComparer : IEqualityComparer<IDevice> {
    public bool Equals(IDevice x, IDevice y) {
        if(x==y)
          return true;
        if(x==null||y==null)
          return false;
        return x.Address.Equals(y.Address);
    }

    public int GetHashCode(IDevice obj) {
        if(obj == null)
          return 0;
        else
          return obj.Address.GetHashCode();
    }
}

Если вы хотите проверить тип, зависит от контекста.При переопределении Equals я обычно проверяю, если x.GetType()==y.GetType(), но так как вы используете здесь специальный компаратор, который намеренно игнорирует часть объекта, я, вероятно, не сделаю тип частью идентификатора.

2 голосов
/ 13 февраля 2012

Должен ли я игнорировать свойство Tag в реализации Equals?

Нет, я думаю, что это плохая идея.

Делает ли это трудным для понимания код?

Абсолютно: новый разработчик не поймет, почему два устройства с разными тегами, помещенными в хэш-набор, становятся одним устройством.

Есть ли лучший способ сделать то, что я пытаюсь сделать?

Есть как минимум два способа, которыми я могу думать:

  • Предоставить пользовательский компаратор
  • Добавьте класс с именем DeviceWithTag и оставьте Device "без тегов".

Я бы предпочел второй подход, потому что он выглядит так, будто ваши Tag моделируют реальный "тег", приклеенный к устройству locator , который игнорирует свой тег, кроме как для отображения.

2 голосов
/ 13 февраля 2012

Да, ваша текущая реализация определенно сбивает с толку.Определенное вами равенство явно не является правильным понятием равенства для устройств.

Итак, вместо реализации IEquatable<Device>, как вы сделали, я бы определил реализацию IEqualityComparer<Device>, возможно

class DeviceAddressEqualityComparer : IEqualityComparer<Device> {
    public bool Equals(Device x, Device y) {
        Contract.Requires(x != null);
        Contract.Requires(y != null);
        return x.Address.Equals(y.Address);
    }

    public int GetHashCode(Device obj) {
        Contract.Requires(obj != null);
        return obj.Address.GetHashCode();
    }
}

Вы можете передавать экземпляры IEqualityComparer<T> в Contains, Distinct и другие методы LINQ, которые зависят от равенства (например, GroupBy).

1 голос
/ 13 февраля 2012

Нужно ли вообще реализовывать равенство в терминах Tag?Это звучит не так, как вы, поэтому я не вижу ничего плохого в вашем подходе.

Если пользователю не нужно знать о Address, то вы также можете утверждать, что они нене нужно знать о базовом равенстве на основе адреса ... не так ли?Если они этого не делают, то, опять же, я бы сказал, что в вашем подходе нет ничего плохого.

Если им действительно нужно знать о равенстве, то вам, возможно, придется переосмыслить свой дизайн и разоблачить Address в некоторыхспособ.

0 голосов
/ 13 февраля 2012

В итоге я создал новый интерфейс для реализации IDevice.Новый интерфейс также позволяет легко создавать средства сравнения на основе устройства.

public interface IPhysicallyEquatable<T>
{
    bool PhysicallyEquals(T other);
    int GetPhysicalHashCode();
}

public class PhysicalEqualityComparer<T> : IEqualityComparer<T>
    where T : IPhysicallyEquatable<T>
{
    public bool Equals(T x, T y)
    {
        if (null == x) throw new ArgumentNullException("x");
        if (null == y) throw new ArgumentNullException("y");
        return x.PhysicallyEquals(y);
    }
    public int GetHashCode(T obj)
    {
        if (null == obj) throw new ArgumentNullException("obj");
        return obj.GetPhysicalHashCode();
    }
}

public interface IDevice : IPhysicallyEquatable<IDevice>
{
    // ...
}
...