在可变类型中实现IEquatable

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

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

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

我想通过重写Equals并实现IEquatable来模仿代码中的行为(使用ContainsDistinct等方法):

 public class Device : IEquatable { 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); } } 

如您所见,我忽略了Equals实现中的Tag属性。

所以,我的问题是:我应该忽略Equals实现中的Tag属性吗? 这样做会让代码更难理解吗? 有没有更好的方法来做我想做的事情? 我需要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 { 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(); } } } 

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

所以,我没有像你那样实现IEquatable ,而是定义了IEqualityComparer ,也许

 class DeviceAddressEqualityComparer : IEqualityComparer { 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实例传递给ContainsDistinct和依赖于相等的其他LINQ方法(例如, GroupBy )。

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

只有当两个对象对于所有目的都是等效的时,IMO才应该重写Equals 。 对于某些目的,具有不同Tag的对象看起来不同。

我根本不会在这些对象上重写Equals 。 我宁愿实现自定义比较器IEqualityComparer并在适当的地方使用它。 大多数具有相等概念的方法都将IEqualityComparer作为可选参数。

我不会禁止null参数,但处理它们。 我还为参考平等增加了一个早期。

 public class DeviceByAddressEqualityComparer : IEqualityComparer { 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() ,但由于你在这里使用了一个特殊用途的比较器,故意忽略了对象的一部分,我可能不会使类型部分身份。

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

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

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

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

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

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

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

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

你需要在Tag方面实现平等吗? 这听起来不像你,所以我认为你的方法没有任何问题。

如果用户不需要知道Address ,那么您可能还会争辩说他们不需要知道基于地址的基本相等性……是吗? 如果他们不这样做,那么我会说你的方法没有错。

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

我最终为IDevice创建了一个新接口来实现。 新界面还允许我根据设备轻松创建相等比较器。

 public interface IPhysicallyEquatable { bool PhysicallyEquals(T other); int GetPhysicalHashCode(); } public class PhysicalEqualityComparer : IEqualityComparer where T : IPhysicallyEquatable { 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 { // ... }