2010-01-14 8 views
6

Un dictionnaire ne peut pas être modifié si nous le bouclons dans .Net. Mais pourquoi la valeur dans un dictionnaire est-elle seulement lue? Quelqu'un a des idées? Pourquoi l'équipe .Net a décidé de ne pas modifier la valeur lors de la lecture en boucle d'un dictionnaire. Je peux comprendre si la clé ne peut pas être changée mais pourquoi la valeur? De même, si vous utilisez LINQ et récupérez les clés sous forme de Ienumerable, la valeur peut-elle être modifiée? le chargement paresseux a-t-il un rôle à jouer?Valeur du dictionnaire C#

+0

Je ne suis pas sûr si vous voulez dire changer la valeur retournée par l'énumérateur (l'objet qui vous permet de 'foreach' sur les collections) ou ajouter/supprimer des éléments pendant l'itération? – Skurmedel

Répondre

10

Pourquoi l'équipe BCL décident de ne pas permettre des modifications à la valeur lors de la boucle sur le dictionnaire. Je comprends que la clé ne peut pas être modifiée, mais pourquoi ne pas autoriser les modifications de la valeur?

Je ne peux pas parler définitivement pour l'équipe qui a construit le dictionnaire, mais je peux faire quelques instruits suppositions. Tout d'abord, on dit souvent que les bons programmes sont stricts quant à la justesse de leurs sorties, mais pardonnent dans ce qu'ils acceptent. Je ne crois pas que ce soit un bon principe de conception.Ce est un mauvais principe de conception, car il permet les appelants buggy de prendre dépendances sur non défini et non pris en charge le comportement. Cela crée donc une charge de compatibilité ascendante. (Nous voyons certainement cela dans le monde du navigateur Web, où chaque fournisseur de navigateur est soumis à des pressions pour être compatible avec l'acceptation du code HTML par tous les autres navigateurs.)

