2010-11-29 6 views
7

Sto provando a scrivere una query Linq che restituisce una matrice di oggetti, con valori univoci nei relativi costruttori. Per i tipi interi, Distinct restituisce solo una copia di ciascun valore, ma quando provo a creare il mio elenco di oggetti, le cose vanno in pezzi. Sospetto che sia un problema con l'operatore di uguaglianza per la mia classe, ma quando imposto un breakpoint non viene mai colpito.Distinct() restituisce i duplicati con un tipo definito dall'utente

Il filtraggio del duplicato int in una sottoespressione risolve il problema, inoltre mi salva dalla creazione di oggetti che verranno immediatamente eliminati, ma sono curioso del perché questa versione non funzioni.

AGGIORNAMENTO: 11:04 PM Diverse persone hanno sottolineato che MyType non sovrascrive GetHashCode(). Temo di aver semplificato eccessivamente l'esempio. Il MyType originale lo implementa davvero. L'ho aggiunto di seguito, modificato solo per inserire il codice hash in una variabile temporanea prima di restituirlo.

Eseguendo il debugger, vedo che tutte e cinque le invocazioni di GetHashCode restituiscono un valore diverso. E poiché MyType eredita solo da Object, questo è presumibilmente lo stesso comportamento che l'oggetto mostrerebbe.

Sarei corretto quindi per concludere che l'hash dovrebbe invece essere basato sul contenuto di Valore? Questo è stato il mio primo tentativo di scavalcare gli operatori e, al momento, non sembrava che il codice GetHash fosse particolarmente elaborato. (Questa è la prima volta che uno dei miei assegni di uguaglianza non sembra funzionare correttamente.)

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

Avete implementato GetHashCode()? Sembra che tu possa restituire Value.HashCode() – cordialgerm

+0

Stai essenzialmente implementando un tipo di valore nel tuo tipo di riferimento MyClass. Non c'è niente di sbagliato in questo, ma è necessario pensare in termini del valore di MyClass come identità dell'istanza anziché della posizione dell'oggetto in memoria che è la sua identità (l'impostazione predefinita per i tipi di riferimento). Quindi, eseguire l'override di GetHashCode() per restituire Value.GetHashCode() per riflettere tale identità. – dthorpe

+0

@dthorpe - Penso che il mio problema principale fosse un errore nel riconoscere come probabilmente GetHashCode fosse implementato in Object. All'epoca pensavo essenzialmente: "Non ho bisogno di niente di particolare, l'implementazione di default dovrebbe essere abbastanza buona". Non farò di nuovo quell'errore La prossima volta che andrò a sostituire l'operatore ==, troverò un modo completamente diverso di rovinarlo. – ThatBlairGuy

risposta

8

È necessario eseguire l'override di GetHashCode() nella classe. GetHashCode deve essere implementato in tandem con sovraccarichi Equals. È normale che il codice controlli l'uguaglianza di hashcode prima di chiamare Equals. Ecco perché la tua implementazione di Equals non viene chiamata.

+0

Credo che questo non sia corretto - secondo i documenti, Distinct() usa il comparatore di uguaglianza di default. http://msdn.microsoft.com/en-us/library/bb348436.aspx –

+2

Ristrutturato. Il punto è che il poster originale ha notato che la sua implementazione di Equals non viene affatto chiamata. Ragionare? Il sistema che esegue il confronto di uguaglianza (Comparatore di uguaglianza predefinito, qualunque sia) controlla l'hashcode prima di chiamare Equals. Non ha scavalcato GetHashCode, quindi i valori hash non saranno mai gli stessi così la sua implementazione di Equals non verrà mai chiamata. Se risolve il problema GetHashCode(), probabilmente scoprirà da solo che ha anche bug nella sua implementazione di Equals. – dthorpe

+3

'Distinct' utilizza un' set 'per tenere traccia di quali elementi sono stati visti prima, e' set '' utilizza GetHashCode() 'internamente. – Gabe

2

tuo sospetto è corretto, è l'uguaglianza che attualmente solo controlla i riferimenti agli oggetti. Anche l'implementazione non fa nulla in più, modificarlo a questo:

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

Equalizza il parametro deframmentato == che confronta i valori. –

+0

Cosa stai parlando di @Henk ?? Il punto è che questo deve essere aggiunto this.Value == rhs.Value. – Aliostad

+1

Cambiando l'ultima riga per confrontare la proprietà Value, il filtro funziona, ma il vero MyType ha più proprietà da confrontare. Devo davvero confrontarli tutti? Si sarebbe tentati di scrivere come ritorno RHS (MyType) questo == (MyType), ma sarebbe testa fuori in ricorsione .... – ThatBlairGuy

0

penso MyType deve attuare IEquatable per far funzionare tutto questo.

+2

No, solo una corretta implementazione di Equals e GetHashCode –

+1

spiacenti - il mio male - Ho appena implementato IEquatable prima per aggirare questo. – cristobalito

2

In te metodo di uguaglianza si sta ancora testando per l'uguaglianza di riferimento, piuttosto che l'uguaglianza semantica, ad esempio, su questa linea:

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

si sono appena confrontano due riferimenti a oggetti che, anche se in loro possesso esattamente gli stessi dati , non sono ancora lo stesso oggetto. Invece, il test per l'uguaglianza deve confrontare una o più proprietà del tuo oggetto. Per esempio, se l'oggetto aveva una proprietà ID, e gli oggetti con lo stesso ID dovrebbe essere considerato semanticamente equivalenti, allora si potrebbe fare questo:

result = lhs.ID == rhs.ID 

Si noti che l'override equals() significa che si dovrebbe anche ignorare GetHashCode() , che è un altro bollitore di pesce, e può essere abbastanza difficile da fare correttamente.

+0

Come altri hanno sottolineato, questo non spiega perché l'override di OP di OP non viene mai chiamato - e la soluzione è di sovrascrivere anche GetHashCode(). Questo è pieno di difficoltà, quindi ho fatto un altro suggerimento in una nuova risposta. –

+0

A questo punto, credo che parte del problema sia la mia implementazione di GetHashCode(). Invece di restituire semplicemente base.GetHashCode(), sembra che debba restituire qualcosa basato sul valore. – ThatBlairGuy

1

È necessario implementare GetHashCode().

+0

Questo è vero solo nel fatto che dovrebbe sempre essere eseguito quando si esegue l'override di Equals(), ma da solo non risolverà il problema del questionario. –

+1

Mikey Cee: Se 'GetHashCode()' non restituisce lo stesso valore per due oggetti, 'Equals()' non verrà mai chiamato. – Gabe

+1

Mikey Cee: Ma risponde alla domanda del questionario sul motivo per cui il suo breakpoint in Equals non viene mai colpito. – dthorpe

0

Le altre risposte hanno praticamente coperto il fatto che è necessario implementare correttamente Equals e GetHashCode, ma come un lato Nota Potrebbe essere interessati a sapere che tipi anonimi sono questi valori implementato automaticamente:

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

Quindi, senza dover definire questa classe, ottieni automaticamente il comportamento di Equals e GetHashCode che stai cercando. Fantastico, eh?

1

Sembra che una semplice operazione Distinct può essere implementato più elegantemente come segue:

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

dove ID è la proprietà che determina se due oggetti sono semanticamente equivalenti. Dalla confusione qui (incluso quella di me stesso), l'implementazione di default di Distinct() sembra essere un po 'contorta.

+0

Nel codice del mondo reale, ho usato una query nidificata per filtrare il duplicato int. Questo è stato sicuramente un esercizio interessante. – ThatBlairGuy

Problemi correlati