Правильно реализовать сравнение двух объектов разного типа, но семантически эквивалентных

Я нашел похожий вопрос

Как сравнить два совершенно разных объекта с похожими свойствами

которые могут неявно и/или частично отвечать на мой вопрос.

Предположим, я хочу сравнить (без множества вложенных условий) этот объект:

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

к System.String. Меня интересует только равенство или неравенство (а не диапазон значений, касающихся идентичности).

Внедрение IEquatable<string> в ObjectA является правильным выбором? Меня не волнует, что просто работает, я хочу определить правильный шаблон для такого случая.

В качестве дополнительной информации, обратите внимание, что ObjectA часто предоставляется как последовательность IEnumerable<ObjectA>.

Мне не нужно знать, "string" == или != objectA экземпляр; сортировка не задействована.

Изменить, чтобы уточнить (и помочь)

Извините, но написать хороший вопрос иногда сложно...

Предположим, я не могу представить ObjectA в виде строки для целей сравнения (нарушение инкапсуляции недопустимо).

  • В контексте-1 я должен сопоставить его с PropertyY.

  • В контексте-2 я должен сопоставить его с алгоритмом, примененным к PropertyY/PropertyZ.

Решение @Oliver в конце вопроса снова помогает мне (и снова +1). Я могу просто определить собственный интерфейс:

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

Поскольку у меня также есть ObjectB с той же логикой, но другими свойствами, оба будут реализовывать IContextConverter (или, может быть, я найду лучшее имя), избегая нарушения RAP.


person jay    schedule 07.03.2013    source источник
comment
Я думаю, что вы должны переопределить метод Equals, как показано в stackoverflow.com/questions/4219261/.   -  person Mihai8    schedule 07.03.2013
comment
Что ты имеешь в виду? Если System.String равно "Hello mum!", а ObjectA имеет x, y, z, равные "Cool", 'Z' и 42L, как мне выяснить, равны они или нет?   -  person Jeppe Stig Nielsen    schedule 07.03.2013
comment
Не уверен, что знаю. Я думаю, что он будет использоваться неправильно, поскольку он предназначен для сравнения объектов одного типа... Посмотрите на сравнение типов с оператором (например, ==). Объект в левой части и другой в правой части имеют один и тот же тип.   -  person jay    schedule 07.03.2013
comment
@Jeppe Stig Nielsen, это не важно для вопроса. У меня есть алгоритм сравнения, и я хочу его инкапсулировать в более правильном для дизайна виде. Возможно, придерживаясь шаблона.   -  person jay    schedule 07.03.2013


Ответы (3)


Я бы настоятельно рекомендовал не реализовывать IEquatable<string>, потому что особенно при работе с коллекциями, словарями, LINQ и т. д. вы действительно не знаете, когда один из этих методов будет вызван где-то глубоко внутри, что может привести к тонкие баги.

Из-за того, что вам нравится сравнивать два объекта разных типов, простой 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.
person Oliver    schedule 07.03.2013
comment
+1, @Oliver, ToEquatableString() возвращает оболочку ObjectA, реализующую IEquatable‹string›? - person jay; 07.03.2013
comment
@jay: Нет. Он возвращает строку, представляющую objectA. По сути, то же самое, что и .ToString(). Эту строку можно использовать для сравнения, как и любую другую строку. - person Oliver; 07.03.2013
comment
+1, то же самое хотел написать. Хотя этот интерфейс предоставляется фреймворком, я думаю, что в 99,9999% случаев использовать его — очень плохая идея. Со всеми примерами, которые я видел до сих пор, используя его (или аналогичные концепции), он не сделал ничего хорошего, но создал нечитаемый и непонятный код вместо простого ObjectA.PropertyX == "string", который может понять даже тот, кто написал свой первый Hello world. ! за день до. - person YavgenyP; 07.03.2013
comment
@Oliver, кажется, это более простое решение; но и решение TypeConverter кажется чистым. Это ответ на мой вопрос. - person jay; 07.03.2013
comment
@Oliver, последнее редактирование уточняет и представляет чистую реализацию. Извините, я не могу снова +1... - person jay; 09.03.2013

Есть много возможностей.

Если вы считаете, что ObjectA является своего рода System.String, вы можете написать определяемое пользователем преобразование (неявное или явное) из ObjectA в System.String, или из System.String в ObjectA, или в обоих направлениях.

Вы также можете перегрузить операторы == и != сигнатурой типа operator ==(ObjectA oa, string s). Помните, что между oa == s и s == oa есть разница.

Любая из этих двух возможностей может привести к путанице. Также было бы запутанно переопределить виртуальный Equals(object) или ввести перегрузку Equals(string). Поэтому я не рекомендую внедрять IEquatable<string>.

Почему бы просто не написать метод с неиспользуемым именем, например public bool EqualsString(string s)? Тогда вам, конечно, придется вызывать этот метод явно, но это приведет к меньшей путанице. Другой идеей было бы использовать конструктор сигнатуры public ObjectA(string s), а затем реализовать «однородное» равенство ObjectA и ObjectA.

person Jeppe Stig Nielsen    schedule 07.03.2013
comment
+1 @Jeppe Stig Neilsen, понравилось решение ObjectA::EqualsString(string). Это приводит к тому, что код становится простым и понятным для чтения (и по мере того, как я узнаю больше, это становится моим приоритетом). - person jay; 07.03.2013

ПРИМЕЧАНИЕ. Я не рекомендую это решение для данного конкретного случая, но я часто использовал эту структуру для реализации равенства значений для структур. Трудные компромиссы распространены в нашей области, и ответы на них, сопровождаемые соответствующими предостережениями, кажутся уместными.

Я полагаю, что вы хотите разработать семамтику равенства значений для своего класса таким же образом, как инфраструктура .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 и строкой:

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);
}
person Pieter Geerkens    schedule 07.03.2013
comment
+1 за образец кода. Но все же сомневаюсь, что это решение. Моя основная потребность заключается в поиске IEnumerable‹ObjectA› с использованием System.String без перемещения логики сравнения в запрос Linq. - person jay; 07.03.2013
comment
@jay: см. обновление. Вы можете столкнуться с трудностями, если платформа попытается выполнить string.Equals(ObjectA) вместо ObjectA.Equals(string). Я не рекомендую это, так как это, вероятно, зависит от конкретной версии реализации фреймворка. - person Pieter Geerkens; 07.03.2013
comment
это лучше объясняет ваше решение. Оценил. - person jay; 07.03.2013