Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List<T>.RemoveAll() efficiency / compiler optimisation

Regarding efficiency, does anyone know if the compiler is clever enough to not create the array containing 1, 3, 5 for each iteration of the loop in the following code?

var foo = new List<int> { 1, 2, 3, 4, 5 };
foo.RemoveAll(i => new[] { 1, 3, 5 }.Contains(i));

I prefer it for readability, but not at the sake of performance.

like image 340
maxp Avatar asked Apr 05 '19 08:04

maxp


3 Answers

The answer is no it doesn't optimize out the allocation of the array

Basically, every time the predicate is called, it checks against the compiler generated class and initializes a new array to call the Contains (as you can see here)

private sealed class <>c
{
    public static readonly <>c <>9 = new <>c();

    public static Predicate<int> <>9__0_0;

    internal bool <M>b__0_0(int i)
    {
        // bam!
        int[] obj = new int[3];
        RuntimeHelpers.InitializeArray(obj, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
        return Enumerable.Contains(obj, i);
    }
}
like image 130
TheGeneral Avatar answered Oct 27 '22 10:10

TheGeneral


As @Michael Randall already wrote, it looks like it is not possible.

I agree, that your questioned code is nicely readable, having the list in the RemoveAll method. But to have the instance only once, I have three ideas of doing it:

int[] a = null;
foo.RemoveAll(i => (a ?? (a = new[] { 1, 3, 5 })).Contains(i));

This is actually yours, with little un-beatuness of needing an external variable.

 foo = foo.Except(new[] { 1, 3, 5 }).ToList();

That's actually pretty nice solution using Linq.

 new List<int>{1, 3, 5}.ForEach(x => foo.Remove(x));


 new[] {1, 3, 5}.Iterate(x => foo.Remove(x));

This is something I'd do. In neary all of my code I have my Extension method "Iterate" to avoid the need of foreach. And also, i dont want to "toList" everything all the time to make a .ForEach(..)

static class Extensions
{
    public static void Iterate<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
    {
        foreach (var item in source)
        {
            action.Invoke(item);
        }
    }
}
like image 25
Malior Avatar answered Oct 27 '22 10:10

Malior


Since the compiler is not that smart, we must outsmart him.

var foo = new List<int> { 1, 2, 3, 4, 5 };
var bar = new HashSet<int>() { 1, 3, 5 };
foo.RemoveAll(i => bar.Contains(i));
like image 26
Theodor Zoulias Avatar answered Oct 27 '22 11:10

Theodor Zoulias