3

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

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

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

我想通过覆盖和实现来模仿代码中的这种行为(用于使用Contains和之类的方法) :DistinctEqualsIEquatable<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);
    }
}

如您所见,我忽略TagEquals.

所以,我的问题是:我应该在实现中忽略该Tag属性Equals吗?这样做是否会使代码更难理解?有没有更好的方法来做我想做的事情?我需要该Tag属性,因为通常用户不知道Address,甚至不知道是否DeviceAddress( 在 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();
        }
    }
}
4

5 回答 5

4

首先你忘了覆盖GetHashCode(),所以你的代码被破坏了。

EqualsIMO只有当两个对象对于所有目的都等效时,您才应该覆盖。对于某些目的,具有不同Tags 的对象似乎不同。

我根本不会覆盖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我通常使用 if 进行检查x.GetType()==y.GetType(),但由于您在这里使用了一个特殊用途的比较器,它故意忽略了对象的一部分,所以我可能不会将类型作为标识的一部分。

于 2012-02-13T17:51:36.720 回答
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>您可以传递to的实例ContainsDistinct以及其他依赖相等性的 LINQ 方法(例如GroupBy)。

于 2012-02-13T17:50:21.400 回答
2

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

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

这样做是否会使代码更难理解?

绝对:一个新的开发人员不会理解为什么将两个具有不同标签的设备放在一个哈希集中成为一个设备。

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

我至少可以想到两种方法:

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

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

于 2012-02-13T17:51:38.170 回答
1

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

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

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

于 2012-02-13T17:50:59.403 回答
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>
{
    // ...
}
于 2012-02-13T19:43:27.830 回答