Как определить, равны ли два значения универсального типа? - PullRequest
10 голосов
/ 09 января 2011

Обновление * Мне очень жаль ... мой пример кода содержал ошибку, которая привела ко многим ответам, которые я не понял. Вместо

Console.WriteLine("3. this.Equals   " + (go1.Equals(go2)));

Я хотел написать

Console.WriteLine("3. this.Equals   " + (go1.Equals(sb2)));

Я пытаюсь понять, как я могу успешно определить, равны ли два значения универсального типа друг другу. Основываясь на ответе Марка Байерса на этот вопрос Я думаю, я могу просто использовать value.Equals(), где значение является универсальным типом. Моя настоящая проблема в реализации LinkedList, но проблему можно показать на этом более простом примере.

class GenericOjbect<T> {
    public T Value { get; private set; }
    public GenericOjbect(T value) {
        Value = value;
    }
    public bool Equals(T value) {
        return (Value.Equals(value));
    }
}

Теперь я определяю экземпляр GenericObject<StringBuilder>, содержащий new StringBuilder("StackOverflow"). Я ожидаю получить true, если я вызову Equals(new StringBuilder("StackOverflow") для этого экземпляра GenericObject, но я получу false.

Пример программы, показывающей это:

using System;
using System.Text;

class Program
{
    static void Main()
    {
        var sb1 = new StringBuilder("StackOverflow");
        var sb2 = new StringBuilder("StackOverflow");

        Console.WriteLine("StringBuilder compare");
        Console.WriteLine("1. ==            " + (sb1 == sb2));
        Console.WriteLine("2. Object.Equals " + (Object.Equals(sb1, sb2)));
        Console.WriteLine("3. this.Equals   " + (sb1.Equals(sb2)));

        var go1 = new GenericOjbect<StringBuilder>(sb1);
        var go2 = new GenericOjbect<StringBuilder>(sb2);

        Console.WriteLine("\nGenericObject compare");
        Console.WriteLine("1. ==            " + (go1 == go2));
        Console.WriteLine("2. Object.Equals " + (Object.Equals(go1, sb2)));
        Console.WriteLine("3. this.Equals   " + (go1.Equals(sb2)));
        Console.WriteLine("4. Value.Equals  " + (go1.Value.Equals(sb2.Value)));
    }
}

Для трех методов сравнения двух объектов StringBuilder только метод экземпляра StringBuilder.Equals (третья строка) возвращает true. Это то, что я ожидал. Но при сравнении объектов GenericObject его метод Equals () (третья строка) возвращает false. Интересно, что четвертый метод сравнения возвращает true. Я думаю, что третье и четвертое сравнение на самом деле делают то же самое.

Я бы ожидал true. Потому что в методе Equals () класса GenericObject оба типа value и Value имеют тип T, который в данном случае равен StringBuilder. Исходя из ответа Марка Байерса в на этот вопрос , я ожидал, что метод Value.Equals() будет использовать метод Equals () StringBuilder. И, как я показал, метод Equal () в StringBuilder возвращает true.

Я даже пытался

public bool Equals(T value) {
    return EqualityComparer<T>.Default.Equals(Value, value);
}

но это также возвращает ложь.

Итак, два вопроса здесь:

  1. Почему код не возвращает true?
  2. Как я могу реализовать метод Equals, чтобы он возвращал true?

Ответы [ 7 ]

6 голосов
/ 09 января 2011

Как указано в ответе Марка Гравелла , проблема в реализации StringBuilder Equals(object), отличной от реализации в Equals(StringBuilder).

Затем вы можете игнорировать проблему, потому что ваш код будет работать с любыми другими когерентно реализованными классами, или вы можете использовать динамический метод для решения проблемы (опять же, как предложил Марк Гравелл).

Но, учитывая, что вы не используете C # 4 (поэтому не динамический), вы можете попробовать следующим образом:

public bool Equals(T value)
{
   // uses Reflection to check if a Type-specific `Equals` exists...
   var specificEquals = typeof(T).GetMethod("Equals", new Type[] { typeof(T) });
   if (specificEquals != null &&
       specificEquals.ReturnType == typeof(bool))
   {
       return (bool)specificEquals.Invoke(this.Value, new object[]{value});
   }
   return this.Value.Equals(value);
}
5 голосов
/ 09 января 2011

Ваш код выглядит нормально. Проблема здесь в том, что StringBuilder имеет запутанный набор Равных, которые являются противоречивыми. В частности, Equals (StringBuilder) не согласен с Equals (объектом), даже если объект равен a StringBuilder.

Все, что нужно EqualityComparer<T> - это нормальная реализация Equals (объекта). Интерфейс (IEquatable<T>) является необязательным. К сожалению, StringBuilder не имеет этого (по крайней мере, по сравнению с Equals (StringBuilder), который используется в вашем третьем тесте).

Но в целом совет такой: используйте EqualityComparer<T>; это поддерживает:

  • nullable-of-T со стандартными «поднятыми» правилами
  • IEquatable-оф-T
  • Object.equals
3 голосов
/ 09 января 2011

Строка 3 с универсальным объектом не вызывает ваш пользовательский письменный метод.Вместо этого он вызывает базу Object.Equals(object).Чтобы вызвать пользовательский метод, вам нужно передать T, а не GenericObject<T>.Что-то вроде: go1.Equals(go2.Value)

2 голосов
/ 09 января 2011

Как говорит Эрик Липперт в ответ на этот вопрос - Разрешение перегрузки выполняется во время компиляции.

Если вы посмотрите на реализацию StringBuilder, вы заметите, что она перегружает Equals и не переопределяет ее. Это в основном корень проблемы в том, почему StringBuilder.Equals не работает так, как вы ожидали в вашем примере.

Возьмите следующие 2 класса в качестве примера. Overloader аналогичен StringBuilder в примере, поскольку он перегружает Equals. Overrider очень похож, за исключением того, что он переопределяет Equals.

public class Overloader
{
  public string Str {get;private set;}
  public Overloader (string str) {Str = str;}

  public bool Equals( Overloader str )
  {
    return this.Str.Equals( str );
  }
}

public class Overrider
{
  public string Str {get;private set;}
  public Overrider (string str) {Str = str;}

  public override bool Equals( object obj )
  {
    if ( obj is Overrider )
    {
      return this.Str.Equals( (obj as Overrider).Str );
    }
    return base.Equals( obj );
  }
}

Я немного изменил ваш GenericObject<T> класс в моем примере:

class GenericOjbect<T>
{
  public T Value {get;private set;}
  public GenericOjbect( T val ) {Value = val;}

  public bool Equals( T val )
  {
    return Value.Equals( val );
  }

  public override bool Equals( object obj )
  {
    if ( obj is T )
    {
      return this.Equals( ( T )obj );
    }
    if (obj != null && obj is GenericOjbect<T> )
    {
      return this.Equals( ( obj as GenericOjbect<T> ).Value );
    }
    return base.Equals( obj );
  }
}

В этом примере программы вы увидите, что Overloader (или Stringbuilder в этом отношении) вернет false. Однако Overrider возвращает true.

class Program
{
  static void Main( string[] args )
  {
    var goOverloader1 = new GenericOjbect<Overloader>( new Overloader( "StackOverflow" ) );
    var goOverloader2 = new GenericOjbect<Overloader>( new Overloader( "StackOverflow" ) );

    var goOverrider1 = new GenericOjbect<Overrider>( new Overrider( "StackOverflow" ) );
    var goOverrider2 = new GenericOjbect<Overrider>( new Overrider( "StackOverflow" ) );

    Console.WriteLine( "Overrider  : {0}", goOverloader1.Equals( goOverloader2 ) ); //False
    Console.WriteLine( "Overloader : {0}", goOverrider1.Equals( goOverrider2 ) ); //True
  }
}

Снова ссылка на Эрика Липперта - разрешение перегрузки выполняется во время компиляции. Это означает, что компилятор в основном выглядит на вашем GenericObject<T>.Equals( T val ) так:

public bool Equals( T val )
{
  return Value.Equals( (Object) val );
}

Чтобы ответить на ваш вопрос Как определить, равны ли два значения универсального типа? . Есть две вещи, которые вы могли бы сделать.

  1. Если вы владеете всеми объектами, которые будут обернуты в GenericObject<T>, убедитесь, что они все как минимум перекрывают Equals.
  2. Вы можете выполнить магию отражения в GenericObject<T>.Equals(T val), чтобы вручную выполнить позднее связывание.
1 голос
/ 09 января 2011

Сначала сравните выходные данные typeof (), чтобы убедиться, что вы сравниваете объекты одного типа, затем напишите метод Equals для класса X, который принимает другой экземпляр класса X, и сравните все свойства ...найдите что-то другое, верните false, иначе продолжайте, пока не вернете true.

Приветствия:)

1 голос
/ 09 января 2011

Вы можете либо реализовать IEquatable<T>, либо реализовать класс сравнения, который реализует IEqualityComparer<T>.

Убедитесь, что value вы проверяете на равенствоявляется неизменным и устанавливается только при инициализации класса.

Другим соображением будет реализация IComparer<T>, при реализации этого вам не нужно беспокоиться о хэше-код, и, следовательно, может быть реализован также для изменяемых типов / полей.

Как только вы правильно реализует IEquatable<T>в вашем классе ваши вопросы будут решены.

Обновление: вызов return EqualityComparer<T>.Default.Equals(Value, value); в основном вернет тот же результат, поскольку IEqualityComparer<T> не реализовано ...

0 голосов
/ 09 января 2011

Чтобы уточнить ответ Гедеона (пожалуйста, подпишите его, а не мой): метод, который вы определили, имеет подпись

bool GenericOjbect<T>::Equals( T )

Пока ваш код вызывает

bool GenericOjbect<T>::Equals( GenericOjbect<T> )

который наследуется (не переопределяется) от Object.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...