La collection a été modifiée; l’opération d’énumération peut ne pas s’exécuter

Je ne peux pas aller au fond de cette erreur, car lorsque le débogueur est attaché, il ne semble pas se produire. Voici le code.

Ceci est un serveur WCF dans un service Windows. La méthode NotifySubscribers est appelée par le service chaque fois qu’il y a un événement de données (à des intervalles aléatoires, mais pas très souvent – environ 800 fois par jour).

Lorsqu’un client Windows Forms est abonné, l’ID d’abonné est ajouté au dictionnaire d’abonnés et, lorsque le client se désabonne, il est supprimé du dictionnaire. L’erreur se produit lorsque (ou après) un client se désabonne. Il semble que la prochaine fois que la méthode NotifySubscribers () soit appelée, la boucle foreach () échoue avec l’erreur dans la ligne d’object. La méthode écrit l’erreur dans le journal des applications, comme indiqué dans le code ci-dessous. Lorsqu’un débogueur est connecté et qu’un client se désabonne, le code s’exécute correctement.

Voyez-vous un problème avec ce code? Dois-je rendre le dictionnaire thread-safe?

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] public class SubscriptionServer : ISubscriptionServer { private static IDictionary subscribers; public SubscriptionServer() { subscribers = new Dictionary(); } public void NotifySubscribers(DataRecord sr) { foreach(Subscriber s in subscribers.Values) { try { s.Callback.SignalData(sr); } catch (Exception e) { DCS.WriteToApplicationLog(e.Message, System.Diagnostics.EventLogEntryType.Error); UnsubscribeEvent(s.ClientId); } } } public Guid SubscribeEvent(ssortingng clientDescription) { Subscriber subscriber = new Subscriber(); subscriber.Callback = OperationContext.Current. GetCallbackChannel(); subscribers.Add(subscriber.ClientId, subscriber); return subscriber.ClientId; } public void UnsubscribeEvent(Guid clientId) { try { subscribers.Remove(clientId); } catch(Exception e) { System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + e.Message); } } } 

Ce qui se passe probablement, c’est que SignalData modifie indirectement le dictionnaire des abonnés sous le capot pendant la boucle et mène à ce message. Vous pouvez vérifier cela en changeant

 foreach(Subscriber s in subscribers.Values) 

À

 foreach(Subscriber s in subscribers.Values.ToList()) 

Si j’ai raison, le problème disparaîtra

Lorsqu’un abonné se désabonne, vous modifiez le contenu de la collection d’abonnés pendant l’énumération.

