web-dev-qa-db-fra.com

Utilisation de LINQ pour supprimer des éléments d’une liste <T>

Dites que j'ai une requête LINQ telle que:

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

Étant donné que authorsList est de type List<Author>, comment puis-je supprimer les éléments Author de authorsList qui sont renvoyés par la requête dans authors?

Ou, autrement dit, comment puis-je supprimer tous les prénoms équivalents à Bob de authorsList?

Remarque: Ceci est un exemple simplifié aux fins de la question.

574
TK.

Eh bien, il serait plus facile de les exclure en premier lieu:

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

Cependant, cela ne ferait que modifier la valeur de authorsList au lieu de supprimer les auteurs de la collection précédente. Vous pouvez aussi utiliser RemoveAll :

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

Si vous avez vraiment besoin de le faire sur la base d'une autre collection, j'utiliserais un hachage, une suppression et un contenu:

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

Il serait préférable d'utiliser List <T> .RemoveAll pour accomplir cela.

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

Si vous avez vraiment besoin de supprimer des éléments, qu’en est-il de Except ()?
Vous pouvez supprimer en fonction d'une nouvelle liste, ou supprimer à la volée en imbriquant le Linq.

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();
38
BlueChippy

Vous ne pouvez pas faire cela avec des opérateurs LINQ standard, car LINQ fournit une prise en charge des requêtes et non des mises à jour.

Mais vous pouvez générer une nouvelle liste et remplacer l'ancienne.

var authorsList = GetAuthorList();

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

Ou vous pouvez supprimer tous les éléments de authors lors d'un deuxième passage.

var authorsList = GetAuthorList();

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

foreach (var author in authors)
{
    authorList.Remove(author);
}
24
Daniel Brückner

Solution simple:

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

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

Je me promenais, s'il y a une différence entre RemoveAll et Except et les pros de l'utilisation de HashSet, alors j'ai fait une vérification rapide des performances :)

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();
        }
    }
}

Résultats ci-dessous:

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

Comme on peut le constater, la meilleure option dans ce cas consiste à utiliserRemoveAll (HashSet)

14
suszig

C'est une très vieille question, mais j'ai trouvé un moyen très simple de le faire: 

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

Notez que, puisque la variable de retour authorsList est un List<T>, le IEnumerable<T> renvoyé par Except() doit être converti en un List<T>.

8
Carlos Martinez T

Vous pouvez enlever de deux manières

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

ou

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

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

J'ai eu le même problème, si vous voulez une sortie simple basée sur votre condition where, la première solution est préférable.

7
AsifQadri

Dites que authorsToRemove est un IEnumerable<T> qui contient les éléments que vous souhaitez supprimer de authorsList.

Ensuite, voici un autre moyen très simple d'accomplir la tâche de suppression demandée par l'OP:

authorsList.RemoveAll(authorsToRemove.Contains);
6
atconway

Je pense que tu pourrais faire quelque chose comme ça

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

Bien que je pense que les solutions déjà données résolvent le problème de manière plus lisible.

5
ebrown

Ci-dessous, l'exemple pour supprimer l'élément de la liste.

 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
4
Sheo Dayal Singh

je pense qu'il vous suffit d'assigner les éléments de la liste des auteurs à une nouvelle liste pour que cela prenne effet.

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

Pour que le code soit fluide (si l'optimisation du code n'est pas cruciale) et que vous deviez effectuer d'autres opérations dans la liste:

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

ou

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
0
Zbigniew Wiadro

LINQ tire ses origines de la programmation fonctionnelle, qui met l'accent sur l'immuabilité des objets, de sorte qu'elle ne fournit pas de moyen intégré de mettre à jour la liste d'origine sur place.

Note sur l'immutabilité (extraite d'une autre réponse SO):

Voici la définition de l'immuabilité de Wikipedia (lien)

"Dans la programmation fonctionnelle et orientée objet, un objet immuable est un objet dont l'état ne peut pas être modifié après sa création."

0
Samuel Jack