Verwenden von LINQ zum Entfernen von Elementen aus einer Liste <T>


654

Angenommen, ich habe eine LINQ-Abfrage wie:

var authors = from x in authorsList
              where x.firstname == "Bob"
              select x;

Da authorsListist der Typ List<Author>, wie kann ich die löschen AuthorElemente aus , authorsListdie von der Abfrage in zurückgegeben werden authors?

Oder anders ausgedrückt: Wie kann ich alle Vornamen löschen, die Bob entsprechen authorsList?

Hinweis: Dies ist ein vereinfachtes Beispiel für die Zwecke der Frage.

Antworten:


1138

Nun, es wäre einfacher, sie überhaupt auszuschließen:

authorsList = authorsList.Where(x => x.FirstName != "Bob").ToList();

Dies würde jedoch nur den Wert von ändern, authorsListanstatt die Autoren aus der vorherigen Sammlung zu entfernen. Alternativ können Sie verwenden RemoveAll:

authorsList.RemoveAll(x => x.FirstName == "Bob");

Wenn Sie es wirklich basierend auf einer anderen Sammlung tun müssen, würde ich ein HashSet, RemoveAll und Contains verwenden:

var setToRemove = new HashSet<Author>(authors);
authorsList.RemoveAll(x => setToRemove.Contains(x));

14
Was ist der Grund für die Verwendung von HashSet für eine andere Sammlung?
123 456 789 0

54
@LeoLuis: Es macht die ContainsPrüfung schnell und stellt sicher, dass Sie die Sequenz nur einmal auswerten.
Jon Skeet

2
@LeoLuis: Ja, das Erstellen eines HashSets aus einer Sequenz wertet es nur einmal aus. Nicht sicher, was Sie unter "schwachem Sammlungssatz" verstehen.
Jon Skeet

2
@ AndréChristofferAndersen: Was meinst du mit "veraltet"? Es funktioniert noch. Wenn Sie eine haben List<T>, ist es in Ordnung, sie zu verwenden.
Jon Skeet

4
@ AndréChristofferAndersen: Es wäre besser zu verwendenauthorsList = authorsList.Where(x => x.FirstName != "Bob")
Jon Skeet

133

Es ist besser, List <T> .RemoveAll zu verwenden, um dies zu erreichen.

authorsList.RemoveAll((x) => x.firstname == "Bob");

8
@Reed Copsey: Der Lambda-Parameter in Ihrem Beispiel ist in Klammern eingeschlossen, dh (x). Gibt es dafür einen technischen Grund? Wird es als gute Praxis angesehen?
Matt Davis

24
Nein. Es ist mit> 1 Parameter erforderlich. Mit einem einzelnen Parameter ist dies optional, trägt jedoch zur Konsistenz bei.
Reed Copsey

48

Wenn Sie wirklich Elemente entfernen müssen, was ist dann mit Except ()?
Sie können basierend auf einer neuen Liste entfernen oder im laufenden Betrieb entfernen, indem Sie den Linq verschachteln.

var authorsList = new List<Author>()
{
    new Author{ Firstname = "Bob", Lastname = "Smith" },
    new Author{ Firstname = "Fred", Lastname = "Jones" },
    new Author{ Firstname = "Brian", Lastname = "Brains" },
    new Author{ Firstname = "Billy", Lastname = "TheKid" }
};

var authors = authorsList.Where(a => a.Firstname == "Bob");
authorsList = authorsList.Except(authors).ToList();
authorsList = authorsList.Except(authorsList.Where(a=>a.Firstname=="Billy")).ToList();

Except()ist der einzige Weg, um in der Mitte der LINQ-Anweisung zu gehen. IEnumerablehat noch Remove()nicht RemoveAll().
Jari Turkia

29

Sie können dies nicht mit Standard-LINQ-Operatoren tun, da LINQ Abfrage und keine Aktualisierungsunterstützung bietet.

Sie können jedoch eine neue Liste erstellen und die alte ersetzen.

var authorsList = GetAuthorList();

authorsList = authorsList.Where(a => a.FirstName != "Bob").ToList();

Oder Sie können alle Elemente authorsin einem zweiten Durchgang entfernen .

var authorsList = GetAuthorList();

var authors = authorsList.Where(a => a.FirstName == "Bob").ToList();

