2013-08-20 1 views
1

J'ai un SortedDictionary déclaré comme tel:Récupéré Dictionnaire clé non trouvé

SortedDictionary<MyObject,IMyInterface> dict = new SortedDictionary<MyObject,IMyInterface>(); 

Lorsque son avec les valeurs, si je prends une clé du dictionnaire et essaie ensuite de faire référence immédiatement après, je reçois un KeyNotFoundException :

MyObject myObj = dict.Keys.First(); 
var value = dict[myObj];  // This line throws a KeyNotFoundException 

Comme je l'ai Survoler la dictionnaire (après l'erreur) avec le débogueur, je peux voir clairement la même clé que j'ai essayé de faire référence est en fait contenue dans le dictionnaire. Je suis en train de remplir le dictionnaire en utilisant un ReadOnlyCollection de MyObjects. Peut-être que quelque chose d'étrange se passe là-bas? J'ai essayé de remplacer l'opérateuret les méthodes Equals pour obtenir la comparaison explicite que je voulais, mais pas une telle chance. Cela ne devrait vraiment pas importer puisque je reçois une clé directement à partir du Dictionary puis en interrogeant le Dictionary en utilisant cette même clé. Je ne peux pas comprendre ce qui cause cela. Quelqu'un at-il déjà vu ce comportement?

EDIT 1

Dans primordial Equals je surchargée aussi (comme MS recommande) GetHashCode aussi bien. Voici la mise en œuvre de MyObject pour toute personne intéressée:

public class MyObject 
{ 
public string UserName { get; set;} 
public UInt64 UserID { get; set;} 

    public override bool Equals(object obj) 
    { 
     if (obj == null || GetType()!= obj.GetType()) 
     { 
      return false; 
     } 

     // Return true if the fields match: 
     return this.Equals((MyObject)obj); 
    } 

    public bool Equals(MyObject other) 
    { 
     // Return true if the fields match 
     return this.UserID == other.UserID; 
    } 

    public override int GetHashCode() 
    { 
     return (int)this.UserID; 
    } 


public static bool operator ==(MyObject a, MyObject b) 
{ 
    // If both are null, or both are same instance, return true. 
    if (System.Object.ReferenceEquals(a, b)) 
    { 
     return true; 
    } 

    // If one is null, but not both, return false. 
    if (((object)a == null) || ((object)b == null)) 
    { 
     return false; 
    } 

    // Return true if the fields match: 
    return a.UserID == b.UserID 
} 

public static bool operator !=(MyObject a, MyObject b) 
{ 
    return !(a == b); 
} 
} 

Ce que je remarquai de débogage est que si j'ajoute une montre rapide (après la KeyNotFoundException est moulinée) pour l'expression:

dict.ElementAt(0).Key == value; 

il retourne vrai . Comment se peut-il?

EDIT 2 Le problème a fini par être parce que SortedDictionary (et Dictionary ainsi) ne sont pas thread-safe. Il y avait un thread d'arrière-plan qui exécutait certaines opérations sur le dictionnaire qui semblaient déclencher un recours de la collection (l'ajout d'éléments à la collection le ferait). En même temps, quand le dictionnaire itérait à travers les valeurs pour trouver ma clé, la collection était en train d'être changée et elle ne trouvait pas ma clé même si elle était là.

Désolé pour tous ceux qui ont demandé du code sur celui-ci, je suis en train de déboguer une application dont j'ai hérité et je ne me suis pas rendu compte que cela se passait sur un thread de fond temporisé. En tant que tel, je pensais avoir copié et collé tout le code pertinent, mais je n'avais pas réalisé qu'il y avait un autre fil conducteur derrière tout ce qui manipulait la collection.

+1

Vous devez * remplacer * Equals (et GetHashCode), mais vous pouvez également vouloir le surcharger. Veuillez montrer votre classe - ou plutôt, un programme court mais complet démontrant le problème. –

+0

Pour être clair: Vous avez observé ceci en premier sans surcharge/surcharge de Equals/==/GetHashCode? Parce qu'une implémentation défectueuse de ceux-ci serait la réponse facile. –

+0

