在可变类型中实现IEquatable <t> </t>

时间:2012-02-13 17:45:19

标签: c# .net equals iequatable

我有一个代表外部物理测量设备的课程。简化版本如下所示:

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

Tag是用于识别设备的用户定义值。 Address是适配器用于与设备通信的值。如果Device的两个实例具有相同的Address,则将使用相同的外部测量设备。

我想通过覆盖Contains并实施Distinct来模仿代码中的行为(使用EqualsIEquatable<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 个答案:

答案 0 :(得分:4)

首先,您忘记覆盖GetHashCode(),因此您的代码已损坏。

IMO只有在两个对象用于所有目的时才应覆盖Equals。对于某些目的,具有不同Tag s的对象看起来不同。

我根本不会覆盖这些对象上的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(),但由于你在这里使用了一个特殊用途的比较器,故意忽略了对象的一部分,我可能不会将类型作为身份的一部分

答案 1 :(得分:2)

是的,您当前的实施绝对令人困惑。你所定义的平等显然不是设备平等的正确概念。

所以,我没有像你一样实施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>的实例传递给ContainsDistinct以及其他依赖于相等性的LINQ方法(例如GroupBy)。

答案 2 :(得分:2)

  

我应该忽略Equals实现中的Tag属性吗?

不,我认为这是一个坏主意。

  

这样做会让代码更难理解吗?

绝对:新开发人员不明白为什么两个带有不同标签的设备放在一个哈希集中会变成一个设备。

  

有没有更好的方法来做我想做的事情?

我至少有两种方式可以思考:

  • 提供自定义比较器
  • 添加一个名为DeviceWithTag的课程,并保持Device“无标记”。

我更喜欢第二种方法,因为它看起来像你的Tag模型一样粘在设备定位器上的真实世界“标签”,忽略了它的标签而非显示目的。

答案 3 :(得分:1)

您是否需要根据Tag实现平等?这听起来不像你,所以我认为你的方法没有任何问题。

如果用户不需要了解Address,那么您可能还会争辩说他们不需要知道基于地址的基本平等......是吗?如果他们不这样做,那么我会说你的做法没有任何问题。

如果他们确实需要了解平等,那么您可能需要重新考虑您的设计并以某种方式公开Address

答案 4 :(得分:0)

我最终为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>
{
    // ...
}