简体   繁体   English

实现IEquatable <T> 在一个可变的类型

[英]Implementing IEquatable<T> in a mutable type

I have a class that represents an external physical measuring device. 我有一个代表外部物理测量设备的课程。 The simplified version looks like this: 简化版本如下所示:

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

Tag is a user-defined value for identifying the device. Tag是用于标识设备的用户定义值。 Address is the value used by an adapter to communicate with the device. Address是适配器用于与设备通信的值。 If two instances of Device have the same Address , then the same external measuring device will be used. 如果两个Device实例具有相同的Address ,则将使用相同的外部测量设备。

I'd like to mimic that behavior in code (for using methods like Contains and Distinct ) by overriding Equals and implementing IEquatable<T> : 我想通过重写Equals并实现IEquatable<T>来模仿代码中的行为(使用ContainsDistinct等方法):

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);
    }
}

As you can see, I'm ignoring the Tag property in the implementation of Equals . 如您所见,我忽略了Equals实现中的Tag属性。

So, my question is: Should I ignore the Tag property in the implementation of Equals ? 所以,我的问题是:我应该忽略Equals实现中的Tag属性吗? Does doing so make the code harder to understand? 这样做会让代码更难理解吗? Is there a better way of doing what I'm trying to do? 有没有更好的方法来做我想做的事情? I need the Tag property because, often, the user will not know the Address , or even whether or not the Device has an Address (that is taken care of in the App.config file, and the user will be dealing with an interface called IDevice which doesn't have an Address property). 我需要Tag属性,因为用户通常不知道Address ,甚至Device是否有Address (在App.config文件中处理,用户将处理一个名为的接口) IDevice没有Address属性)。

Update: 更新:

Thanks everyone for the responses. 谢谢大家的回复。

So, I gather that I should be using a custom IEqualityComparer . 所以,我认为我应该使用自定义IEqualityComparer Do you have any guidance on how to do so if my real code looks more like this? 如果我的真实代码看起来更像这样,您对如何操作有任何指导吗?

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...
    }
}

Should I check the device type in my IEqualityComparer ? 我应该检查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();
        }
    }
}

First of all you forgot to override GetHashCode() , so your code is broken. 首先,您忘记覆盖GetHashCode() ,因此您的代码已损坏。

IMO you should override Equals only if two objects are equivalent for all purposes. 只有当两个对象对于所有目的都是等效的时,IMO才应该重写Equals And objects with different Tag s seem different for some purposes. 对于某些目的,具有不同Tag的对象看起来不同。

I wouldn't override Equals on these objects at all. 我根本不会在这些对象上重写Equals I'd rather implement a custom comparer IEqualityComparer<T> and use that where appropriate. 我宁愿实现自定义比较器IEqualityComparer<T>并在适当的地方使用它。 Most methods that have a notion of equality, take an IEqualityComparer<T> as optional parameter. 大多数具有相等概念的方法都将IEqualityComparer<T>作为可选参数。

I wouldn't forbid null parameters, but handle them. 我不会禁止null参数,但处理它们。 I've also added an early-out for referential equality. 我还为参考平等增加了一个早期。

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();
    }
}

If you want to check the type, depends on context. 如果要检查类型,则取决于上下文。 When overriding Equals I typically check with if x.GetType()==y.GetType() , but since you're using a special purpose comparer here, which deliberately ignores part of the object, I probably wouldn't make the type part of the identity. 当重写Equals我通常会检查x.GetType()==y.GetType() ,但由于你在这里使用了一个特殊用途的比较器,故意忽略了部分对象,我可能不会将类型部分身份。

Yes, your current implementation is definitely confusing. 是的,您目前的实施肯定令人困惑。 The equality you've defined is clearly not the right notion of equality for devices. 你所定义的平等显然不是设备平等的正确概念。

So, rather than implementing IEquatable<Device> as you've done, I'd define an implementation of IEqualityComparer<Device> , maybe 所以,我没有像你那样实现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();
    }
}

You can pass instances of IEqualityComparer<T> to Contains , Distinct and other LINQ methods that depend on equality (eg, GroupBy ). 您可以将IEqualityComparer<T>实例传递给ContainsDistinct和其他依赖于相等的LINQ方法(例如, GroupBy )。

Should I ignore the Tag property in the implementation of Equals? 我应该忽略Equals实现中的Tag属性吗?

No, I think this is a bad idea. 不,我认为这是一个坏主意。

Does doing so make the code harder to understand? 这样做会让代码更难理解吗?

Absolutely: a new developer would not understand how come two devices with different tags put in a hash set become one device. 绝对:一个新的开发人员不明白为什么两个带有不同标签的设备放在一个哈希集中会变成一个设备。

Is there a better way of doing what I'm trying to do? 有没有更好的方法来做我想做的事情?

There are at least two ways that I can think: 我至少有两种方式可以思考:

  • Provide a custom comparator 提供自定义比较器
  • Add a class called DeviceWithTag , and keep the Device "tagless". 添加一个名为DeviceWithTag的类,并保持Device “无标记”。

I would prefer the second approach, because it does look like your Tag models a real-world "tag" glued onto the device locator , which ignores its tag other than for display purposes. 我更喜欢第二种方法,因为它看起来像你的Tag模型一样粘在设备定位器上的真实世界“标签”,除了用于显示目的之外,它忽略了它的标签。

Do you need to implement equality in terms of Tag at all? 你需要在Tag方面实现平等吗? It doesn't sound like you do, so I don't see anything wrong with your approach. 这听起来不像你,所以我认为你的方法没有任何问题。

If the user doesn't need to know about Address , then you might also argue that they don't need to know about the underlying equality based on address... do they? 如果用户不需要知道Address ,那么您可能还会争辩说他们不需要知道基于地址的基本相等性......是吗? If they don't, then again, I would say there is nothing wrong with your approach. 如果他们不这样做,那么我会说你的方法没有错。

If they do need to know about equality, then you may need to rethink your design and expose Address in some way. 如果他们确实需要了解相等性,那么您可能需要重新考虑您的设计并以某种方式公开Address

I ended up creating a new interface for IDevice to implement. 我最终为IDevice创建了一个新的接口来实现。 The new interface also lets me easily create equality comparers based on the device. 新界面还允许我根据设备轻松创建相等比较器。

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>
{
    // ...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM