Usando o LINQ para remover elementos de uma Lista <T>


655

Digamos que eu tenha uma consulta LINQ, como:

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

Dado que authorsListé do tipo List<Author>, como posso excluir os Authorelementos deauthorsList retornados pela consulta em authors?

Ou, dito de outra maneira, como posso excluir todos os nomes iguais a Bob de authorsList ?

Nota: Este é um exemplo simplificado para os propósitos da pergunta.

Respostas:


1139

Bem, seria mais fácil excluí-los em primeiro lugar:

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

No entanto, isso apenas alteraria o valor, em authorsListvez de remover os autores da coleção anterior. Como alternativa, você pode usar RemoveAll:

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

Se você realmente precisa fazer isso com base em outra coleção, eu usaria um HashSet, RemoveAll e Contains:

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

14
Qual o motivo do uso do HashSet para outra coleção?
— 123 456 789 0

54
@LeoLuis: Torna a Containsverificação rápida e garante que você avalie a sequência apenas uma vez.
— quer

2
@LeoLuis: Sim, a construção de um HashSet a partir de uma sequência o avalia apenas uma vez. Não sei o que você quer dizer com "conjunto de coleta fraco".
— precisa saber é o seguinte

2
@ AndréChristofferAndersen: O que você quer dizer com "desatualizado"? Ainda funciona. Se você tem um List<T>, é bom usá-lo.
— Jon Skeet

4
@ AndréChristofferAndersen: Seria melhor usarauthorsList = authorsList.Where(x => x.FirstName != "Bob")
— Jon Skeet

133

Seria melhor usar List <T> .RemoveAll para fazer isso.

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

8
@ Copeedeed: O parâmetro lambda no seu exemplo está entre parênteses, ou seja, (x). Existe uma razão técnica para isso? É considerado uma boa prática?
— Matt Davis

24
Não. É necessário com> 1 parâmetro. Com um único parâmetro, é opcional, mas ajuda a manter a consistência.
— Reed Copsey

48

Se você realmente precisa remover itens, o que acontece com Except ()?
Você pode remover com base em uma nova lista ou remover rapidamente, aninhando o 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();

Except()é o único caminho a percorrer no meio da instrução LINQ. IEnumerablenão tem Remove()nem RemoveAll().
— precisa saber é o seguinte

29

Você não pode fazer isso com operadores LINQ padrão porque o LINQ fornece consulta, não atualização de suporte.

Mas você pode gerar uma nova lista e substituir a antiga.

var authorsList = GetAuthorList();

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

Ou você pode remover todos os itens authorsem uma segunda passagem.

var authorsList = GetAuthorList();

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

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

12
RemoveAll()não é um operador LINQ.
— Daniel Brückner

Me desculpe. Você está 100% correto. Infelizmente, não consigo reverter meu voto negativo. Me desculpe por isso.
— Shai Cohen

Removetambém é um método List< T>, não um método System.Linq.Enumerable .
— DavidRR

@ Daniel, Corrija-me se estiver errado, podemos evitar .ToList () de Where condtion para a segunda opção. Ou seja, o código abaixo funcionará. var autoresList = GetAuthorList (); var autores = autoresList.Where (a => a.PrimeiroNome == "Bob"); foreach (var author nos autores) {authorList.Remove (author); }
— Sai

Sim, isso vai funcionar. Só é necessário transformá-lo em uma lista se você precisar de uma lista para transmiti-la a algum método ou se desejar adicionar ou remover mais itens posteriormente. Também pode ser útil se você precisar enumerar a sequência várias vezes, pois precisará avaliar apenas a condição where potencialmente cara uma vez ou se o resultado pode mudar entre duas enumerações, por exemplo, porque a condição depende do horário atual. Se você deseja usá-lo apenas em um loop, não há absolutamente nenhuma necessidade de primeiro armazenar o resultado em uma lista.
— Daniel Brückner

20

Solução simples:

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

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

como remover "Bob" e "Jason" Quero dizer múltiplo na lista de cadeias de caracteres?
— Neo

19

Fiquei me perguntando, se há alguma diferença entre RemoveAlle Excepte os profissionais de usar HashSet, então eu fiz uma rápida verificação de desempenho :)

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

Resultados abaixo:

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

Como podemos ver, a melhor opção nesse caso é usar RemoveAll(HashSet)


Este código: "l2.RemoveAll (new HashSet <string> (toRemove). Contém);" não deve ser compilado ... e se seus testes estiverem corretos, apenas o segundo que Jon Skeet já sugeriu.
— Pascal

2
l2.RemoveAll( new HashSet<string>( toRemove ).Contains );compila bem apenas FYI
— AzNjoE

9

Esta é uma pergunta muito antiga, mas achei uma maneira muito simples de fazer isso:

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

Observe que, como a variável de retorno authorsListé a List<T>, a IEnumerable<T>retornada por Except()deve ser convertida em a List<T>.


7

Você pode remover de duas maneiras

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;

Eu tive o mesmo problema, se você deseja uma saída simples com base em sua condição where, a primeira solução é melhor.


Como posso verificar "Bob" ou "Billy"?
— Si8

6

Digamos que authorsToRemoveseja um IEnumerable<T>que contenha os elementos dos quais você deseja remover authorsList.

Aqui está outra maneira muito simples de realizar a tarefa de remoção solicitada pelo OP:

authorsList.RemoveAll(authorsToRemove.Contains);

5

Eu acho que você poderia fazer algo assim

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

Embora eu ache que as soluções já fornecidas resolvam o problema de maneira mais legível.


4

Abaixo está o exemplo para remover o elemento da lista.

 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

O LINQ tem suas origens na programação funcional, que enfatiza a imutabilidade de objetos, portanto, não fornece uma maneira integrada de atualizar a lista original no local.

Nota sobre imutabilidade (extraída de outra resposta do SO):

Aqui está a definição de imutabilidade da Wikipedia .

Na programação funcional e orientada a objetos, um objeto imutável é um objeto cujo estado não pode ser modificado após a criação.


0

Eu acho que você só precisa atribuir os itens da lista de autores a uma nova lista para ter esse efeito.

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

0

Para manter o código fluente (se a otimização do código não for crucial) e você precisar executar algumas operações adicionais na lista:

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

ou

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.