Лучший способ удалить несколько элементов, соответствующих предикату, из словаря c #? - PullRequest
51 голосов
/ 22 января 2009

Мне нужно удалить несколько элементов из словаря. Простой способ сделать это заключается в следующем:

  List<string> keystoremove= new List<string>();
  foreach (KeyValuePair<string,object> k in MyCollection)
     if (k.Value.Member==foo)
        keystoremove.Add(k.Key);
  foreach (string s in keystoremove)
        MyCollection.Remove(s);

Причина, по которой я не могу напрямую удалить элементы в блоке foreach, заключается в том, что это вызовет исключение («Коллекция была изменена ...»)

Я бы хотел сделать следующее:

 MyCollection.RemoveAll(x =>x.Member==foo)

Но класс Dictionary <> не предоставляет метод RemoveAll (Predicate <> Match), как это делает класс List <. </p>

Каков наилучший способ (как по производительности, так и по элегантности) сделать это?

Ответы [ 6 ]

82 голосов
/ 22 января 2009

Вот альтернативный способ

foreach ( var s in MyCollection.Where(kv => kv.Value.Member == foo).ToList() ) {
  MyCollection.Remove(s.Key);
}

Вставка кода в список напрямую позволяет избежать проблемы «удаления при перечислении». .ToList() вызовет перечисление до начала работы foreach.

21 голосов
/ 22 января 2009

вы можете создать метод расширения :

public static class DictionaryExtensions
{
    public static void RemoveAll<TKey, TValue>(this IDictionary<TKey, TValue> dict, 
        Func<TValue, bool> predicate)
    {
        var keys = dict.Keys.Where(k => predicate(dict[k])).ToList();
        foreach (var key in keys)
        {
            dict.Remove(key);
        }
    }
}

...

dictionary.RemoveAll(x => x.Member == foo);
11 голосов
/ 22 января 2009

Вместо удаления просто сделайте обратное. Создайте новый словарь из старого, содержащего только интересующие вас элементы.

public Dictionary<T, U> NewDictionaryFiltered<T, U>
(
  Dictionary<T, U> source,
  Func<T, U, bool> filter
)
{
return source
  .Where(x => filter(x.Key, x.Value))
  .ToDictionary(x => x.Key, x => x.Value);
}
10 голосов
/ 22 августа 2011

Модифицированная версия решения метода расширения Аку. Основное отличие состоит в том, что он позволяет предикату использовать ключ словаря. Небольшое отличие состоит в том, что он расширяет IDictionary, а не словарь.

public static class DictionaryExtensions
{
    public static void RemoveAll<TKey, TValue>(this IDictionary<TKey, TValue> dic,
        Func<TKey, TValue, bool> predicate)
    {
        var keys = dic.Keys.Where(k => predicate(k, dic[k])).ToList();
        foreach (var key in keys)
        {
            dic.Remove(key);
        }
    }
}

. . .

dictionary.RemoveAll((k,v) => v.Member == foo);
0 голосов
/ 22 января 2009

Вместо удаления просто сделайте обратное (создайте новый словарь из старого, содержащий только интересующие вас элементы) и позвольте сборщику мусора позаботиться о старом словаре:

var newDictionary = oldDictionary.Where(x => x.Value != foo);
0 голосов
/ 22 января 2009

Можете ли вы просто изменить свой цикл, чтобы использовать индекс (т.е. FOR вместо FOREACH)? Конечно, вам придется вернуться назад, то есть считать от 1 до нуля.

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