正确实现两个类型不同但语义等价的对象的比较

本文关键字:语义 比较 对象 类型 实现 两个 | 更新日期: 2023-09-27 18:02:51

我发现了一个类似的问题

如何比较具有相似属性的两个截然不同的对象

可以含蓄地和/或部分地回答我的问题。

假设我想比较(没有很多嵌套条件)这个对象:

class ObjectA {
  public string PropertyX { get; set; }
  public char PropertyY { get; set; }
  public long PropertyZ { get; set; }
}

到a System.String。我只对等式不等式(不是关于恒等的值范围)感兴趣。

ObjectA中实施IEquatable<string>是正确的选择?我不关心什么简单地起作用,我想确定这种情况下的正确模式。

作为其他信息,请考虑ObjectA将经常作为IEnumerable<ObjectA>的序列提供。

我不需要知道"string" ==!= objectA instance;不涉及排序。

编辑以澄清(和帮助)

对不起,写一个好问题有时很难…

假设我不能将ObjectA表示为字符串用于比较(违反封装不是一个选项)。

  • 在context-1中,我必须将其与PropertyY相匹配。

  • 在context-2中,我必须将其与应用于PropertyY/PropertyZ的算法相匹配。

@Oliver问题最后的解决方案再次帮助了我(并且再次+1)。我可以简单地定义一个自定义接口:

interface IContextConverter {
  string ToEquatableStringForContext1();
  string ToEquatableStringForContext2();  
}

因为我也有一个具有相同逻辑但不同属性的ObjectB,两者都将实现IContextConverter (或者我可能找到一个更好的名字)避免违反RAP。

正确实现两个类型不同但语义等价的对象的比较

我强烈建议不要实现IEquatable<string>,因为特别是在使用集合、字典、LINQ等时,你真的不知道这些方法中的一个何时会在内部深处被调用,这可能会导致微妙的bug。

由于你喜欢比较两个不同类型的对象,一个简单的Comparer<T>也不能工作。

所以要么写一个TypeConverter,将你的对象转换成所需的类型(在你的情况下是string),要么为你的对象添加一个方法,如.ToEquatableString(),并使用它们的输出来比较你的对象与其他字符串。

这里有一个例子,你可以得到所有的元素,匹配字符串中的一个在另一个集合:

IEnumerable<String> otherElements = new[] {"abc", "def", "ghi" };
IEnumerable<ObjectA> myObjects = GetObjects();
var matchesFound = otherElements.Join( // Take the first collection.
              myObjects, // Take the second collection.
              s => s, // Use the elements in the first collection as key (the string).
              obj => obj.ToEquatableString(),  // Create a string from each object for comparison.
              (s, obj) => obj, // From the matching pairs take simply the objects found.
              StringComparer.OrdinalIgnoreCase); // Use a special string comparer if desired.

有很多可能性。

如果您认为ObjectASystem.String的一种,您可以编写自定义的转换(隐式或显式),从ObjectASystem.String,或从System.StringObjectA,或两个方向。

还可以用类似operator ==(ObjectA oa, string s)的签名重载==!=操作符。注意oa == ss == oa之间存在差异。

这两种可能性中的任何一种都可能导致混淆。覆盖虚拟Equals(object)或引入重载Equals(string)也会令人困惑。因此,我不建议实现IEquatable<string>

为什么不简单地写一个没有使用的名字的方法,比如public bool EqualsString(string s) ?当然,您必须显式地调用这个方法,但这样可以减少混淆。另一个想法是使用签名public ObjectA(string s)的构造函数,然后实现ObjectAObjectA的"同构"相等。

注意:对于这种特殊情况,我并不特别推荐这种解决方案,但是我经常使用这个框架来实现结构的值相等。在我们的领域中,困难的权衡是很常见的,解决这些问题的答案,加上适当的警告,似乎是有序的。

我认为你希望在你的类上设计值相等语义,就像。net框架对字符串类所做的那样。至少,以下内容是必要的:

public override bool Equals(object obj) { 
  return (obj is ObjectA) && this == (ObjectA)obj; 
}
bool IEquatable<ObjectA>.Equals(ObjectA rhs) { 
  return this == rhs; 
}
public static bool operator != (ObjectA lhs, ObjectA rhs) { 
  return ! (lhs == rhs); 
}
public static bool operator == (ObjectA lhs, ObjectA rhs) {
  return (lhs.PropertyX == rhs.PropertyX);
}
public override int GetHashCode() { 
  return PropertyX.GetHashCode() 
}

扩展以允许ObjectA和string之间的值比较:

bool IEquatable<ObjectA>.Equals(string rhs) { 
  return this == rhs; 
}
public static bool operator != (ObjectA lhs, string rhs) { 
  return ! (lhs == rhs); 
}
public static bool operator != (string lhs, ObjectA rhs) { 
  return ! (lhs == rhs); 
}
public static bool operator == (ObjectA lhs, string rhs) {
  return (lhs.PropertyX == rhs);
}
public static bool operator == (string lhs, ObjectA rhs) {
  return (lhs == rhs.PropertyX);
}