foreach (var author in authors)
{
    authorList.Remove(author);
}

12
RemoveAll()ist kein LINQ-Operator.
Daniel Brückner

Entschuldigen Sie. Sie sind 100% korrekt. Leider kann ich meine Ablehnung nicht rückgängig machen. Das tut mir leid.
Shai Cohen

Removeist auch eine List< T> -Methode, keine System.Linq.Enumerable- Methode.
DavidRR

@ Daniel, korrigiere mich, wenn ich falsch liege, können wir .ToList () von Where-Bedingung für die zweite Option vermeiden. Dh unten Code wird funktionieren. var autorsList = GetAuthorList (); var author = autorsList.Where (a => a.FirstName == "Bob"); foreach (var Autor in Autoren) {authorList.Remove (Autor); }
Sai

Ja, das wird funktionieren. Das Umwandeln in eine Liste ist nur erforderlich, wenn Sie eine Liste benötigen, um sie an eine Methode zu übergeben, oder wenn Sie später weitere Elemente hinzufügen oder entfernen möchten. Dies kann auch nützlich sein, wenn Sie die Sequenz mehrmals aufzählen müssen, weil Sie dann die potenziell teure where-Bedingung nur einmal bewerten müssen oder wenn sich das Ergebnis zwischen zwei Aufzählungen ändern kann, z. B. weil die Bedingung von der aktuellen Zeit abhängt. Wenn Sie es nur in einer Schleife verwenden möchten, müssen Sie das Ergebnis absolut nicht zuerst in einer Liste speichern.
Daniel Brückner

20

Einfache Lösung:

static void Main()
{
    List<string> myList = new List<string> { "Jason", "Bob", "Frank", "Bob" };
    myList.RemoveAll(x => x == "Bob");

    foreach (string s in myList)
    {
        //
    }
}

Wie entferne ich "Bob" und "Jason"? Ich meine mehrere in der Stringliste?
Neo

19

Ich habe mich gefragt, ob es einen Unterschied zwischen RemoveAllund Exceptund den Profis der Verwendung gibt HashSet, also habe ich eine schnelle Leistungsprüfung durchgeführt :)

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

namespace ListRemoveTest
{
    class Program
    {
        private static Random random = new Random( (int)DateTime.Now.Ticks );

        static void Main( string[] args )
        {
            Console.WriteLine( "Be patient, generating data..." );

            List<string> list = new List<string>();
            List<string> toRemove = new List<string>();
            for( int x=0; x < 1000000; x++ )
            {
                string randString = RandomString( random.Next( 100 ) );
                list.Add( randString );
                if( random.Next( 1000 ) == 0 )
                    toRemove.Insert( 0, randString );
            }

            List<string> l1 = new List<string>( list );
            List<string> l2 = new List<string>( list );
            List<string> l3 = new List<string>( list );
            List<string> l4 = new List<string>( list );

            Console.WriteLine( "Be patient, testing..." );

            Stopwatch sw1 = Stopwatch.StartNew();
            l1.RemoveAll( toRemove.Contains );
            sw1.Stop();

            Stopwatch sw2 = Stopwatch.StartNew();
            l2.RemoveAll( new HashSet<string>( toRemove ).Contains );
            sw2.Stop();

            Stopwatch sw3 = Stopwatch.StartNew();
            l3 = l3.Except( toRemove ).ToList();
            sw3.Stop();

            Stopwatch sw4 = Stopwatch.StartNew();
            l4 = l4.Except( new HashSet<string>( toRemove ) ).ToList();
            sw3.Stop();


            Console.WriteLine( "L1.Len = {0}, Time taken: {1}ms", l1.Count, sw1.Elapsed.TotalMilliseconds );
            Console.WriteLine( "L2.Len = {0}, Time taken: {1}ms", l1.Count, sw2.Elapsed.TotalMilliseconds );
            Console.WriteLine( "L3.Len = {0}, Time taken: {1}ms", l1.Count, sw3.Elapsed.TotalMilliseconds );
            Console.WriteLine( "L4.Len = {0}, Time taken: {1}ms", l1.Count, sw3.Elapsed.TotalMilliseconds );

            Console.ReadKey();
        }


