List<T>.RemoveAll() эффективность/оптимизация компилятора

Что касается эффективности, кто-нибудь знает, достаточно ли умен компилятор, чтобы не создавать массив, содержащий 1, 3, 5 для каждой итерации цикла в следующем коде?

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

Я предпочитаю это для удобочитаемости, но не ради производительности.

Ответы

Ответ 1

Ответ - нет, это не оптимизирует распределение массива.

По сути, каждый раз, когда вызывается предикат, он проверяет класс, сгенерированный компилятором, и инициализирует новый массив для вызова Contains (как вы можете видеть здесь)

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

Ответ 2

Как уже писал @Michael Randall, похоже, что это невозможно.

Я согласен, что ваш сомнительный код хорошо читается, имея список в методе RemoveAll. Но чтобы иметь экземпляр только один раз, у меня есть три идеи сделать это:

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

Это на самом деле ваше, с небольшим количеством необходимости внешней переменной.

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

Это на самом деле довольно хорошее решение с использованием Linq.

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


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

Это то, что я бы сделал. Почти во всем моем коде у меня есть метод Extension "Iterate", чтобы избежать необходимости в foreach. А также, я не хочу все время "списывать", чтобы сделать .ForEach(..)

static class Extensions
{
    public static void Iterate<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
    {
        foreach (var item in source)
        {
            action.Invoke(item);
        }
    }
}

Ответ 3

Поскольку компилятор не настолько умен, мы должны его перехитрить.

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