come verificare se l'oggetto esiste già in un elenco


102

Ho una lista

  List<MyObject> myList

e sto aggiungendo elementi a un elenco e desidero controllare se quell'oggetto è già nell'elenco.

quindi prima di farlo:

 myList.Add(nextObject);

Voglio vedere se nextObject è già nell'elenco.

L'oggetto "MyObject" ha un numero di proprietà ma il confronto si basa sulla corrispondenza su due proprietà.

Qual è il modo migliore per eseguire un controllo prima di aggiungere un nuovo "MyObject" a questo elenco di "MyObject".

L'unica soluzione che ho pensato è stata quella di passare da un elenco a un dizionario e quindi rendere la chiave una stringa concatenata delle proprietà (questo sembra un po 'poco elegante).

Altre soluzioni più pulite usando list o LINQ o qualcos'altro?

Risposte:


153

Dipende dalle esigenze della situazione specifica. Ad esempio, l'approccio del dizionario sarebbe abbastanza buono assumendo:

  1. L'elenco è relativamente stabile (non molti inserimenti / eliminazioni, per i quali i dizionari non sono ottimizzati)
  2. L'elenco è piuttosto ampio (altrimenti l'overhead del dizionario è inutile).

Se quanto sopra non è vero per la tua situazione, usa il metodo Any():

Item wonderIfItsPresent = ...
bool containsItem = myList.Any(item => item.UniqueProperty == wonderIfItsPresent.UniqueProperty);

Questo enumererà l'elenco finché non trova una corrispondenza o finché non raggiunge la fine.


L'uso di un delegato predicato per list.exists è un'altra soluzione vedi sotto, ma se hai liste enormi e valore chiave con un dizionario sarà molto più veloce in quanto è una tabella hash! Buon divertimento
Doug

1
Come controllare più valori?
Nitin Karale

80

Usa semplicemente il metodo Contiene . Nota che funziona in base alla funzione di uguaglianzaEquals

bool alreadyExist = list.Contains(item);

5
Questo non ha funzionato per me, ha sempre detto che non esiste
Si8

4
@ Si8 Se stai cercando di confrontare oggetti, devi essere sicuro che l'implementazione di IEquatable <T> .Equals sia implementata correttamente per il tipo di oggetto. Altrimenti, non confronterai il contenuto dell'oggetto. Vedere il collegamento Contiene indicato da Ahmad per un esempio di come implementarlo.
Doug Knudsen

56

Se è possibile mantenere queste 2 proprietà, potresti:

bool alreadyExists = myList.Any(x=> x.Foo=="ooo" && x.Bar == "bat");

7

Sei sicuro di aver bisogno di un elenco in questo caso? Se si popola l'elenco con molti elementi, le prestazioni ne risentiranno con myList.Containso myList.Any; il tempo di esecuzione sarà quadratico. Potresti prendere in considerazione l'utilizzo di una struttura dati migliore. Per esempio,

 public class MyClass
    {
        public string Property1 { get; set; }
        public string Property2 { get; set; }

    }

    public class MyClassComparer : EqualityComparer<MyClass>
    {
        public override bool Equals(MyClass x, MyClass y)
        {
            if(x == null || y == null)
               return x == y;

            return x.Property1 == y.Property1 && x.Property2 == y.Property2;
        }

        public override int GetHashCode(MyClass obj)
        {
            return obj == null ? 0 : (obj.Property1.GetHashCode() ^ obj.Property2.GetHashCode());
        }
    }

Puoi usare un HashSet nel modo seguente:

  var set = new HashSet<MyClass>(new MyClassComparer());
  foreach(var myClass in ...)
     set.Add(myClass);

Naturalmente, se questa definizione di uguaglianza per MyClassè "universale", non è necessario scrivere IEqualityComparerun'implementazione; potresti semplicemente sovrascrivere GetHashCodee Equalsnella classe stessa.


Sì, bool per V era il mio preferito. Del resto, non è passato molto tempo (eh, circa 3 settimane) che HashSet non era disponibile per me perché stavo lavorando su codice 2.0, e ho lasciato l'implementazione Mono di HashSet perché è così dannatamente utile :)
Jon Hanna

4

Un altro punto da menzionare è che dovresti assicurarti che la tua funzione di uguaglianza sia come ti aspetti. È necessario sovrascrivere il metodo equals per impostare le proprietà dell'oggetto che devono corrispondere affinché due istanze siano considerate uguali.

Quindi puoi semplicemente fare mylist.contains (item)


3

Ecco una rapida app per console per descrivere il concetto di come risolvere il tuo problema.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication3
{
    public class myobj
    {
        private string a = string.Empty;
        private string b = string.Empty;

        public myobj(string a, string b)
        {
            this.a = a;
            this.b = b;
        }

        public string A
        {
            get
            {
                return a;
            }
        }

        public string B
        {
            get
            {
                return b;
            }
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            List<myobj> list = new List<myobj>();
            myobj[] objects = { new myobj("a", "b"), new myobj("c", "d"), new myobj("a", "b") };


            for (int i = 0; i < objects.Length; i++)
            {
                if (!list.Exists((delegate(myobj x) { return (string.Equals(x.A, objects[i].A) && string.Equals(x.B, objects[i].B)) ? true : false; })))
                {
                    list.Add(objects[i]);
                }
            }
        }
    }
}

Godere!


3

Modifica: prima avevo detto:


Cosa c'è di inelegante nella soluzione del dizionario. Mi sembra perfettamente elegante, specialmente dal momento che devi solo impostare il comparatore nella creazione del dizionario.


Ovviamente, però, non è elegante usare qualcosa come chiave quando è anche il valore.

Quindi userei un HashSet. Se le operazioni successive richiedessero l'indicizzazione, creerei un elenco da esso quando l'aggiunta è stata eseguita, altrimenti, usa semplicemente l'hashset.


Lo userei solo se l'elenco di oggetti fosse enorme poiché è una tabella hash e sono ottimi per ricerche veloci.
Doug

0

Semplice ma funziona

MyList.Remove(nextObject)
MyList.Add(nextObject)

o

 if (!MyList.Contains(nextObject))
    MyList.Add(nextObject);

-1

Se usi EF core aggiungi

 .UseSerialColumn();

Esempio

modelBuilder.Entity<JobItem>(entity =>
        {
            entity.ToTable("jobs");

            entity.Property(e => e.Id)
                .HasColumnName("id")
                .UseSerialColumn();
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.