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.

TK.
fonte

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));
Jon Skeet
fonte
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");
Reed Copsey
fonte
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();
BlueChippy
fonte
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);
}
Daniel Brückner
fonte
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)
    {
        //
    }
}
CodeLikeBeaker
fonte
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)

suszig
fonte
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>.

Carlos Martinez T
fonte
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.

AsifQadri
fonte
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);
atconway
fonte
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.

ebrown
fonte
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
Sheo Dayal Singh
fonte
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.

Samuel Jack
fonte
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;
aj go
fonte
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>;
Zbigniew Wiadro
fonte