Лучший способ удалить элементы из коллекции - PullRequest
66 голосов
/ 16 октября 2008

Каков наилучший способ удаления элементов из коллекции в 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 дополнительные переменные.

Ответы [ 14 ]

127 голосов
/ 16 октября 2008

Если RoleAssignments - List<T>, вы можете использовать следующий код.

workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
25 голосов
/ 16 октября 2008

Если вы хотите получить доступ к членам коллекции по одному из их свойств, вы можете вместо этого использовать Dictionary<T> или KeyedCollection<T>. Таким образом, вам не нужно искать предмет, который вы ищете.

В противном случае вы могли бы по крайней мере сделать это:

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
        break;
    }
}
21 голосов
/ 16 октября 2008

@ smaclell спросил, почему обратная итерация была более эффективной в комментарии к @ sambo99.

Иногда это более эффективно. Предположим, у вас есть список людей, и вы хотите удалить или отфильтровать всех клиентов с кредитным рейтингом <1000; </p>

У нас есть следующие данные

"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, но список [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);
}
9 голосов
/ 17 апреля 2013

Если это 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/

9 голосов
/ 16 октября 2008

Для простой структуры List наиболее эффективным представляется использование Predicate RemoveAll.

Например.

 workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);

Причины:

  1. Метод Predicate / Linq RemoveAll реализован в List и имеет доступ к внутреннему массиву, в котором хранятся фактические данные. Это сместит данные и изменит размер внутреннего массива.
  2. Реализация метода RemoveAt довольно медленная и копирует весь базовый массив данных в новый массив. Это означает, что обратная итерация бесполезна для List

Если вы застряли в реализации этого в эпоху до c # 3.0. У вас есть 2 варианта.

  • Легко обслуживаемый вариант. Скопируйте все соответствующие элементы в новый список и поменяйте местами основной список.

Например.

List<int> list2 = new List<int>() ; 
foreach (int i in GetList())
{
    if (!(i % 2 == 0))
    {
        list2.Add(i);
    }
}
list2 = list2;

или

  • Хитрый немного более быстрый вариант, который включает в себя смещение всех данных в списке вниз, когда они не совпадают, и затем изменение размера массива.

Если вы действительно часто удаляете вещи из списка, возможно, другая структура, такая как HashTable (.net 1.1) или Dictionary (.net 2.0) или HashSet (.net 3.5) лучше подходит для этой цели.

7 голосов
/ 16 октября 2008

Какой тип коллекции? Если это список, вы можете использовать полезный «RemoveAll»:

int cnt = workspace.RoleAssignments
                      .RemoveAll(spa => spa.Member.Name == shortName)

(Это работает в .NET 2.0. Конечно, если у вас нет более нового компилятора, вам придется использовать «делегат (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. (Вы можете скопировать моно, если вы застряли на .NET 2.0). Если это некая пользовательская коллекция, которая не может принять элемент, но ДОЛЖНА принять индекс, некоторые другие перечисляемые методы, такие как Select, передают вам целочисленный индекс.

2 голосов
/ 17 февраля 2010

Это мое общее решение

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

2 голосов
/ 13 сентября 2009

Вот довольно хороший способ сделать это

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();
0 голосов
/ 23 апреля 2018

Лучший способ сделать это - использовать 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;

0 голосов
/ 25 января 2016

Сначала сохраните ваши элементы, затем удалите их.

var itemsToDelete = Items.Where(x => !!!your condition!!!).ToArray();
for (int i = 0; i < itemsToDelete.Length; ++i)
    Items.Remove(itemsToDelete[i]);

Вам необходимо переопределить GetHashCode() в вашем классе предметов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...