80 votes

Meilleure façon de retirer des éléments d'une collection

Quelle est la meilleure façon d'aborder la suppression d'éléments d'une collection en C#, une fois que l'élément est connu, mais pas son index. Voici une façon de procéder, mais elle semble peu élégante au mieux.

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

Ce que j'aimerais vraiment faire, c'est trouver l'élément à supprimer par propriété (dans ce cas, le nom) sans parcourir toute la collection en boucle et sans utiliser deux variables supplémentaires.

88 votes

J'adore les noms de variables. Je n'aimerais pas être l'imbécile qui se fait supprimer, par contre.

0 votes

Ajoutez une instruction break lors d'une recherche réussie si vous envisagez de procéder de cette manière, bien que l'utilisation d'un dictionnaire soit probablement préférable de toute façon si vous recherchez toujours les choses par le nom du membre.

0 votes

Je pense que vous vouliez dire RemoveAt() dans votre extrait de code, puisque vous passez l'index. Une fois que l'élément est connu, vous pouvez appeler Remove() directement.

138voto

JaredPar Points 333733

Si RoleAssignments est un List<T> vous pouvez utiliser le code suivant.

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

0 votes

Je pense que c'est seulement RemoveAll qui a le paramètre de fonction. En tout cas, je ne peux pas le construire avec Remove.

5 votes

Oui, c'est RemoveAll. J'ai pris le temps de vérifier, j'ai vérifié que c'était bien RemoveAll et j'ai quand même collé dans Remove. Dommage que stackoverflow n'ait pas un compilateur intégré :)

0 votes

Cela génère une nouvelle liste si je ne me trompe pas.

29voto

Jon B Points 26872

Si vous souhaitez accéder aux membres de la collection par l'intermédiaire d'une de leurs propriétés, vous pouvez envisager d'utiliser une balise Dictionary<T> o KeyedCollection<T> à la place. Ainsi, vous n'avez pas à chercher l'article que vous recherchez.

Sinon, tu pourrais au moins faire ça :

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
        break;
    }
}

14 votes

Cela provoquerait une exception car vous modifiez la collection au fur et à mesure que vous l'utilisez...

17 votes

Non, ça ne l'est pas. C'est parce qu'il y a une pause après avoir retiré l'objet.

27 votes

Au cas où quelqu'un lirait ceci et l'appliquerait à une situation où l'on supprime plusieurs éléments, enregistrez les index multiples dans un tableau et utilisez une boucle for séparée qui boucle en arrière dans le tableau de suppression pour supprimer les éléments.

23voto

Robert Paulson Points 10792

@smaclell a demandé pourquoi l'itération inverse était plus efficace dans un commentaire à @sambo99.

Parfois c'est plus efficace. Considérons que vous avez une liste de personnes et que vous voulez supprimer ou filtrer tous les clients ayant une cote de crédit < 1000 ;

Nous disposons des données suivantes

"Bob" 999
"Mary" 999
"Ted" 1000

Si nous devions itérer vers l'avant, nous aurions rapidement des problèmes.

for( int idx = 0; idx < list.Count ; idx++ )
{
    if( list[idx].Rating < 1000 )
    {
        list.RemoveAt(idx); // whoops!
    }
}

A idx = 0, on enlève Bob qui décale alors tous les éléments restants vers la gauche. Au prochain passage de la boucle, idx = 1, mais liste[1] est maintenant Ted au lieu de Mary . On finit par sauter Mary par erreur. Nous pourrions utiliser une boucle while, et nous pourrions introduire plus de variables.

Ou alors, on inverse l'itération :

for (int idx = list.Count-1; idx >= 0; idx--)
{
    if (list[idx].Rating < 1000)
    {
        list.RemoveAt(idx);
    }
}

Tous les index à gauche de l'élément supprimé restent les mêmes, de sorte que vous ne sautez aucun élément.

