2010-09-03 8 views
35

Ho un elenco fortemente tipizzato di oggetti personalizzati, MyObject, che ha un ID proprietà insieme ad altre proprietà.
Diciamo che l'Id di un oggetto MyObject lo definisce come univoco e voglio verificare se la mia raccolta non ha già un oggetto MyObject con un ID 1 prima di aggiungere il mio nuovo MyObject alla raccolta.
Voglio usare if (! List.Contains (myObj)) ma come faccio a far rispettare il fatto che solo una o due proprietà di MyObject lo definiscono univoco?
Posso usare IComparable? O devo solo sovrascrivere un metodo Equals ma dovrei ereditare qualcosa prima è giusto?Che cosa usa Collection.Contains() per verificare la presenza di oggetti esistenti?



Grazie

risposta

36

List<T>.Contains utilizza EqualityComparer<T>.Default, che a sua volta utilizza IEquatable<T> se il tipo implementa, o object.Equals altrimenti.

Si potrebbe semplicemente implementare IEquatable<T> ma è una buona idea per sostituire object.Equals se farlo, e un molto buona idea per sostituire GetHashCode() se si fa questo:

public class SomeIDdClass : IEquatable<SomeIDdClass> 
{ 
    private readonly int _id; 
    public SomeIDdClass(int id) 
    { 
     _id = id; 
    } 
    public int Id 
    { 
     get { return _id; } 
    } 
    public bool Equals(SomeIDdClass other) 
    { 
     return null != other && _id == other._id; 
    } 
    public override bool Equals(object obj) 
    { 
     return Equals(obj as SomeIDdClass); 
    } 
    public override int GetHashCode() 
    { 
     return _id; 
    } 
} 

Si noti che il codice hash riferisce ai criteri per l'uguaglianza. Questo è vitale.

Questo lo rende applicabile anche per qualsiasi altro caso in cui l'uguaglianza, come definita dall'avere lo stesso ID, sia utile.Se si dispone di un uno-di obbligo di verificare se una lista è un oggetto del genere, allora probabilmente sarei suggerisco solo facendo:

return someList.Any(item => item.Id == cmpItem.Id); 
4

È possibile utilizzare LINQ per fare questo piuttosto facilmente.

var result = MyCollection.Any(p=>p.myId == Id); 
if(result) 
{ 
    //something 
} 
+0

oh diritto. sembra a posto. – topwik

+5

Un po 'più conciso sarebbe: MyCollection.Any (x => x.myId == Id) –

+8

Non solo più conciso, ma questo dovrebbe scorrere su tutta la collezione. 'Any' andrebbe in corto circuito alla prima partita. –

0

È possibile utilizzare IEquatable<T>. Implementalo nella tua classe, quindi verifica se T passa alla sezione Equals con lo stesso ID di questo.Id. Sono sicuro che questo funziona per il controllo di una chiave in un dizionario, ma non l'ho usato per una raccolta.

2

È possibile ignorare Equals e GetHashCode, implementare un IEqualityComparer<MyObject> e l'uso che nella chiamata Contains, o utilizzare un metodo di estensione come Any

if (!myList.Any(obj => obj.Property == obj2.Property && obj.Property2 == obj2.Property2)) 
    myList.Add(obj2); 
+0

+1, questa è una buona soluzione ad alte prestazioni, soprattutto se si utilizzano set o dizionari. (parlando al punto Equals/GetHashCode) –

27

List<T> utilizza l'operatore di confronto restituito da EqualityComparer<T>.Default e secondo la documentation per quella :

controlli di proprietà

il default se tipo T implementa l' System.IEquatable (Of T) interface e, in tal caso, restituisce un EqualityComparer (Of T) che utilizza tale implementazione. In caso contrario, restituisce un EqualityComparer (Of T) che utilizza le override di Object.Equals e Object.GetHashCode fornito da T.

Quindi è possibile implementare IEquatable<T> sulla vostra classe personalizzata, o ignorare il Equals (e GetHashCode) metodi per eseguire il confronto con le proprietà richieste. In alternativa è possibile utilizzare LINQ:

bool contains = list.Any(i => i.Id == obj.Id); 
+0

Vorrei andare con l'istruzione Linq. Se si desidera, è possibile implementare un metodo di estensione Contiene (questo elenco comparativo, elemento T, Func comparatore) che apparirebbe come List.Contains ma aggiungere la possibilità di fornire un delegato di confronto. Comprenderebbe un'istruzione Linq simile a ciò che viene fornito qui. – KeithS

+0

+1 per il linq: stavo cercando questo esatto snippet di codice – elwyn

+0

+1 per LINQ, perché consente di confrontare le classi da fonti esterne. E per l'eleganza del codice – PPC

0

prima definire classe helper con IEqualityComparer.

public class MyEqualityComparer<T> : IEqualityComparer<T> 
{ 
    Func<T, int> _hashDelegate; 

    public MyEqualityComparer(Func<T, int> hashDelegate) 
    { 
     _hashDelegate = hashDelegate; 
    } 

    public bool Equals(T x, T y) 
    { 
     return _hashDelegate(x) == _hashDelegate(y); 
    } 

    public int GetHashCode(T obj) 
    { 
     return _hashDelegate(obj); 
    } 
} 

Poi nel codice, basta definire il comparatore e usarlo:

var myComparer = new MyEqualityComparer<MyObject>(delegate(MyObject obj){ 
    return obj.ID; 
}); 

var result = collection 
    .Where(f => anotherCollection.Contains(f.First, myComparer)) 
    .ToArray(); 

In questo modo è possibile definire il modo in cui l'uguaglianza è calcolato senza modificare le classi. Puoi anche usarlo per elaborare oggetti da librerie di terze parti dato che non puoi modificare il loro codice.

Problemi correlati