768

Say that I have LINQ query such as:

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

Given that authorsList is of type List<Author>, how can I delete the Author elements from authorsList that are returned by the query into authors?

Or, put another way, how can I delete all of the firstname's equalling Bob from authorsList?

Note: This is a simplified example for the purposes of the question.

John M
  • 14,338
  • 29
  • 91
  • 143
TK.
  • 46,577
  • 46
  • 119
  • 147

14 Answers14

1316

Well, it would be easier to exclude them in the first place:

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

However, that would just change the value of authorsList instead of removing the authors from the previous collection. Alternatively, you can use RemoveAll:

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

If you really need to do it based on another collection, I'd use a HashSet, RemoveAll and Contains:

var setToRemove = new HashSet<Author>(authors);
authorsList.RemoveAll(x => setToRemove.Contains(x));
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 17
    What's the reason for using HashSet for another collection? – 123 456 789 0 Aug 07 '12 at 01:51
  • 67
    @LeoLuis: It makes the `Contains` check fast, and ensures you only evaluate the sequence once. – Jon Skeet Aug 07 '12 at 01:57
  • That's nice to know. Does it really have to be a HashSet though, will a weak collection set be fine? Lastly, are you sure that it only evaluates the sequence once? – 123 456 789 0 Aug 07 '12 at 03:19
  • 3
    @LeoLuis: Yes, building a HashSet from a sequence only evaluates it once. Not sure what you mean by "weak collection set". – Jon Skeet Aug 07 '12 at 03:21
  • 1
    @JonSkeet About the RemoveAll. Do you know if items are removed at the moment of detection or all at once at the end? I am looking for a way to do the first one. – Odys Feb 14 '13 at 18:15
  • @odyodyodys: I'd *expect* it to be the latter, but I don't know for sure. – Jon Skeet Feb 14 '13 at 18:54
  • I think what LeoLuis means by "weak collection set" is a non-generic collection. – Andreas Grech Mar 13 '13 at 11:04
  • 1
    @AndreasGrech: Unfortunately we'll probably never know. I can't see why a non-generic collection would be desirable here. – Jon Skeet Mar 13 '13 at 11:06
  • Is RemoveAll outdated? I found the equivalent RemoveWhere in the HashSet<> collection. – André C. Andersen Apr 28 '13 at 12:05
  • 3
    @AndréChristofferAndersen: What do you mean by "outdated"? It still works. If you've got a `List`, it's fine to use it. – Jon Skeet Apr 28 '13 at 12:08
  • @JonSkeet You're right, I was looking at `IEnumerable` not `List`. For `IEnumerable` it seems like you could use `authorsList = authorsList.Except(authorsList.Where(x => x.FirstName == "Bob"))` instead. – André C. Andersen May 26 '13 at 10:13
  • 5
    @AndréChristofferAndersen: It would be better to use `authorsList = authorsList.Where(x => x.FirstName != "Bob")` – Jon Skeet May 26 '13 at 11:30
  • Doesn't it require Author to implement IEquality Comparer for the Contains to work right? – bahramzy Jun 11 '18 at 11:51
  • @bahramzy: Or just regular equality. Or if you're happy with the default equality. – Jon Skeet Jun 11 '18 at 21:34
  • The HashSet solution really helped me. I have a list of models that all inherit from an interface called IModel. I cannot call directly to the properties of the models that inherit from IModel. By creating a HashSet from the models that I want to remove and following what Jon Skeet said, I can remove these specific models from this list of IModel. – shinyshark Nov 08 '21 at 09:52
164

It'd be better to use List<T>.RemoveAll to accomplish this.

authorsList.RemoveAll((x) => x.firstname == "Bob");
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 9
    @Reed Copsey: The lambda parameter in your example is enclosed in parentheses, i.e., (x). Is there a technical reason for this? Is it considered good practice? – Matt Davis Sep 10 '09 at 05:56
  • 31
    No. It's required with >1 parameter. With a single parameter, it's optional, but it does help keep consistency. – Reed Copsey Sep 10 '09 at 11:41
56

If you really need to remove items then what about Except()?
You can remove based on a new list, or remove on-the-fly by nesting the 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();
abatishchev
  • 98,240
  • 88
  • 296
  • 433
BlueChippy
  • 5,935
  • 16
  • 81
  • 131
  • 1
    `Except()` is the only way to go in middle of LINQ-statement. `IEnumerable` doesn't have `Remove()` nor `RemoveAll()`. – Jari Turkia Apr 11 '19 at 06:43
34

You cannot do this with standard LINQ operators because LINQ provides query, not update support.

But you can generate a new list and replace the old one.

var authorsList = GetAuthorList();

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

Or you could remove all items in authors in a second pass.

