41

MSDN has no information on the order preserving properties of data structures. So I've been making the assumption that:

  • HashTable and HashSet do not preserve the insertion order (i.e. the "hash" in there is a giveaway)
  • Dictionary and List do preserve the insertion order.

From this I extrapolate that if I have a Dictionary<double, double> foo that defines a curve, foo.Keys.ToList() and foo.Values.ToList() will give me an ordered list of the scope and domain of that curve without messing about with it?

Palec
  • 12,743
  • 8
  • 69
  • 138
Oren Mazor
  • 4,437
  • 2
  • 29
  • 28

4 Answers4

54

You should NOT expect either the keys or values in a regular Dictionary<TKey,TValue> to be maintained in any order. In a SortedDictionary<TKey,TValue> the keys and values are maintained in order by the value of the key - this is not the same as insertion order.

The only built-in dictionary in the .NET framework that preserves insertion order is System.Collections.Specialized.OrderedDictionary. Unfortunately, this class is not generic - however, it's not terribly hard to write a generic wrapper around it. Keep in mind, when dealing with value types (like int or double) it will result in boxing of the keys/values (generic dictionaries don't impose boxing on value types).

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
LBushkin
  • 129,300
  • 32
  • 216
  • 265
  • 2
    this is what I initially assumed, but then some more googling seemed to have suggested that Dictionary DOES preserve insertion order. OrderedDictionary it is. thanks! – Oren Mazor Apr 27 '10 at 16:36
  • 1
    Oren, can you paste some link that you've found about Dictionary preserving insertion order? – AgostinoX Jun 10 '20 at 15:54
3

I have performed extensive testing on Dictionary<TKey, TValue> ordering. I have discovered this: The order is maintained as far as i add new key/value pairs to the collection. If i replace one already existing element, or even worse if i delete some elements, the following insertions ARE NOT GUARANTEED TO BE IN TAIL of the dictionary.

In a specific case, i had a very nasty bug for the assumption that the order of dictionary keys were exactly as inserted, and i have discovered the bug to be an unexpected reallocation of keys, thus the order is not preserved.

To preserve the order, you must wrap all write access to the dictionary (except add) and write a pseudo-algorithm like this:

1- Copy the dictionary to a regular list (of key/value pairs) 2- Remove/replace/modify the list 3- Clear the dictionary and re-add all key/value pairs back to the dictionary

You are willing to use Dictionary constructor to pre-allocate enough elements to store your actual list, equal to list.count+1

This ugly way solved the bug, but i can guarantee you that Dictionary<TKey, TValue> wont preserve the key insertion order except when just inserting at tail.

Dharman
  • 30,962
  • 25
  • 85
  • 135
user2991288
  • 361
  • 2
  • 5
-1

As @Anton pointed out the Dictionary<TKey,TValue> is an unordered collection. The proper returning of your values is coincidence and will eventually fail. If you need to have an ordered hashtable you should use SortedDictionary<TKey,TValue>

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 16
    `SortedDictionary` does not preserve insertion order, it maintains items based on a natural ordering of the keys. There is a non-generic `OrderedDictionary` class in the `System.Collections.Specialized` namespace which *does* preserve insertion order at the cost of additional storage. (It's basically implemented as a hash table and a list). – LBushkin Apr 27 '10 at 16:09
  • Thanks for differentiating between "insertion order" and "sorted order". Everywhere on internet folks are confusing this concept with sorted dictionaries in C# :-/ – Monnie_tester Jul 02 '21 at 21:48
-12

By all means, rely on Dictionary<TKey, TValue> to preserve ordering!

While Dictionary<TKey, TValue> clearly states that enumeration ordering is undefined, we tested that it indeed does preserve insertion ordering (at least as long as you do not remove items from it). If someone can provide a test that disproves it, we would be very interested since our production code relies on it.

You might take the same approach and save yourself some effort and your customer some money.

Sure, Microsoft might change the Dictionary implementation in a future .NET version, but if that happens, your automated test will detect it, and you can replace Dictionary with another container at that time, right?

  • 13
    You're relying on this feature _in production code_??? Are you mad? Dictionary does specifically _not_ preserve insertion order, the fact that it does in your case is very much an edge case & side-effect of how you are using it. You can see this by having a look at the `Insert` method in reflector - it uses the key hash code to determine where to put the entry in the backing arrays, and then just iterates through the arrays in ther `Enumerator` – thecoop Sep 07 '10 at 14:10
  • 3
    @thecoop: I do agree that relying on temporal ordering is dangerous, but there is more going on that causes the `Dictionary` to behave this way for more than just an edge case. It actually maintains two arrays. One for the actual items stored (entries) and one for the index into the former (buckets). New items are always added to the next available slot in the entry array regardless of what happens with the hash codes and the bucket array. Even if you remove an item the order of that entry array is still deterministic (though not temporal anymore). Again, I would never rely on this detail. – Brian Gideon Sep 07 '10 at 17:46
  • 3
    Trust me, we were as surprised as you, but try as we might, we just couldn't write a test that broke our production code (on that point, anyway, hehe). We also wrote a test to insert a million items in random order in a Dictionary, iterate it and assert that the elements come out in the same order as inserted. And again, autotests will tell us if for some reason we cannot rely on it in the future. – Kurt Nørre Sep 08 '10 at 07:24
  • 5
    I think the point here is that, while it may work at the moment, it might not work in the future. You may have good autotests that will catch this, but this is just going to be extra work that could have been avoided if you started off with a better design that is based on a collection that does guarantee preserving the order (e.g. `OrderedDictionary`). So I do not think this is good advice for someone who is at the stage of designing their software. – Ben Nov 10 '14 at 12:48
  • I cannot believe this, LOL. This "feature" of the Dictionary is the only reason why our code was working. We build a dictionary, then craft an exec call to a stored procedure, however, we omit null values and we were using `exec proc @Param1, @Param2, etc.` instead of `exec proc @Param1Name=@Param1Value, @Param2Name = @Param2Value`, so the values were being passed by position instead of by name. So I'm like, how the hell has that been working unless Dictionary enumeration is preserving insertion order... that's the only way the positions could be passed consistently. Sure enough, it DOES! – Triynko Mar 29 '16 at 18:48
  • Internally, it seems to maintain a "buckets" array for the hashes, and a separate "entries" array for the values, which is filled up sequentially. The Dictionary enumerator iterates over the "entries" array (not the "buckets") array, so it ends up preserving insertion order. Still, it's definitely NOT cool to rely on this internal/undefined behavior. – Triynko Mar 29 '16 at 18:49
  • 1
    Yah you're trusting Microsoft to not change the behavior on you. If they don't guarantee order, your fate is in their hands. – Jroonk Jul 18 '20 at 08:40
  • Step 1: `Dictionary` - Step 2: `Add(1, "")` - Step 3: `Add(2, "")` - Step 4: `Remove(1)` - Step 5: `Add(3, "")` - Key iteration result: `[3, 2]`. The thoroughness of your "testing" is questionable. It is trivial to construct a non-working example if you look at the implementation. – dialer Apr 24 '23 at 15:52