Если я реализую IEquatable <T>, потеряю ли я возможность сравнения по ссылке? - PullRequest
1 голос
/ 27 февраля 2020

Я хотел бы сравнить объект с другим, чтобы узнать, равны они или нет. Таким образом, кажется, способ сделать это - реализовать интерфейс IEquatable в моем классе.

Но я не уверен, как это повлияет на поведение моего класса. Теперь в моем коде я использую для сравнения двух объектов по ссылке таким образом:

if(myObject1 == myObject2)
{
    // code when both objects are the same. 
    // Set values in some properties and do some actions according that.
}
else
{
    // code when both objects are no the same. 
    // Set values in some properties and do some actions according that.
}

Но в некоторых особых случаях, в основном при тестировании, я хотел бы сравнить 2 объекта и считать равными, если все свойства равны, но в этом случае я не знаю, повлияет ли это на мой основной код, в котором я сравниваю по ссылке.

Другой вариант - реализовать метод, который сравнивает таким образом, но Я не знаю, хорошая ли это идея или лучше реализовать интерфейс IEquatable.

Спасибо.

Ответы [ 3 ]

5 голосов
/ 27 февраля 2020

Здесь происходит несколько разных вещей.

Во-первых, IEquatable<T> не напрямую связано с оператором ==. Если вы реализуете IEquatable<T>, но не переопределяете оператор ==, то == продолжит делать то, что в данный момент делает: сравнивайте ваши объекты по ссылке.

IEquatable<T> дает вам Equals(T) метод, и все тут. Само по себе это не влияет на Equals(object) (что также необходимо реализовать) или == и !=.

Итак, давайте предположим, что вы перегружаете оператор ==, чтобы вызвать наш Equals метод:

public static bool operator ==(Foo left, Foo right) => Equals(left, right);
public static bool operator !=(Foo left, Foo right) => !Equals(left, right);

Это только изменило оператор == между двумя Foo экземплярами. Вы по-прежнему можете написать:

if ((object)myObject1 == (object)myObject2))

, и это приведет к использованию метода object ==, который сравнивается по ссылке.

Другой способ сделать это:

if (ReferenceEquals(myObject1, myObject2))

, который делает то же самое .


Также обратите внимание, что IEquatable<T> редко используется для классов: в действительности нет никакого смысла. Классы уже имеют метод Equals(object) и метод GetHashCode(), которые необходимо переопределить, и добавление метода Equals(T) не дает вам много.

IEquatable<T>, однако, полезно для структур: они также есть метод Equals(object), который нужно переопределить, но если вы на самом деле вызовите его, то в итоге вы закончите боксом, так как он принимает object. Если вы реализуете IEquatable<T> здесь, то вы также получаете метод Equals(T), который можно вызывать без каких-либо ограничений.


Все это говорит о том, что я напишу ваш код так как он предназначен для работы в вашем приложении и для выполнения любых вещей, специфичных для тестирования c, в вашем тестовом проекте. Это означает, что если ваши объекты должны сравниваться по ссылке в вашем коде, я бы не добавлял ничего нового к самому объекту.

В вашем тестовом проекте вы можете написать свой собственный метод для проверки наличия двух экземпляров объекта. ваш объект обладает такими же свойствами (как пользовательский bool AreFoosEqual(Foo f1, Foo f2) или как полноценный IEqualityComparer<Foo> экземпляр). Затем вы можете заставить это делать именно то, что нужно вашим тестам, не беспокоясь о нарушении работы вашего приложения.

Вы также можете написать свой тестовый метод в виде последовательности утверждений, которая сообщает вам , какое свойство неверно, а в чем разница. Это может дать вам более богатый результат теста:

public static void AssertFoosEquals(Foo f1, Foo f2)
{
    Assert.AreEqual(f1.Foo, f2.Foo, "Foo property mismatch");
    Assert.AreEqual(f1.Bar, f2.Bar, "Bar property mismtach");
}
3 голосов
/ 27 февраля 2020

Если вы хотите сравнить одинаковые объекты, но различными способами, я предлагаю использовать Comparer , который реализует IEqualityComparer<T>:

  public class MyClassTestComparer : IEqualityComparer<MyClass> {
    public bool Equals(MyClass x, MyClass y) {
      if (ReferenceEquals(x, y))
        return true;
      else if (null == x || null == y)
        return false;

      return x.Propery1 == y.Property1 && 
             x.Propery2 == y.Property2 &&
             x.ProperyN == y.PropertyN; 
    }

    public int GetHashCode(MyClass obj) {
      return obj == null 
        ? 0 
        : obj.Propery1.GetHashCode() ^ obj.Propery2.GetHashCode();
    }
  }

тогда вы можете выбрать правильный компаратор

 public static IEqualityComparer<MyClass> MyClassComparer {
   if (we_should_use_test_comparer)
     return new MyClassTestComparer();
   else
     return EqualityComparer<MyClass>.Default;  
 } 

Наконец if будет

 if (MyClassComparer.Equals(myObject1, myObject2)) {
   // Equals: by reference or by properties (in test)
 }
1 голос
/ 27 февраля 2020

Когда вы выполняете модульный тест ->

Как:

public void TestSomething()
{
     var expectedValue1 = "SomeExpectedValue";

     var actualValue = instance.Method();

     Assert.Equal(expectedValue1, actualValue);

}

Тогда вы просто "утверждаете" свойства, на которые хотите посмотреть, если вы возвращаете объект, а не значение:

public void TestSomething()
{
     var expectedValue1 = "SomeExpectedValue";

     TestableObject subject = instance.Method();

     Assert.Equal(expectedValue1, subject.Somevalue);
}

Если вам нужна более общая настройка c, вы можете написать отражение с использованием потока generi c, который просматривает все свойства объекта и пытается сопоставить их с другим предоставленный объект.

Или вы можете скачать пакет инструментов nuget, который уже позволяет вам это делать.

Я бы не отказывался от какой-либо функциональности, просто для целей тестирования. Таким образом, лежит код спагетти. В идеале ваш код должен проверяться на 100% с помощью модульных тестов, без указания c разделов кода, которые дополняют или помогают вашим методам тестирования. (Если указанный код не ограничен самим тестовым проектом и не содержится ни в одном из тестируемого кода.

...