web-dev-qa-db-fra.com

Meilleur moyen de supprimer des éléments d'une collection

Quelle est la meilleure façon de supprimer des éléments d'une collection en C #, une fois que l'élément est connu, mais pas son index. C'est une façon de le faire, mais cela semble au mieux inélégant.

//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, nom) sans parcourir en boucle toute la collection et utiliser 2 variables supplémentaires.

65
Dan

Si vous souhaitez accéder aux membres de la collection par l’une de leurs propriétés, vous pouvez utiliser plutôt un Dictionary<T> ou un KeyedCollection<T>. De cette façon, vous n'avez pas à rechercher l'élément que vous recherchez.

Sinon, vous pourriez au moins faire ceci:

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

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

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

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

Parfois c'est plus efficace. Considérez que vous avez une liste de personnes et que vous souhaitez supprimer ou filtrer tous les clients dont la cote de crédit est inférieure à 1 000;

Nous avons les données suivantes

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

Si nous devions avancer, nous aurions bientôt des ennuis

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

À idx = 0, nous supprimons Bob, qui déplace alors tous les éléments restants. La prochaine fois dans la boucle idx = 1, mais list [1] est maintenant Ted au lieu de Mary. Nous finissons par sauter Mary par erreur. Nous pourrions utiliser une boucle while et introduire davantage de variables.

Ou bien, nous inversons simplement 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, vous ne sautez donc aucun élément.

Le même principe s'applique si vous recevez une liste d'index à supprimer d'un tableau. Pour que tout soit clair, vous devez trier la liste, puis supprimer les éléments du plus haut index au plus bas.

Maintenant, vous pouvez simplement utiliser Linq et déclarer ce que vous faites de manière simple.

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

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

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

Pour une structure de liste simple, le moyen le plus efficace semble utiliser l'implémentation Predicate RemoveAll. 

Par exemple.

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

Les raisons 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. Il va déplacer les données et redimensionner le tableau interne.
  2. L'implémentation 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é à mettre cela en œuvre dans une ère pré-c # 3.0. Vous avez 2 options. 

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

Par exemple. 

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 implique de décaler toutes les données de la liste lorsqu'elles ne correspondent pas, puis de redimensionner le tableau. 

Si vous supprimez fréquemment des éléments d'une liste, peut-être une autre structure telle qu'un HashTable (.net 1.1) ou un Dictionary (.net 2.0) ou un HashSet (.net 3.5) sont mieux adaptés à cette fin. 

9
Sam Saffron

S'il s'agit d'une ICollection, vous n'aurez pas de méthode RemoveAll. Voici une méthode d'extension qui le fera:

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

Basé sur: http://phejndorf.wordpress.com/2011/03/09/a-removeall-extension-for-the-collection-class/

8
Colin

De quel type est la collection? Si c'est la liste, vous pouvez utiliser l'utile "RemoveAll":

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

(Cela fonctionne dans .NET 2.0. Bien sûr, si vous ne possédez pas le nouveau compilateur, vous devrez utiliser "delegate (SPRoleAssignment spa) {return spa.Member.Name == shortName;}" à la place de Nice syntaxe lambda.)

Une autre approche si ce n'est pas une liste, mais toujours 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 les fichiers Mono dans, 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 DOIT prendre un index, certaines des autres méthodes Enumerable, telles que Select, transmettent l'index entier à votre place. 

7
MichaelGG

Voici un très bon moyen de le faire

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();
2
Jb

Ceci est ma solution générique

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

Cela semblerait beaucoup plus simple si les méthodes d’extension prennent en charge le passage par référence! Usage:

var result = string[]{"mike", "john", "ALi"}
result = result.Remove(x => x.Username == "mike").ToArray();
Assert.IsTrue(result.Length == 2);

EDIT: assure que la boucle de liste reste valide même lors de la suppression d'éléments en décrémentant l'index (idx).

2
Jalal El-Shaer

Pour ce faire tout en parcourant la collection et ne pas obtenir l’exception de modification d’une collection, c’est l’approche que j’ai adoptée par le passé (notez .ToList () à la fin de la collection originale, cela crée une autre collection en mémoire , alors vous pouvez modifier la collection existante)

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

0
Anthony Shaw

La meilleure façon de le faire est d'utiliser linq. 

Exemple de classe:

 public class Product
    {
        public string Name { get; set; }
        public string Price { get; set; }      
    }

Requête Linq:

var subCollection = collection1.RemoveAll(w => collection2.Any(q => q.Name == w.Name));

Cette requête supprimera tous les éléments de collection1 si Name correspond à un élément Name de collection2

N'oubliez pas d'utiliser: using System.Linq;

0
john.kernel

Beaucoup de bonnes réponses ici; J'aime particulièrement les expressions lambda ... très propres. Cependant, j'ai été négligent de ne pas spécifier le type de collection. Il s'agit d'une SPRoleAssignmentCollection (à partir de MOSS) qui ne comporte que Remove (int) et Remove (SPPrincipal), mais pas le très pratique RemoveAll (). Je me suis donc décidé, à moins que la suggestion ne soit meilleure.

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
                        {
                            if (spAssignment.Member.Name != shortName) continue;
                            workspace.RoleAssignments.Remove((SPPrincipal)spAssignment.Member);
                            break;
                        }
0
Dan

Semblable au point de vue Collection de dictionnaires, je l'ai fait.

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

Remarque: Le code ci-dessus échouera dans .Net Client Profile (3.5 et 4.5). Certains téléspectateurs ont mentionné qu'il était Ils ont échoué dans .Net4.0 et ne savent pas exactement quels paramètres sont à l'origine du problème. .

Remplacez donc par le code ci-dessous (.ToList ()) pour l'instruction Where, afin d'éviter cette erreur. «La collection a été modifiée. l'opération d'énumération peut ne pas s'exécuter. ”

var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false).ToList();

Par MSDN à partir de .Net4.5, les profils de clients sont abandonnés. http://msdn.Microsoft.com/en-us/library/cc656912(v=vs.110).aspx

0
Sai

Il existe une autre approche que vous pouvez adopter en fonction de la manière dont vous utilisez votre collection. Si vous téléchargez les assignations une fois (par exemple, lorsque l'application est exécutée), vous pouvez traduire la collection à la volée en une table de hachage où:

shortname => SPRoleAssignment

Si vous procédez ainsi, lorsque vous souhaitez supprimer un élément par son nom abrégé, il vous suffit de supprimer l’élément de la table de hachage par clé.

Malheureusement, si vous chargez souvent ces SPRoleAssignments, cela ne sera évidemment pas plus rentable en termes de temps. Les suggestions que d'autres personnes ont faites sur l'utilisation de Linq seraient utiles si vous utilisez une nouvelle version du .NET Framework, mais sinon, vous devrez vous en tenir à la méthode que vous utilisez.

0
Ed Altorfer

Enregistrez d'abord vos éléments, puis supprimez-les.

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

Vous devez remplacer GetHashCode() dans votre classe Item.

0
Stas BZ