web-dev-qa-db-fra.com

Le meilleur moyen de supprimer plusieurs éléments correspondant à un prédicat d'un dictionnaire c #?

Je dois supprimer plusieurs éléments d'un dictionnaire. Un moyen simple de le faire est le suivant:

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

La raison pour laquelle je ne peux pas supprimer directement les éléments du bloc foreach est que cela déclencherait une exception ("La collection a été modifiée ...")

Je voudrais faire ce qui suit:

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

Mais la classe Dictionary <> n'expose pas une méthode RemoveAll (Predicate <> Match), comme le fait la classe List <>.

Quelle est la meilleure façon (à la fois en termes de performances et d'élégance) de le faire?

51
Brann

Voici une autre façon

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

Pousser le code directement dans une liste vous permet d'éviter le problème de "suppression lors de l'énumération". La .ToList() forcera l'énumération avant que foreach ne démarre vraiment.

85
JaredPar

vous pouvez créer un méthode d'extension :

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

Au lieu de supprimer, faites simplement l'inverse. Créez un nouveau dictionnaire à partir de l'ancien contenant uniquement les éléments qui vous intéressent.

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);
}
11
Amy B

Version modifiée de la solution de méthode d'extension d'Aku. La principale différence est qu'elle permet au prédicat d'utiliser la clé du dictionnaire. Une différence mineure est qu'elle étend IDictionary plutôt que Dictionary.

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

Pouvez-vous simplement changer votre boucle pour utiliser un index (c'est-à-dire FOR au lieu de FOREACH)? Il faudrait bien sûr boucler en arrière, c'est-à-dire compter-1 jusqu'à zéro.

0
Geoff

Au lieu de supprimer, faites simplement l'inverse (créez un nouveau dictionnaire à partir de l'ancien contenant uniquement les éléments qui vous intéressent) et laissez le garbage collector s'occuper de l'ancien dictionnaire:

var newDictionary = oldDictionary.Where(x => x.Value != foo);
0
Darin Dimitrov