var authorsList = GetAuthorList();

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

foreach (var author in authors)
{
    authorList.Remove(author);
}
Daniel Brückner
  • 59,031
  • 16
  • 99
  • 143
  • 19
    `RemoveAll()` is not a LINQ operator. – Daniel Brückner Apr 04 '14 at 17:50
  • 1
    My apologies. You are 100% correct. Unfortunately, I can't seem to reverse my downvote. Sorry about that. – Shai Cohen Apr 04 '14 at 21:16
  • 1
    [`Remove`](http://msdn.microsoft.com/en-us/library/cd666k3e%28v=vs.110%29.aspx) is also a `List`<`T`> method, not a [System.Linq.Enumerable](http://msdn.microsoft.com/en-us/library/system.linq.enumerable%28v=vs.110%29.aspx) method. – DavidRR Jul 29 '14 at 01:42
  • 1
    @Daniel, Correct me if I'm wrong we can avoid .ToList() from Where condtion for the second option. I.e. below code will work. var authorsList = GetAuthorList(); var authors = authorsList.Where(a => a.FirstName == "Bob"); foreach (var author in authors) { authorList.Remove(author); } – Sai Sep 04 '14 at 15:54
  • 1
    Yes, this will work. Turning it into a list is only required if you need a list to pass it to some method or if you want to add or remove more stuff later. It may also be useful if you have to enumerate the sequence multiple times because then you only have to evaluate the potentially expensive where condition once or if the result may change between two enumeration, for example because the condition depends on the current time. If you only want to use it in one loop there is absolutely no need to first store the result in a list. – Daniel Brückner Sep 04 '14 at 19:06
  • 1
    not totally true. If you are pulling your data from a model, you may need to make a list out of it if you have any notmapped fields. You can't access them otherwise to do linq queries on. – John Lord Jul 19 '19 at 04:25
23

Simple solution:

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

    foreach (string s in myList)
    {
        //
    }
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
CodeLikeBeaker
  • 20,682
  • 14
  • 79
  • 108
21

I was wondering, if there is any difference between RemoveAll and Except and the pros of using HashSet, so I have done quick performance check :)

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

Results below:

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

As we can see, best option in that case is to use RemoveAll(HashSet)

d219
  • 2,707
  • 5
  • 31
  • 36
suszig
  • 310
  • 2
  • 4
  • This code: "l2.RemoveAll( new HashSet( toRemove ).Contains );" should not compile... and if your tests are correct then they just second what Jon Skeet already suggested. – Pascal Jul 24 '14 at 18:37
  • 2
    `l2.RemoveAll( new HashSet( toRemove ).Contains );` compiles fine just FYI – AzNjoE Mar 01 '16 at 17:44
11

This is a very old question, but I found a really simple way to do this:

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

Note that since the return variable authorsList is a List<T>, the IEnumerable<T> returned by Except() must be converted to a List<T>.

DavidRR
  • 18,291
  • 25
  • 109
  • 191
Carlos Martinez T
  • 6,458
  • 1
  • 34
  • 40
8

You can remove in two ways

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

or

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

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

I had same issue, if you want simple output based on your where condition , then first solution is better.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
AsifQadri
  • 2,388
  • 1
  • 20
  • 30
7

Say that authorsToRemove is an IEnumerable<T> that contains the elements you want to remove from authorsList.

Then here is another very simple way to accomplish the removal task asked by the OP:

authorsList.RemoveAll(authorsToRemove.Contains);
DavidRR
  • 18,291
  • 25
  • 109
  • 191
atconway
  • 20,624
  • 30
  • 159
  • 229
7

LINQ has its origins in functional programming, which emphasises immutability of objects, so it doesn't provide a built-in way to update the original list in-place.

Note on immutability (taken from another SO answer):

Here is the definition of immutability from Wikipedia.

In object-oriented and functional programming, an immutable object is an object whose state cannot be modified after it is created.

d219
  • 2,707
  • 5
  • 31
  • 36
Samuel Jack
  • 32,712
  • 16
  • 118
  • 155
6

Below is the example to remove the element from the list.

 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
  • 1,591
  • 19
  • 11
5

I think you could do something like this

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

Although I think the solutions already given solve the problem in a more readable way.

ebrown
  • 811
  • 1
  • 8
  • 13
1

i think you just have to assign the items from Author list to a new list to take that effect.

//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
  • 637
  • 2
  • 10
  • 27
0

To keep the code fluent (if code optimisation is not crucial) and you would need to do some further operations on the list:

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

or

authorsList = authorsList.Where(x => !setToRemove.Contains(x)).<do_some_further_Linq>;
Chris W
  • 1,562
  • 20
  • 27