Les composants définissent les contrats - ils disent quelles informations ils acceptent en entrée, quelles opérations ils soutiennent, et quels résultats ils produisent. Lorsque vous acceptez des entrées qui ne figurent pas dans votre contrat ou des opérations de support qui ne figurent pas dans votre contrat, vous effectuez essentiellement un contrat nouveau, un contrat non documenté, non pris en charge et pouvant être interrompu. une future version. Vous êtes en train de construire une bombe à retardement et quand ça se déclenche, soit un client (qui ne savait même pas qu'il faisait quelque chose de non supporté) se casse, ou le fournisseur de composants finit par devoir supporter à jamais un contrat ne t'inscris pas.

Il est donc un bon principe de conception à appliquer strictement votre contrat. Le contrat de IEnumerable sur une collection est "il est illégal de modifier la collection pendant l'itération". Un exécutant de ce contrat peut choisir de dire «eh bien, je sais que certaines modifications sont sûres, alors je vais autoriser celles-ci», et hé, soudainement, vous ne mettez plus en œuvre le contrat. Vous mettez en œuvre un contrat différent et non documenté sur lequel les gens vont s'appuyer.

Il est préférable de simplement appliquer le contrat, même si cela n'est pas nécessaire. De cette façon, à l'avenir, vous aurez la liberté de vous fier à votre contrat documenté sans vous inquiéter du fait qu'un appelant a rompu le contrat et s'en soit tiré, et s'attend à pouvoir continuer à le faire pour toujours.

Il serait facile de concevoir un dictionnaire qui permette la mutation des valeurs pendant l'itération. Cela empêche les fournisseurs de composants de pouvoir désactiver cette fonctionnalité et ils ne sont pas tenus de la fournir. Il est donc préférable de donner une erreur lorsque quelqu'un tente d'empêcher un appelant de violer le contrat. Deuxième hypothèse: le type de dictionnaire est non scellé et peut donc être étendu. Les tiers pourraient étendre le dictionnaire de telle sorte que leurs invariants seraient violés si une valeur était changée pendant un dénombrement. Supposons, par exemple, que quelqu'un étende un dictionnaire de telle sorte qu'il puisse être énuméré trié par la valeur. Lorsque vous écrivez une méthode qui prend un Dictionnaire et y réagit, vous supposez que l'opération fonctionnera sur tous les dictionnaires, même les extensions tierces. Les concepteurs d'un composant destiné à l'extension doivent être encore plus vigilants que d'habitude pour s'assurer que l'objet applique son contrat, car des tiers inconnus pourraient se fier à l'exécution de ce contrat. Parce qu'il peut être un dictionnaire que ne peut pas prendre en charge la modification d'une valeur pendant l'itération, la classe de base ne doit pas le prendre en charge non plus; faire autrement est de violer la substituabilité des classes dérivées pour les classes de base.

Si vous utilisez LINQ et que vous récupérez les clés sous forme de IEnumerable, la valeur peut-elle être modifiée?

La règle est qu'un dictionnaire ne peut pas être modifié en itérant sur lui. Que vous utilisiez LINQ pour faire l'itération ou non est sans importance; l'itération est l'itération.

est-ce que le chargement paresseux a un rôle à jouer?

Bien sûr. Rappelez-vous, la définition d'une requête LINQ ne parcourt rien; le résultat d'une expression de requête est un objet de requête. Ce n'est que lorsque vous itérez sur cet objet que l'itération réelle se produit sur la collection. Quand vous dites:

var bobs = from item in items where item.Name == "Bob" select item; 

aucune itération n'arrive ici. Ce n'est que lorsque vous dites

foreach(var bob in bobs) ... 

que l'itération sur les éléments se produit.

+0

Merci Eric pour m'avoir expliqué d'une manière géniale. J'ai eu ma réponse. – Prashant

4

Un KeyValuePair est un struct et les structures mutables sont mauvaises, donc il a été rendu en lecture seule.

Pour modifier certaines valeurs, vous pouvez parcourir les KeyValuePairs et stocker toutes les mises à jour que vous souhaitez effectuer. Lorsque vous avez fini d'itérer, vous pouvez ensuite parcourir votre liste de mises à jour et les appliquer. Voici un exemple de la façon dont vous pouvez le faire (sans utiliser LINQ):

Dictionary<string, string> dict = new Dictionary<string,string>(); 
    dict["foo"] = "bar"; 
    dict["baz"] = "qux"; 

    List<KeyValuePair<string, string>> updates = new List<KeyValuePair<string,string>>(); 
    foreach (KeyValuePair<string, string> kvp in dict) 
    { 
     if (kvp.Key.Contains("o")) 
      updates.Add(new KeyValuePair<string, string>(kvp.Key, kvp.Value + "!")); 
    } 

    foreach (KeyValuePair<string, string> kvp in updates) 
    { 
     dict[kvp.Key] = kvp.Value; 
    } 
1

Je ne sais pas pourquoi, mais vous pouvez contourner cette limitation en valeurs faisant référence indirectement. Par exemple, vous pouvez créer une classe qui fait référence à une autre simplement comme ceci:

class StrongRef<ObjectType> 
{ 
    public StrongRef(ObjectType actualObject) 
    { 
     this.actualObject = actualObject; 
    } 

    public ObjectType ActualObject 
    { 
     get { return this.actualObject; } 
     set { this.actualObject = value; } 
    } 

    private ObjectType actualObject; 
} 

Ensuite, modifier la place des valeurs dans le dicitonary, vous pouvez modifier la référence indirecte. (Je l'ai entendu sur la bonne autorité que la routine suivante est utilisée par Blue Peter pour remplacer leurs chiens morts avec des animaux vivants identiques à la recherche.)

public void ReplaceDeadDogs() 
{ 
    foreach (KeyValuePair<string, StrongRef<Dog>> namedDog in dogsByName) 
    { 
     string name = namedDog.Key; 
     StrongRef dogRef = namedDog.Value; 

     // update the indirect reference 
     dogRef.ActualObject = PurchaseNewDog(); 
    } 
} 

D'autres alternatives serait d'enregistrer les changements nécessaires dans un second dictionnaire lors de l'itération puis de l'application de ces modifications (en répétant le deuxième dictionnaire), ou pour créer un dictionnaire de remplacement complet tout en l'itérant et en l'utilisant à la place de l'original après avoir terminé la boucle.

0

De l'this Microsoft support reply (basé sur Hashtable mais le même)

La clé ici est que les agents recenseurs sont conçu pour fournir un aperçu de la collection , mais pour des raisons de performance ils ne le font pas copier l'ensemble de la collection dans un autre tableau temporaire ou quoi que ce soit de ce type. Au lieu de cela, ils utilisent la collection en direct et lancent une exception s'ils détectent que quelqu'un a changé la collection. Oui, il fait ce type de code plus difficile à écrire ...

Questions connexes