@JonSkeet - Vous avez raison, j'ai mal tapé. J'ai fait _override_ les méthodes 'Equals' (et' GetHashCode'). J'ai ajouté le code pertinent. –

Répondre

0

Il semble que le problème a été résolu car SortedDictionary n'est pas thread-safe. Il y avait un thread d'arrière-plan qui effectuait certaines opérations sur le dictionnaire (en ajoutant des éléments à la collection), ce qui semble déclencher un recours de la collection. En même temps, quand le dictionnaire essayait de parcourir les valeurs pour trouver ma clé, la collection était changée et utilisée, rendant l'énumérateur invalide, et elle ne trouvait pas ma clé même si elle était là.

0

En plus de surcharger == et Equals, assurez-vous de surcharger GetHashCode avec une fonction de hachage appropriée. En particulier, voir cette spécification de la documentation:

  • Si deux objets se comparent comme égaux, la méthode GetHashCode pour chaque objet doit retourner la même valeur. Toutefois, si deux objets ne sont pas égaux, les méthodes GetHashCode pour les deux objets ne doivent pas renvoyer des valeurs différentes.
  • La méthode GetHashCode pour un objet doit systématiquement retourner le même code de hachage tant qu'il n'y a aucune modification de l'état de l'objet qui détermine la valeur de retour de la méthode Equals de l'objet. Notez que cela est vrai uniquement pour l'exécution en cours d'une application, et qu'un code de hachage différent peut être renvoyé si l'application est réexécuter.
  • Pour obtenir les meilleures performances, une fonction de hachage doit générer une distribution uniforme pour toutes les entrées, y compris les entrées fortement regroupées. Une implication est que de petites modifications à l'état de l'objet doivent entraîner des modifications importantes du code de hachage résultant pour les meilleures performances de table de hachage .
  • Les fonctions de hachage doivent être peu coûteuses à calculer.
  • La méthode GetHashCode ne doit pas déclencher d'exceptions.

Je suis d'accord avec Jon Skeet's suspicion que vous en quelque sorte modifier involontairement UserID propriété après elle est ajoutée comme clé. Mais puisque la seule propriété qui est important pour tester l'égalité dans MyObject est UserID (et donc c'est la seule propriété que le Dictionary se soucie), je vous recommande refactorisation votre code pour utiliser un Dictionary<ulong, IMyInterface> simple, à la place:

Dictionary<ulong, IMyInterface> dict = new Dictionary<string, IMyInterface>(); 
ulong userID = dict.Keys.First(); 
var value = dict[userID]; 
+0

J'ai fait, mettre à jour ma question pour refléter cela ... –

+0

@JoelB S'il vous plaît montrer votre implémentation de '==', 'Equals', et' GetHashCode'. Nous pouvons être en mesure de repérer l'erreur. –

+0

Code ajouté..merci de vous! –

0

I avoir un soupçon - il est possible que vous soyez en changeant le UserID de la clé après l'insertion. Par exemple, cela démontrerait le problème:

var key = new MyObject { UserId = 10 }; 
var dictionary = new Dictionary<MyObject, string>(); 
dictionary[key] = "foo"; 

key.UserId = 20; // This will change the hash code 

var value = dict[key]; // Bang! 

Vous ne devriez pas modifier les propriétés impliquées dans des considérations de code de hachage égalité/pour un objet qui est utilisé comme la clé dans une collection à base de hachage. Idéalement, changez votre code pour que ce ne puisse pas être modifié - make UserId en lecture seule, initialisé lors de la construction.

Ce qui précède provoquerait un problème - mais il est possible que ce ne soit pas la même chose que le problème que vous voyez, bien sûr.

+0

Point pris et j'y ajouterai certainement des protections si je garde mon implémentation 'GetHashCode' surchargée. Dans ce cas ce n'est pas le problème puisque je connaissais le problème avant de remplacer 'GetHashCode' –

+0

@JoelB: Eh bien, avez-vous remplacé' Equals' à ce moment-là? C'est la seule explication à laquelle je peux penser, mais comme nous n'avons actuellement aucun moyen de reproduire le problème, nous devrons deviner. Encore une fois, si vous pouvez fournir un moyen de reproduire cela, nous sommes beaucoup plus susceptibles de pouvoir vous aider. –