2010-11-29 3 views
7

Я пытаюсь написать запрос Linq, который возвращает массив объектов с уникальными значениями в своих конструкторах. Для целых типов Distinct возвращает только одну копию каждого значения, но когда я пытаюсь создать список объектов, все разваливается. Я подозреваю, что это проблема с оператором равенства для моего класса, но когда я устанавливаю точку останова, она никогда не попадает.Distinct() возвращает дубликаты с пользовательским типом

Фильтрация дубликата int в подвыражении решает проблему, а также избавляет меня от создания объектов, которые будут немедленно удалены, но мне любопытно, почему эта версия не работает.

UPDATE: 11:04 PM Несколько человек указали, что MyType не переопределяет GetHashCode(). Боюсь, я упростил пример. Первоначальный MyType действительно реализует его. Я добавил его ниже, измененный только для того, чтобы поместить хеш-код в временную переменную, прежде чем возвращать его.

Запуск через отладчик, я вижу, что все пять вызовов GetHashCode возвращают другое значение. И поскольку MyType наследует только объект Object, это, по-видимому, такое же поведение, что и объект Object.

Должен ли я быть правильным, чтобы сделать вывод о том, что хеш должен основываться на содержании Value? Это была моя первая попытка переопределить операторов, и в то время не показалось, что GetHashCode должен быть особенно причудливым. (Это первый раз, когда один из моих проверок равенства, похоже, не работает должным образом.)

class Program 
{ 
    static void Main(string[] args) 
    { 
     int[] list = { 1, 3, 4, 4, 5 }; 
     int[] list2 = 
      (from value in list 
      select value).Distinct().ToArray(); // One copy of each value. 
     MyType[] distinct = 
      (from value in list 
      select new MyType(value)).Distinct().ToArray(); // Two objects created with 4. 

     Array.ForEach(distinct, value => Console.WriteLine(value)); 
    } 
} 

class MyType 
{ 
    public int Value { get; private set; } 

    public MyType(int arg) 
    { 
     Value = arg; 
    } 

    public override int GetHashCode() 
    { 
     int retval = base.GetHashCode(); 
     return retval; 
    } 

    public override bool Equals(object obj) 
    { 
     if (obj == null) 
      return false; 

     MyType rhs = obj as MyType; 
     if ((Object)rhs == null) 
      return false; 

     return this == rhs; 
    } 

    public static bool operator ==(MyType lhs, MyType rhs) 
    { 
     bool result; 

     if ((Object)lhs != null && (Object)rhs != null) 
      result = lhs.Value == rhs.Value; 
     else 
      result = (Object)lhs == (Object)rhs; 

     return result; 
    } 

    public static bool operator !=(MyType lhs, MyType rhs) 
    { 
     return !(lhs == rhs); 
    } 
} 
+0

Внедрили ли вы GetHashCode()? Похоже, вы могли бы вернуть Value.HashCode() – cordialgerm

+0

В основном вы используете тип значения в типе ссылки MyClass. В этом нет ничего плохого, но вам нужно подумать о том, что значение MyClass является идентификатором экземпляра, а не местоположением объекта в памяти, являющимся его личностью (по умолчанию для ссылочных типов). Таким образом, переопределите GetHashCode(), чтобы вернуть Value.GetHashCode(), чтобы отразить эту идентичность. – dthorpe

+0

@dthorpe - Я думаю, что моей главной проблемой было неспособность распознать, как GetHashCode, вероятно, будет реализован в Object. Мое мышление в то время было по существу: «Мне не нужно ничего фантастического, реализация по умолчанию должна быть достаточно хорошей». Я не буду повторять эту ошибку. В следующий раз, когда я перейду на переопределение оператора ==, я найду совершенно другой способ испортить его. – ThatBlairGuy

ответ

8

Вам необходимо переопределить GetHashCode() в своем классе. GetHashCode должен быть реализован в тандеме с перегрузками Equals. Обычно код проверяет равенство hashcode перед вызовом Equals. Вот почему ваша реализация Equals не вызвана.

+0

Я считаю, что это неверно - согласно документам, Distinct() использует сопоставитель равенства по умолчанию. http://msdn.microsoft.com/en-us/library/bb348436.aspx –

