1

I have a concurrent dictionary which maps an array of characters to an integer

var dictionary = new ConcurrentDictionary<char[], int>();

It works fine if I map an array

var key = new char[] { 'A', 'B' };
var value = 8;
dictionary.TryAdd(key, value);
Console.WriteLine(dictionary[key]);

gives

8

But if I use an array created from a queue it doesn't work

var qu = new Queue<char>();
qu.Enqueue('A');
qu.Enqueue('B');
Console.WriteLine(dictionary[qu.ToArray()]);

gives

Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key 'System.Char[]' was not present in the dictionary.
   at System.Collections.Concurrent.ConcurrentDictionary`2.ThrowKeyNotFoundException(TKey key)
   at System.Collections.Concurrent.ConcurrentDictionary`2.get_Item(TKey key)
   at Program.<Main>$(String[] args) in C:\Users\User\Desktop\SubstitutionCipher\Program.cs:line 65

C:\Users\User\Desktop\SubstitutionCipher\bin\Debug\net6.0\SubstitutionCipher.exe (process 18688) exited with code -532462766.

What is wrong and how to fix it?

Fenix FVE
  • 53
  • 4
  • 5
    Default array equality is just reference equality, i.e. two arrays are only equal if they both are actually the same one array instance. Either use string instead of char[] for the dictionary keys, or write a custom EqualityComparer that does the equality comparison you need and pass it to the dictionary constructor. A question that pops up in my mind however is: What is the motivation for using char arrays as dictionary keys? Arrays are mutable, and therefore are normally bad candidates for dictionary keys. –  Sep 27 '22 at 14:34
  • @MySkullCaveIsADarkPlace Would it work if I use List instead? – Fenix FVE Sep 27 '22 at 14:38
  • @FenixFVE Same issue as the arrays – Irwene Sep 27 '22 at 14:40
  • Nope. It wouldn't. As far as default equality is concerned, the standard collections never do equality comparisions by their content, but always only by their own instance identity. You could of course write your own collection type with a custom equality comparison logic, however, such mutable collections are still a bad choice for dictionary keys (because mutating a collection/array used as a dictionary key would then royally f*ck up dictionary lookups if they were based on equality comparison of the collection's content) –  Sep 27 '22 at 14:40
  • @MySkullCaveIsADarkPlace I need to iterate through text and count frequency of n-grams in text. What is a better solution? – Fenix FVE Sep 27 '22 at 14:42
  • A que is a class object while an array is not. Using ToArray() creates a makes an array of the class and doesn't make it a char[] unless you do a cast. – jdweng Sep 27 '22 at 14:42
  • @jdweng No, `Queue.ToArray()` returns a `T[]`. OP does not have a compile-time error. – Johnathan Barclay Sep 27 '22 at 14:44
  • "_What is a better solution?"_ Use a string instead of char[] as dictionary keys. You can turn a char[] array into a string. The string type has a constructor overload just for this purpose. And strings are immutable, so they are safe to use as dictionary keys... –  Sep 27 '22 at 14:44
  • @MySkullCaveIsADarkPlace True, thanks for that, took a specific case for a generality MSDN article on the subject https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity – Irwene Sep 27 '22 at 14:49

1 Answers1

4

In order to find the item in the dictionary, the dictionary gets a hash code and does an equality check. For the char-arrays, this leads to a reference comparison. Even though the arrays do contain the same items, they are different objects, so the reference comparison fails. Hence, the item is not found.

There are several ways to solve this:

  • Either you help the dictionary to find the item by implementing a custom equality comparer - that is a class that implements IEqualityComparer<T> and is used as a constructor parameter when creating the dictionary.
  • Or you use a type that already implements a comparison that fits your needs. In the case of a char-Array, you could use a string as a simple alternative, e.g.
var key = new string(new char[] { 'A', 'B' });
var value = 8;
dictionary.TryAdd(key, value);
Console.WriteLine(dictionary[key]);
var qu = new Queue<char>();
qu.Enqueue('A');
qu.Enqueue('B');
key = new string(qu.ToArray());
Console.WriteLine(dictionary[key]);

See this fiddle to test.

Markus
  • 20,838
  • 4
  • 31
  • 55