Heute stand ich vor dem Problem, dass ich ein Byte Array als Index in einem Dictionary in .NET verwenden wollte. Das hat natürlich nicht funktioniert, da der Hash-Code von zwei inhaltsgleichen Byte-Arrays ungleich ist und dadurch der Vergleich beim Zugriff fehl schlägt. Wenn ich also überprüfen möchte, ob ein Key bereits in der Datenstruktur vorhanden ist, wird es immer fehl schlagen, da das erzeugte Objekt mit in den Hashwert eingeht.
Das folgende kleine Beispiel zeigt das Problem:
1 2 3 4 |
byte[] arr1 = new byte[] { 1, 2, 3 }; byte[] arr2 = new byte[] { 1, 2, 3 }; Console.WriteLine(string.Format("Hash Array 1 = '{0}', Hash Array 2 = '{1}'", arr1.GetHashCode(), arr2.GetHashCode())); |
Man erhält als Ergebnis „Hash Array 1 = ‚37121646‘, Hash Array 2 = ‚45592480‘”. Fügt man also das erste Array z.B. einem Dictionary hinzu, wird mit bei einer Abfrage mit ContainsKey(…) ein false erhalten.
Glücklicherweise gibt es seit .NET 3.5 die Möglichkeit, diesen Datentypen im Konstruktor einen IEqualityComparer<T> mitzugeben, der dann den Vergleich übernimmt. Eine Klasse, die dieses Interface implementiert, muss die Methoden Equals(…) und GetHashCode(…) zur Verfügung stellen. Für meinen Anwendungsfall reicht die folgende Implementierung:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
internal class ByteArrayComparer : IEqualityComparer<byte[]> { public bool Equals(byte[] x, byte[] y) { if (x == null || y == null) { return x == y; } return x.SequenceEqual(y); } public int GetHashCode(byte[] obj) { return BitConverter.ToString(obj).GetHashCode(); } } |
Wichtig ist hier speziell die GetHashCode(…) Methode. Hier wird der Inhalt des Arrays mit dem BitConverter in einen Hex-String konvertiert und von diesem String der HashCode zurückgegeben. Der Hash wird dann also über den Inhalt gebildet und ist dann bei beiden Arrays gleich. Unsere neue Implementierung wird dem Konstruktor dann mit übergeben und dann in der entsprechenden Datenstruktur verwendet. Das folgende Beispiel zeigt die Anwendung und ein „Vorher / Nachher“ Vergleich:
1 2 3 4 5 6 7 8 9 10 |
byte[] arr1 = new byte[] { 1, 2, 3 }; byte[] arr2 = new byte[] { 1, 2, 3 }; //ohne IEqualityComparer HashSet<byte[]> test = new HashSet<byte[]>() { arr1 }; Console.WriteLine(string.Format("Ohne IEqualityComparer - Contains = {0}", test.Contains(arr2))); //mit IEqualityComparer test = new HashSet<byte[]>(new ByteArrayComparer()) { arr1 }; Console.WriteLine(string.Format("Mit IEqualityComparer - Contains = {0}", test.Contains(arr2))); |
Startet man das Programm, erhält man folgende Ausgabe „Ohne IEqualityComparer – Contains = False. Mit IEqualityComparer – Contains = True“. Problem gelöst :-).