Il existe plusieurs manières de résoudre ce problème, l’une étant de modifier la boucle for pour utiliser un .ToList() explicite:

 public void NotifySubscribers(DataRecord sr) { foreach(Subscriber s in subscribers.Values.ToList()) { ^^^^^^^^^ ... 

À mon avis, un moyen plus efficace est d’avoir une autre liste dans laquelle vous déclarez que vous mettez tout ce qui «doit être retiré». Ensuite, après avoir terminé votre boucle principale (sans le .ToList ()), vous faites une autre boucle sur la liste “à supprimer”, en supprimant chaque entrée à mesure qu’elle se produit. Donc, dans votre classe, vous ajoutez:

 private List toBeRemoved = new List(); 

Ensuite, vous le changez pour:

 public void NotifySubscribers(DataRecord sr) { toBeRemoved.Clear(); ...your unchanged code skipped... foreach ( Guid clientId in toBeRemoved ) { try { subscribers.Remove(clientId); } catch(Exception e) { System.Diagnostics.Debug.WriteLine("Unsubscribe Error " + e.Message); } } } ...your unchanged code skipped... public void UnsubscribeEvent(Guid clientId) { toBeRemoved.Add( clientId ); } 

Cela ne résoudra pas seulement votre problème, il vous empêchera de continuer à créer une liste à partir de votre dictionnaire, ce qui est coûteux s’il y a beaucoup d’abonnés. En supposant que la liste des abonnés à supprimer sur une itération donnée soit inférieure au nombre total dans la liste, cela devrait être plus rapide. Mais bien sûr, n’hésitez pas à le profiler pour être sûr que c’est le cas s’il y a un doute dans votre situation d’utilisation spécifique.

Vous pouvez également verrouiller votre dictionnaire d’abonnés pour éviter qu’il soit modifié chaque fois qu’il est mis en boucle:

  lock (subscribers) { foreach (var subscriber in subscribers) { //do something } } 

Remarque : En général, les collections .Net ne prennent pas en charge l’énumération et la modification simultanées. Si vous essayez de modifier la liste de collection alors que vous êtes en train de l’énumérer, une exception sera déclenchée.

Donc, le problème derrière cette erreur est que nous ne pouvons pas modifier la liste / dictionnaire pendant que nous effectuons une boucle. Mais si on itère un dictionnaire en utilisant une liste temporaire de ses clés, en parallèle on peut modifier l’object dictionnaire, car maintenant on ne itère pas le dictionnaire (et on itère sa collection de clés).

échantillon:

 //get key collection from dictionary into a list to loop through List keys = new List(Dictionary.Keys); // iterating key collection using simple for-each loop foreach (int key in keys) { // Now we can perform any modification with values of dictionary. Dictionary[key] = Dictionary[key] - 1; } 

Voici un article de blog sur cette solution.

Et pour une plongée en profondeur dans stackoverflow: pourquoi cette erreur se produit?

En fait, le problème me semble être que vous supprimez des éléments de la liste et que vous vous attendez à continuer à lire la liste comme si de rien n’était.

Ce que vous devez vraiment faire, c’est commencer à la fin et revenir au début. Même si vous supprimez des éléments de la liste, vous pourrez continuer à le lire.

InvalidOperationException – Une exception InvalidOperationException s’est produite. Il signale qu’une “collection a été modifiée” dans une boucle foreach

Utilisez l’instruction break, une fois l’object supprimé.

ex:

 ArrayList list = new ArrayList(); foreach (var item in list) { if(condition) { list.remove(item); break; } } 

J’ai eu le même problème, et il a été résolu lorsque j’ai utilisé une boucle for au lieu de foreach .

 // foreach (var item in itemsToBeLast) for (int i = 0; i < itemsToBeLast.Count; i++) { var matchingItem = itemsToBeLast.FirstOrDefault(item => item.Detach); if (matchingItem != null) { itemsToBeLast.Remove(matchingItem); continue; } allItems.Add(itemsToBeLast[i]);// (attachDetachItem); } 

J’ai vu beaucoup d’options pour cela mais pour moi celle-ci était la meilleure.

 ListItemCollection collection = new ListItemCollection(); foreach (ListItem item in ListBox1.Items) { if (item.Selected) collection.Add(item); } 

Ensuite, parcourez simplement la collection.

Sachez qu’un ListItemCollection peut contenir des doublons. Par défaut, rien n’empêche d’append des doublons à la collection. Pour éviter les doublons, vous pouvez le faire:

 ListItemCollection collection = new ListItemCollection(); foreach (ListItem item in ListBox1.Items) { if (item.Selected && !collection.Contains(item)) collection.Add(item); } 

Ok, alors ce qui m’a aidé était de revenir en arrière. J’essayais de supprimer une entrée d’une liste mais de l’itérer vers le haut et cela a foutu la boucle car l’entrée n’existait plus:

 for (int x = myList.Count - 1; x > -1; x--) { myList.RemoveAt(x); } 

Vous pouvez copier un object dictionnaire d’abonné dans le même type d’object dictionnaire temporaire, puis effectuer une itération de l’object dictionnaire temporaire à l’aide de la boucle foreach.

Une autre façon de résoudre ce problème serait de supprimer les éléments de créer un nouveau dictionnaire et d’append uniquement les éléments que vous ne souhaitez pas supprimer, puis de remplacer le dictionnaire original par le nouveau. Je ne pense pas que ce soit un problème d’efficacité parce que cela n’augmente pas le nombre de fois que vous parcourez la structure.