Как лучше всего подойти к удалению элементов из коллекции в C#, если элемент известен, но не индексирован. Это один из способов сделать это, но в лучшем случае он кажется неэлегантным.
//Remove the existing role assignment for the user.
int cnt = 0;
int assToDelete = 0;
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name == shortName)
{
assToDelete = cnt;
}
cnt++;
}
workspace.RoleAssignments.Remove(assToDelete);
Что мне действительно хотелось бы сделать, так это найти удаляемый элемент по свойству (в данном случае по имени) без перебора всей коллекции и использования 2 дополнительных переменных.
Добавьте оператор break при успешном поиске, если вы планируете это сделать, хотя использование словаря, вероятно, в любом случае лучше, если вы всегда ищите вещи по имени члена.
Я думаю, вы имели в виду RemoveAt () в своем фрагменте кода, поскольку вы передаете index. Как только элемент известен, вы можете напрямую вызвать Remove ().
Этот вопрос следует прояснить. С какой структурой .Net имеют дело ответы? Мы говорим о List <T> или какой-либо другой структуре, реализующей IList <T> - ее, вероятно, следует переименовать в «Как лучше всего удалить элементы из List <T> в .net 3.0?»





Если вы хотите получить доступ к членам коллекции по одному из их свойств, вы можете вместо этого использовать Dictionary<T> или KeyedCollection<T>. Таким образом, вам не нужно искать предмет, который вы ищете.
В противном случае вы могли бы хотя бы так:
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name == shortName)
{
workspace.RoleAssignments.Remove(spAssignment);
break;
}
}
Это вызовет исключение, потому что вы изменяете коллекцию по мере ее использования ...
Нет, это не так. Это потому, что после удаления предмета есть перерыв.
Просто на случай, если кто-то прочитает это и применит к ситуации, когда вы удаляете несколько элементов, сохраните несколько индексов в массив и используйте отдельный цикл for, который возвращается назад через массив удаления для удаления элементов.
Или просто отфильтруйте, используя workspace.RoleAssignments = workspace.RoleAssignments.Where (x => x.Member.Name! = ShortName) .ToList () ;. Но я предпочитаю ответ Лиама, когда это возможно.
Если RoleAssignments - это List<T>, вы можете использовать следующий код.
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
Я думаю, что только RemoveAll имеет параметр функции. По крайней мере, я не могу его собрать с помощью Remove.
Да, это RemoveAll. Я на самом деле потратил время, чтобы проверить это, убедился, что это RemoveAll, и все еще вставил в Remove. Жаль, что у stackoverflow нет встроенного компилятора :)
Это создает новый список, если я не ошибаюсь.
AFAIK, это удаление на месте.
Какой тип коллекции? Если это List, вы можете использовать полезный «RemoveAll»:
int cnt = workspace.RoleAssignments
.RemoveAll(spa => spa.Member.Name == shortName)
(Это работает в .NET 2.0. Конечно, если у вас нет более новой версии компилятора, вам придется использовать "delegate (SPRoleAssignment spa) {return spa.Member.Name == shortName;}" вместо красивого лямбда-синтаксис.)
Другой подход, если это не список, но все же ICollection:
var toRemove = workspace.RoleAssignments
.FirstOrDefault(spa => spa.Member.Name == shortName)
if (toRemove != null) workspace.RoleAssignments.Remove(toRemove);
Для этого требуются методы расширения Enumerable. (Вы можете скопировать Mono, если вы застряли на .NET 2.0). Если это какая-то настраиваемая коллекция, которая не может принимать элемент, но ДОЛЖНА принимать индекс, некоторые другие методы Enumerable, такие как Select, передают целочисленный индекс за вас.
Для простой структуры списка наиболее эффективным способом является использование реализации Predicate RemoveAll.
Например.
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
Причины:
Если вы застряли в реализации этого в эпоху до C# 3.0. У вас есть 2 варианта.
Например.
List<int> list2 = new List<int>() ;
foreach (int i in GetList())
{
if (!(i % 2 == 0))
{
list2.Add(i);
}
}
list2 = list2;
Или же
Если вы действительно часто удаляете что-то из списка, возможно, для этой цели лучше подходит другая структура, такая как Хеш-таблица (.net 1.1), Словарь (.net 2.0) или HashSet (.net 3.5).
@smaclell спросил, почему обратная итерация более эффективна, в комментарии к @ sambo99.
Иногда так эффективнее. Предположим, у вас есть список людей, и вы хотите удалить или отфильтровать всех клиентов с кредитным рейтингом <1000;
У нас есть следующие данные
"Bob" 999
"Mary" 999
"Ted" 1000
Если бы мы пошли дальше, у нас вскоре были бы проблемы
for( int idx = 0; idx < list.Count ; idx++ )
{
if ( list[idx].Rating < 1000 )
{
list.RemoveAt(idx); // whoops!
}
}
При idx = 0 мы удаляем Bob, который затем сдвигает все оставшиеся элементы влево. В следующий раз через цикл idx = 1, но
list [1] теперь Ted вместо Mary. Мы по ошибке пропускаем Mary. Мы могли бы использовать цикл while и ввести больше переменных.
Или мы просто перебираем в обратном порядке:
for (int idx = list.Count-1; idx >= 0; idx--)
{
if (list[idx].Rating < 1000)
{
list.RemoveAt(idx);
}
}
Все индексы слева от удаленного элемента остаются прежними, поэтому вы не пропустите ни одного элемента.
Тот же принцип применяется, если вам дан список индексов, которые нужно удалить из массива. Чтобы все было прямо, вам нужно отсортировать список, а затем удалить элементы от самого высокого индекса до самого низкого.
Теперь вы можете просто использовать Linq и прямо заявлять, что вы делаете.
list.RemoveAll(o => o.Rating < 1000);
В этом случае удаление одного элемента не более эффективно итерация вперед или назад. Вы также можете использовать для этого Linq.
int removeIndex = list.FindIndex(o => o.Name == "Ted");
if ( removeIndex != -1 )
{
list.RemoveAt(removeIndex);
}
Для простого List <T>, если вам нужно удалить более 1 элемента, обратный цикл for ВСЕГДА является наиболее эффективным способом сделать это. Это, безусловно, более эффективно, чем копирование данных в список listToRemove. Готов поспорить, реализация Linq использует тот же трюк.
@ sambo99 Я полностью согласен и попытаюсь расширить ваш ответ, объяснив, почему прямая итерация не всегда работает. Также я указываю, что при отсутствии дополнительной информации обратная итерация не более и не менее эффективна, если вы удаляете не более 1 записи. O (n) так же хорош, как и со списками.
Ерп. Я исправляю это сейчас ... List <T> имеет очень неприятную реализацию RemoveAt, самый эффективный способ - это копирование нужных нам данных из списка
Представьте, что вы хотите, чтобы ваши данные были удалены с левого края (скажем, вы хотите удалить большинство левых элементов, но вы не знаете, сколько). Очевидно, обратное решение - наихудший способ сделать это .... В вашем примере вместо WHOOPSIE! вы должны были написать list.RemoveAt (idx--); Alrigth?
@ maxima120 вы правы, но если вы прочитали то, что я сказал, я пытался понять иллюстрировать, почему обратная итерация может быть более эффективной самый в то время. Я не утверждаю, что это универсальное применение. В настоящее время я бы не стал перебирать себя и предпочел бы версию LINQ.
На самом деле это не LINQ - это просто методы List.
Есть другой подход, который вы можете использовать в зависимости от того, как вы используете свою коллекцию. Если вы загружаете задания один раз (например, при запуске приложения), вы можете «на лету» преобразовать коллекцию в хеш-таблицу, где:
shortname => SPRoleAssignment
Если вы это сделаете, то когда вы захотите удалить элемент по короткому имени, все, что вам нужно сделать, это удалить элемент из хеш-таблицы по ключу.
К сожалению, если вы часто загружаете эти SPRoleAssignments, это, очевидно, не будет более экономичным с точки зрения времени. Предложения других людей об использовании Linq были бы хороши, если вы используете новую версию .NET Framework, но в противном случае вам придется придерживаться того метода, который вы используете.
Здесь много хороших отзывов; Мне особенно нравятся лямбда-выражения ... очень чистые. Однако я был упущен, не указав тип Коллекции. Это SPRoleAssignmentCollection (от MOSS), в котором есть только Remove (int) и Remove (SPPrincipal), но не удобный RemoveAll (). Итак, я остановился на этом, если нет лучшего предложения.
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name != shortName) continue;
workspace.RoleAssignments.Remove((SPPrincipal)spAssignment.Member);
break;
}
Вам все еще лучше с моим другим предложением использовать FirstOrDefault. things.Remove (things.FirstOrDefault (t => t.Whatever == true))
Вот довольно хороший способ сделать это
http://support.microsoft.com/kb/555972
System.Collections.ArrayList arr = new System.Collections.ArrayList();
arr.Add("1");
arr.Add("2");
arr.Add("3");
/*This throws an exception
foreach (string s in arr)
{
arr.Remove(s);
}
*/
//where as this works correctly
Console.WriteLine(arr.Count);
foreach (string s in new System.Collections.ArrayList(arr))
{
arr.Remove(s);
}
Console.WriteLine(arr.Count);
Console.ReadKey();
Это мое общее решение
public static IEnumerable<T> Remove<T>(this IEnumerable<T> items, Func<T, bool> match)
{
var list = items.ToList();
for (int idx = 0; idx < list.Count(); idx++)
{
if (match(list[idx]))
{
list.RemoveAt(idx);
idx--; // the list is 1 item shorter
}
}
return list.AsEnumerable();
}
Было бы намного проще, если бы методы расширения поддерживали передачу по ссылке! использование:
var result = string[]{"mike", "john", "ali"}
result = result.Remove(x => x.Username == "mike").ToArray();
Assert.IsTrue(result.Length == 2);
Обновлено: гарантирует, что цикл списка остается действительным даже при удалении элементов путем уменьшения индекса (idx).
Это не рабочее решение! Вы пропускаете пункты как в конце, так и в середине списка! Попробуйте например var result = string[]{"mike", "mike", "john", "ali", "mike"}, удалится только первый "Майк".
@flindeberg Интересно, что вы имеете в виду под «пропуском», поскольку цикл проходит по всем элементам ?! ... но я попробую ваши данные
Когда вы увеличиваете свой индекс (idx) после удаления элемента, вы пропускаете «следующий» элемент. Пример: если вы удалите элемент в позиция 4, позиция 5 будет «перемещен» в позиция 4, позиция 6-> позиция 5, (n) -> (n - 1) и т.д., а затем вы увеличите свой индекс до 5, где вы найдете (старый 6, новый 5), и вы пропустили (старые 5, новые 4). Это проясняет ситуацию?
Хорошо, я обновил свой ответ ... спасибо, что заметили это;)
Чтобы сделать это во время цикла по коллекции и не получать исключение модификации коллекции, это подход, который я использовал в прошлом (обратите внимание на .ToList () в конце исходной коллекции, это создает другую коллекцию в памяти , то вы можете изменить существующую коллекцию)
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList())
{
if (spAssignment.Member.Name == shortName)
{
workspace.RoleAssignments.Remove(spAssignment);
}
}
Если это ICollection, то у вас не будет метода RemoveAll. Вот метод расширения, который сделает это:
public static void RemoveAll<T>(this ICollection<T> source,
Func<T, bool> predicate)
{
if (source == null)
throw new ArgumentNullException("source", "source is null.");
if (predicate == null)
throw new ArgumentNullException("predicate", "predicate is null.");
source.Where(predicate).ToList().ForEach(e => source.Remove(e));
}
По материалам: http://phejndorf.wordpress.com/2011/03/09/a-removeall-extension-for-the-collection-class/
Если требуется удалить более одного элемента из коллекции, это лучший способ сделать это.
Так же, как и с точки зрения коллекции словарей, я сделал это.
Dictionary<string, bool> sourceDict = new Dictionary<string, bool>();
sourceDict.Add("Sai", true);
sourceDict.Add("Sri", false);
sourceDict.Add("SaiSri", true);
sourceDict.Add("SaiSriMahi", true);
var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false);
foreach (var item in itemsToDelete)
{
sourceDict.Remove(item.Key);
}
Примечание: Приведенный выше код не будет работать в профиле клиента .Net (3.5 и 4.5), также некоторые зрители упомянули, что это Неудача для них в .Net4.0, а также не уверен, какие настройки вызывают проблему.
Поэтому замените приведенный ниже код (.ToList ()) для оператора Where, чтобы избежать этой ошибки. «Коллекция была изменена; операция перечисления может не выполняться ».
var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false).ToList();
Согласно MSDN. Начиная с .Net4.5, профиль клиента больше не поддерживается. http://msdn.microsoft.com/en-us/library/cc656912(v=vs.110).aspx
Сначала сохраните свои элементы, а затем удалите их.
var itemsToDelete = Items.Where(x => !!!your condition!!!).ToArray();
for (int i = 0; i < itemsToDelete.Length; ++i)
Items.Remove(itemsToDelete[i]);
Вам необходимо переопределить GetHashCode() в своем классе Item.
Лучший способ сделать это - использовать linq.
Пример класса:
public class Product
{
public string Name { get; set; }
public string Price { get; set; }
}
Запрос Linq:
var subCollection = collection1.RemoveAll(w => collection2.Any(q => q.Name == w.Name));
Этот запрос удалит все элементы из collection1, если Name соответствует любому элементу Name из collection2.
Не забудьте использовать: using System.Linq;
Если у вас есть List<T>, то List<T>.RemoveAll - ваш лучший выбор. Более действенного и быть не может. Внутри он перемещает массив за один раз, не говоря уже о том, что это O (N).
Если у вас есть только IList<T> или ICollection<T>, у вас есть примерно три варианта:
public static void RemoveAll<T>(this IList<T> ilist, Predicate<T> predicate) // O(N^2)
{
for (var index = ilist.Count - 1; index >= 0; index--)
{
var item = ilist[index];
if (predicate(item))
{
ilist.RemoveAt(index);
}
}
}
или же
public static void RemoveAll<T>(this ICollection<T> icollection, Predicate<T> predicate) // O(N)
{
var nonMatchingItems = new List<T>();
// Move all the items that do not match to another collection.
foreach (var item in icollection)
{
if (!predicate(item))
{
nonMatchingItems.Add(item);
}
}
// Clear the collection and then copy back the non-matched items.
icollection.Clear();
foreach (var item in nonMatchingItems)
{
icollection.Add(item);
}
}
или же
public static void RemoveAll<T>(this ICollection<T> icollection, Func<T, bool> predicate) // O(N^2)
{
foreach (var item in icollection.Where(predicate).ToList())
{
icollection.Remove(item);
}
}
Выберите 1 или 2.
1 занимает меньше памяти и быстрее, если вам нужно выполнить меньшее количество удалений (т.е. предикат в большинстве случаев ложен).
2 будет быстрее, если вам нужно выполнить больше удалений.
3 - самый чистый код, но ИМО работает плохо. Опять же, все зависит от исходных данных.
Для некоторых деталей тестирования см. https://github.com/dotnet/BenchmarkDotNet/issues/1505
Любите имена переменных. Однако я бы не хотел быть тем @ss, который удаляют.