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