+2

Reworded. Дело в том, что оригинальный плакат отметил, что его реализация Equals вообще не называется. Причина? Система, которая выполняет сравнение равенства (по умолчанию, равенство по умолчанию) проверяет хэш-код перед вызовом Equals. Он не переопределил GetHashCode, поэтому значения хэша никогда не будут одинаковыми, поэтому его реализация Equals никогда не будет вызвана. Если он исправляет проблему GetHashCode(), он, вероятно, сам по себе выяснит, что у него также есть ошибки в его реализации Equals. – dthorpe

+3

'Distinct' использует' Set ', чтобы отслеживать, какие элементы были замечены раньше, и' Set 'использует' GetHashCode() 'внутренне. – Gabe

2

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

public override bool Equals(object obj) 
{ 
    if (obj == null) 
     return false; 

    MyType rhs = obj as MyType; 
    if ((Object)rhs == null) 
     return false; 

    return this.Value == rhs.Value; 
} 
+0

Equals отменяет переопределенное ==, которое сравнивает значения. –

+0

Что вы думаете о @Henk ?? Дело в том, что это нужно добавить this.Value == rhs.Value. – Aliostad

+1

Изменение этой последней строки для сравнения свойства Value делает работу фильтрации, но реальный MyType имеет несколько свойств для сравнения. Мне действительно нужно сравнить их все? Заманчиво написать в качестве возвращаемого (MyType) this == (MyType) rhs, но это будет отражено в рекурсии .... – ThatBlairGuy

0

Я думаю MyType необходимо реализовать IEquatable для этой работы.

+2

Нет, только правильная реализация Equals и GetHashCode –

+1

Извините, мой плохой - я только что внедрил IEquatable , прежде чем обойти это. – cristobalito

2

В вас метод равенства вы все еще тестируем на равенство ссылок, а не семантического равенства, например, на этой линии:

result = (Object)lhs == (Object)rhs 

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

result = lhs.ID == rhs.ID 

Обратите внимание, что переопределение Equals() означает, что вы должны переопределить GetHashCode() , который является еще одним чайником рыбы и может быть довольно сложно сделать правильно.

+0

Как указывали другие, это не объясняет, почему переопределение OP из Equals никогда не вызывается - и решение должно также переопределить GetHashCode(). Это чревато трудностями, поэтому я сделал другое предложение в новом ответе. –

+0

На этом этапе я считаю, что частью проблемы является моя реализация GetHashCode(). Вместо простого возврата base.GetHashCode(), похоже, мне нужно вернуть что-то на основе Value. – ThatBlairGuy

1

Вам необходимо реализовать GetHashCode().

+0

Это верно только в том смысле, что это всегда должно выполняться при переопределении Equals(), но само по себе не решит проблему опроса. –

+1

Mikey Cee: Если 'GetHashCode()' не возвращает одно и то же значение для двух объектов, 'Equals()' никогда не будет вызван. – Gabe

+1

Mikey Cee: Но он отвечает на вопрос вопросника, почему его точка останова в Равах никогда не попадает. – dthorpe

0

других ответов довольно много покрыл то, что вам нужно правильно реализовать Equals и GetHashCode, но как примечание стороны, вы можете быть заинтересованы, чтобы знать, что анонимные типы имеют эти значения осуществляются автоматически:

var distinct = 
     (from value in list 
     select new {Value = value}).Distinct().ToArray(); 

Таким образом, без необходимости определять этот класс, вы автоматически получаете поведение Equals и GetHashCode, которое вы ищете. Круто, а?

1

Кажется, что простой операции Distinct может быть реализована более элегантно следующим образом:

var distinct = items.GroupBy(x => x.ID).Select(x => x.First()); 

, где идентификатор свойство, которое определяет, является ли два объекта семантически эквивалентны. Из путаницы здесь (в том числе и о себе), реализация Distinct() по умолчанию кажется немного запутанной.

+0

В коде реального мира я использовал вложенный запрос для фильтрации дубликата int. Это определенно было интересное упражнение. – ThatBlairGuy

Смежные вопросы