        private static string RandomString( int size )
        {
            StringBuilder builder = new StringBuilder();
            char ch;
            for( int i = 0; i < size; i++ )
            {
                ch = Convert.ToChar( Convert.ToInt32( Math.Floor( 26 * random.NextDouble() + 65 ) ) );
                builder.Append( ch );
            }

            return builder.ToString();
        }
    }
}

Ergebnisse unten:

Be patient, generating data...
Be patient, testing...
L1.Len = 985263, Time taken: 13411.8648ms
L2.Len = 985263, Time taken: 76.4042ms
L3.Len = 985263, Time taken: 340.6933ms
L4.Len = 985263, Time taken: 340.6933ms

Wie wir sehen können, ist die beste Option in diesem Fall die Verwendung RemoveAll(HashSet)


Dieser Code: "l2.RemoveAll (neuer HashSet <string> (toRemove) .Contains);" sollte nicht kompiliert werden ... und wenn Ihre Tests korrekt sind, dann sind sie nur das, was Jon Skeet bereits vorgeschlagen hat.
Pascal

2
l2.RemoveAll( new HashSet<string>( toRemove ).Contains );kompiliert gut nur FYI
AzNjoE

9

Dies ist eine sehr alte Frage, aber ich habe einen wirklich einfachen Weg gefunden, dies zu tun:

authorsList = authorsList.Except(authors).ToList();

Beachten Sie, dass , da die Rückgabevariable authorsLista ist List<T>, die IEnumerable<T>zurück durch Except()muss ein umgewandelt werden List<T>.


7

Sie können auf zwei Arten entfernen

var output = from x in authorsList
             where x.firstname != "Bob"
             select x;

oder

var authors = from x in authorsList
              where x.firstname == "Bob"
              select x;

var output = from x in authorsList
             where !authors.Contains(x) 
             select x;

Ich hatte das gleiche Problem: Wenn Sie eine einfache Ausgabe basierend auf Ihrem Where-Zustand wünschen, ist die erste Lösung besser.


Wie kann ich nach "Bob" oder "Billy" suchen?
Si8

6

Sagen Sie, dass authorsToRemoveeine ist IEnumerable<T>, die die Elemente enthält , aus denen Sie entfernen möchten authorsList.

Dann ist hier ein weiterer sehr einfacher Weg, um die vom OP angeforderte Entfernungsaufgabe zu erfüllen:

authorsList.RemoveAll(authorsToRemove.Contains);

5

Ich denke, Sie könnten so etwas tun

    authorsList = (from a in authorsList
                  where !authors.Contains(a)
                  select a).ToList();

Obwohl ich denke, dass die bereits gegebenen Lösungen das Problem besser lesbar lösen.


4

Unten sehen Sie das Beispiel, um das Element aus der Liste zu entfernen.

 List<int> items = new List<int>() { 2, 2, 3, 4, 2, 7, 3,3,3};

 var result = items.Remove(2);//Remove the first ocurence of matched elements and returns boolean value
 var result1 = items.RemoveAll(lst => lst == 3);// Remove all the matched elements and returns count of removed element
 items.RemoveAt(3);//Removes the elements at the specified index

1

LINQ hat seinen Ursprung in der funktionalen Programmierung, bei der die Unveränderlichkeit von Objekten im Vordergrund steht. Daher bietet es keine integrierte Möglichkeit, die ursprüngliche Liste direkt zu aktualisieren.

Hinweis zur Unveränderlichkeit (entnommen aus einer anderen SO-Antwort):

Hier ist die Definition der Unveränderlichkeit aus Wikipedia .

Bei der objektorientierten und funktionalen Programmierung ist ein unveränderliches Objekt ein Objekt, dessen Status nach seiner Erstellung nicht mehr geändert werden kann.


0

Ich denke, Sie müssen nur die Elemente aus der Autorenliste einer neuen Liste zuweisen, um diesen Effekt zu erzielen.

//assume oldAuthor is the old list
Author newAuthorList = (select x from oldAuthor where x.firstname!="Bob" select x).ToList();
oldAuthor = newAuthorList;
newAuthorList = null;

0

Um den Code flüssig zu halten (wenn die Codeoptimierung nicht entscheidend ist) und Sie einige weitere Vorgänge in der Liste ausführen müssen:

authorsList = authorsList.Where(x => x.FirstName != "Bob").<do_some_further_Linq>;

oder

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.