2
votes

KeyNotFoundException dans le dictionnaire C # après avoir modifié la valeur de propriété en fonction de quoi, le GetHashCode est calculé. Pourquoi?

Voir le code ci-dessous.

namespace HashDictionaryTest
{
    public class TestClass
    {
        public int MyProperty { get; set; }

        public override int GetHashCode() {
            return MyProperty;
        }
    }

}

namespace HashDictionaryTest
{
    public class ValueClass
    {
        public int MyProperty { get; set; }

        public override int GetHashCode() {
            return MyProperty;
        }
    }

}

Clé et classe de valeur:

            static void Main(string[] args)
            {
    // Create Dictionary
                var dict = new Dictionary<TestClass, ValueClass>();

    // Add data to dictionary
                CreateSomeData(dict); 

    // Create a List
                var list = new List<TestClass>();
                foreach(var kv in dict) {
    // Swap property values for each Key
    // For example Key with property value 1 will become 6
    // and 6 will become 1
                    kv.Key.MyProperty = 6 - kv.Key.MyProperty + 1;

    // Add the Key to the List
                    list.Add(kv.Key);
                }

// Try to print dictionary and received KeyNotFoundException.
                foreach (var k in list)
                {
                    Console.WriteLine($"{dict[k].MyProperty} - {k.MyProperty}");
                }
            }



    static void CreateSomeData(Dictionary<TestClass, ValueClass> dictionary) {
        dictionary.Add(new TestClass {MyProperty = 1}, new ValueClass {MyProperty = 1});
        dictionary.Add(new TestClass {MyProperty = 2}, new ValueClass {MyProperty = 2});
        dictionary.Add(new TestClass {MyProperty = 3}, new ValueClass {MyProperty = 3});
        dictionary.Add(new TestClass {MyProperty = 4}, new ValueClass {MyProperty = 4});
        dictionary.Add(new TestClass {MyProperty = 5}, new ValueClass {MyProperty = 5});
        dictionary.Add(new TestClass {MyProperty = 6}, new ValueClass {MyProperty = 6});
    }

J'utilise le dotnet core 2.2 sur Ubuntu. J'ai fait ce test uniquement par curiosité. Cependant, à ma grande surprise, j'ai obtenu KeyNotFoundException .

Je m'attendais à recevoir les mauvaises valeurs. Cependant, j'ai reçu l'exception mentionnée ci-dessus.

Ce que je veux savoir, c'est pourquoi nous avons cette erreur? Quelle est la meilleure pratique pour générer le HashCode afin d'éviter de tels problèmes?


8 commentaires

Ce serait génial si vous pouviez fournir un exemple reproductible minimal . Défaut manquant pour ValueClass


Merci @JohnB J'ai ajouté le code complet. Faites-moi savoir si je manque quelque chose. Également clarifié ma question.


c'est un peu bizarre de code que vous avez. Vous vous amusez avec les clés et vous vous attendez à ce qu'elles soient à nouveau trouvables par magie? peut-être expliquer quel est votre objectif ...


Le dictionnaire ne prend pas en charge la mutation des clés stackoverflow .com / questions / 3007296 /…


essayez plutôt de jouer avec la propriété Value ... pas la Key


@JohnB, je voulais savoir deux choses. 1. Pourquoi cela a-t-il donné "KeyNotFound" vs me donner la mauvaise valeur? Je comprends maintenant. 2. Quelle est la meilleure pratique ici si je veux écrire GetHashCode ()? En raison de l'accord de confidentialité, je ne peux pas révéler les exigences réelles de l'entreprise.


lisez-le, c'est un bon article: codeproject.com/ Conseils / 1255596 /…


En fait, soutenir l'égalité est un gâchis. En plus de GetHashCode , vous voudrez également implémenter IEquatable , remplacer Equals (object) , implémenter Equals ( TestClass) , implémentez operator == et implémentez operator! = .


3 Réponses :


4
votes

C'est le comportement attendu avec votre code. Alors, quel est votre problème avec votre code?