Le même principe s'applique si l'on vous donne une liste d'index à supprimer d'un tableau. Pour que les choses restent claires, vous devez trier la liste, puis supprimer les éléments de l'indice le plus élevé au plus bas.

Maintenant, vous pouvez simplement utiliser Linq et déclarer ce que vous faites d'une manière directe.

list.RemoveAll(o => o.Rating < 1000);

Pour ce cas de suppression d'un seul élément, il n'est pas plus efficace d'itérer en avant ou en arrière. Vous pourriez également utiliser Linq pour cela.

int removeIndex = list.FindIndex(o => o.Name == "Ted");
if( removeIndex != -1 )
{
    list.RemoveAt(removeIndex);
}

2 votes

Pour une simple List<T>, si vous devez supprimer plus d'un élément, la boucle for inverse est TOUJOURS le moyen le plus efficace de le faire. C'est certainement plus efficace que de copier les données dans une liste listToRemove. Je parie que l'implémentation de Linq utilise la même astuce.

0 votes

@sambo99 Je suis tout à fait d'accord et je tente de développer votre réponse en expliquant pourquoi l'itération vers l'avant ne fonctionne pas toujours. J'indique également que, en l'absence d'informations supplémentaires, l'itération inverse n'est ni plus ni moins efficace si vous supprimez au maximum une entrée. O(n) est le meilleur résultat possible avec les listes.

0 votes

Yerp. Je corrige cela maintenant ... List<T> a une très mauvaise implémentation de RemoveAt, la manière la plus efficace semble être de copier les données dont nous avons besoin hors de la liste.

10voto

Sam Saffron Points 56236

Pour une structure de liste simple, la méthode la plus efficace semble être d'utiliser l'implémentation de Predicate RemoveAll.

Eg.

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

Les raisons en sont :

  1. La méthode Predicate/Linq RemoveAll est implémentée dans List et a accès au tableau interne stockant les données réelles. Elle va déplacer les données et redimensionner le tableau interne.
  2. La mise en œuvre de la méthode RemoveAt est assez lente et copiera l'intégralité du tableau de données sous-jacent dans un nouveau tableau. Cela signifie que l'itération inverse est inutile pour List

Si vous êtes coincé à implémenter ceci dans une ère pré-C# 3.0. Vous avez 2 options.

  • L'option facile à entretenir. Copiez tous les éléments correspondants dans une nouvelle liste et échangez la liste sous-jacente.

Eg.

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

Ou

  • L'option délicate, légèrement plus rapide, qui consiste à déplacer toutes les données de la liste vers le bas lorsqu'elles ne correspondent pas, puis à redimensionner le tableau.

Si vous supprimez très fréquemment des éléments d'une liste, peut-être qu'une autre structure, comme une Table de hachage (.net 1.1) ou un Dictionnaire (.net 2.0) ou un HashSet (.net 3.5) sont mieux adaptés à cet effet.

7voto

MichaelGG Points 8202

Quel est le type de la collection ? S'il s'agit d'une liste, vous pouvez utiliser l'utile "RemoveAll" :

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

(Cela fonctionne en .NET 2.0. Bien sûr, si vous ne disposez pas du compilateur le plus récent, vous devrez utiliser "delegate (SPRoleAssignment spa) { return spa.Member.Name == shortName ; }" au lieu de la jolie syntaxe lambda).

Une autre approche s'il ne s'agit pas d'une liste, mais d'une ICollection :

   var toRemove = workspace.RoleAssignments
                              .FirstOrDefault(spa => spa.Member.Name == shortName)
   if (toRemove != null) workspace.RoleAssignments.Remove(toRemove);

Cela nécessite les méthodes d'extension Enumerable. (Vous pouvez copier celles de Mono, si vous êtes bloqué sur .NET 2.0). S'il s'agit d'une collection personnalisée qui ne peut pas prendre un élément, mais qui DOIT prendre un index, certaines des autres méthodes Enumerable, comme Select, transmettent l'index entier pour vous.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X