Regardez votre classe de clé. Vous remplacez votre GetHashCode () et en plus de cela, vous utilisez une valeur mutable pour calculer la méthode GetHashCode () (très très mauvais :(). P >

Console.WriteLine($"{dict[k].MyProperty} - {k.MyProperty}");

La recherche dans l'implémentation du dictionnaire utilise GetHashCode () de l'objet inséré. Au moment de l'insertion de votre objet, votre GetHashCode () a renvoyé une valeur et cet objet a été inséré dans un bucket . Cependant, après avoir changé votre MyProperty ; GetHashCode () ne renvoie pas la même valeur donc elle ne peut plus être recherchée

C'est là que la recherche a lieu

public class TestClass
{
    public int MyProperty { get; set; }

    public override int GetHashCode() {
        return MyProperty;
    }
}

dict [ k] avait déjà changé sa MyProperty donc GetHashCode () ne renvoie pas la valeur lorsque l'objet a été ajouté pour la première fois au dictionnaire.

Une autre chose très importante est de garder à l'esprit que lorsque vous remplacez GetHashCode () puis remplacez Equals () comme w aune. L'inverse est vrai aussi!


8 commentaires

+1 pour la réponse. Quelle est la meilleure pratique pour GetHashCode ()? Comment généreriez-vous le hashcode dans un tel scénario?


GetHashCode () n'est pas censé utiliser des données mutables pour calculer le hachage. Dans votre cas, vous pouvez rendre MyProperty immuable.


@ HasanEmrahSüngü Il n'utilise pas de type mutable pour le hashcode. int est immuable. Le problème est qu'une propriété participant à GetHashCode ne devrait pas changer au cours du cycle de vie de l'objet.


@swdon, je vérifierais la définition de mutable dans votre cas :) Il utilise une valeur int au moment de l'insertion, puis comme par magie, cette valeur int est modifiée < / code> dans une autre chose. Mot-clé en cours de changement. Au fait, où ai-je utilisé dans ma réponse type mutable ?


Je suppose que ce que vous vouliez dire est de supprimer le setter pour MyProperty , de définir MyProperty dans le constructeur, puis de remplacer GetHashCode () comme < code> retourne MyProperty.GetHashCode () .


@swdon, ce que je voulais dire, c'est de ne pas utiliser de valeurs susceptibles de changer lors du calcul de GetHashCode ()


@radarbob, je ne sais pas si vous commentez ma réponse? Ou certains autres utilisateurs ont supprimé des commentaires?


@ HasanEmrahSüngü, désolé pour la confusion. J'ai supprimé mes commentaires dans une réponse



2
votes

<₹ KeyNotFoundException ... Pourquoi?

La raison principale distillée est que les méthodes Equals et GetHashCode sont incohérentes. Cette situation est corrigée en faisant 2 choses:

  • Remplacer Equals dans TestClass
  • Ne jamais modifier un dictionnaire pendant l'itération
    • C'est que l'objet / valeur clé est en cours de modification

<₹ GetHashCode - Equals déconnecter


<₹TestClass.Equals

Je dis TestClass parce que c'est la clé du dictionnaire. Mais cela s'applique également à ValueClass .

Une classe " Equals et GetHashCode doit être cohérente. Lorsque vous remplacez l'un ou l'autre mais pas les deux, ils ne sont pas cohérents. Nous savons tous que "si vous remplacez Equals , remplacez également GetHashCode ". Nous ne remplaçons jamais GetHashCode mais semblons nous en tirer. Écoutez-moi maintenant et croyez-moi la première fois que vous implémentez IEqualityComparer et IEquatable - remplacez toujours les deux.


Dictionnaire itératif

N'ajoutez ni ne supprimez un élément, ne modifiez pas une clé, ni ne modifiez une valeur (parfois) lors de l'itération .


< gagnantGetHashCode

Le code OP ne fait peut-être pas cela littéralement mais certainement virtuellement car il n'y a pas de remplacement égal à égal.

Voici un algorithme de hachage soigné de C # demi god Eric Lipper


0 commentaires

5
votes

Ce que je veux savoir, c'est pourquoi nous avons cette erreur?

Il existe des règles pour GetHashCode et des règles . Si vous enfreignez les directives, vous obtenez des performances médiocres. Si vous enfreignez les règles , les choses se cassent.

Vous enfreignez les règles . L'une des règles de GetHashCode est lorsqu'un objet est dans un dictionnaire, son code de hachage ne doit pas changer. Une autre règle est que les objets égaux doivent avoir des codes de hachage égaux .

Vous avez enfreint les règles, donc les choses sont brisées. C'est ta faute; n'enfreignez pas les règles.

Quelle est la meilleure pratique pour générer le HashCode afin d'éviter de tels problèmes?

Pour une liste des règles et directives, voir:

https://ericlippert.com/2011/02 / 28 / directives-et-règles-pour-gethashcode /


2 commentaires

C'est ce dont j'avais besoin. Je vous remercie.


@RipalBarot: De rien! Mon objectif en écrivant cet article était de rassembler toutes les règles en un seul endroit pour une consommation facile, car de nombreux développeurs comme vous ne savent pas exactement quelles sont les règles et se retrouvent en difficulté. Heureux d'avoir pu aider.