3

As we all know, Dictionary in C# theoretically does not preserve the order of the inserted elements.

For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair<TKey,TValue> structure representing a value and its key. The order in which the items are returned is undefined.

Source

However, when I insert random keys into the dictionary and read them back in a foreach loop, they always come back in the insertion order.

Random rand = new Random();
var map = new Dictionary<int, int>();
for (int i = 0; i < 10; ++i)
{
    var key = rand.Next(1000000000);
    var value = 0;

    Console.WriteLine($"Key: {key}");

    if (map.ContainsKey(key))
        continue;

    map.Add(key,value);
}

Console.WriteLine("Reading Back..");
foreach (var pair in map)
{
    Console.WriteLine($"Key: {pair.Key}");
}

The output from this code is like this:

Key: 593548784  
Key: 765023454  
Key: 1460074  
Key: 913286745  
Key: 804757753  
Key: 281489700  
Key: 395818098  
Key: 227086287  
Key: 323530161  
Key: 629618868

Reading Back..

Key: 593548784  
Key: 765023454  
Key: 1460074  
Key: 913286745  
Key: 804757753  
Key: 281489700  
Key: 395818098  
Key: 227086287  
Key: 323530161  
Key: 629618868

I'm surprised because I thought when elements are inserted to dictionary, hash key is determined by taking mod N of the key field, which would mean the order would be lost (unless N is extremely large).

Does anyone know why the behavior is like this?

gunr2171
  • 16,104
  • 25
  • 61
  • 88
  • Look at the [source](https://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs,fd1acf96113fbda9). There is a growing `entries` array but also a `freeList` index. Mix Remove() and Add() a little. – H H Nov 25 '19 at 19:47
  • There is a general pattern I've noticed with such documentation. If they make a point to say something like that the order of items may not always be consistent, it almost always means that it usually is consistent, just don't count on it because there are cases identified where that isn't the case. If all you're doing is adding to a dictionary then the order will be preserved. Start mixing adding and removal and it gets a bit weirder – DetectivePikachu Nov 25 '19 at 19:53
  • 1
    Non-determinism and undefined behavior are radically different concepts. If it was genuinely non-deterministic, the order would likely be different every time. Undefined behavior means, "We're free to change how this works in the future, so don't depend on what you're seeing." – Clay Nov 25 '19 at 19:53
  • While linked https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior (as duplicate) is for C/C++ the explanation of "what is undefined" is exactly the same- @Clay pointed out "undefined" is not "randomized"/"non-deterministic" ... – Alexei Levenkov Nov 25 '19 at 20:05
  • I would say the question currently being marked as a duplicate of this one is too general, there're much closer questions like for example this one: https://stackoverflow.com/questions/1453190/does-the-enumerator-of-a-dictionarytkey-tvalue-return-key-value-pairs-in-the. – David Ferenczy Rogožan Nov 25 '19 at 20:45
  • @DawidFerenczyRogožan - you can edit and add to the duplicates – H H Nov 25 '19 at 22:37
  • @HenkHolterman I'm not sure how, I don't see where to put it on the question edit form. There're only the title, body, tags and edit summary fields. And I thought it's possible to mark only a single question as a duplicate. – David Ferenczy Rogožan Nov 25 '19 at 22:45
  • I can see a separate _edit_ link in the duplicates box. – H H Nov 25 '19 at 22:55

2 Answers2

3

The fact that the Dictionary doesn't support sorting doesn't mean that you will get your data in a random order every time you read it. You'll usually iterate through it in the same order as it was inserted into, but it's not guaranteed:

For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair structure representing a value and its key. The order in which the items are returned is undefined.

The original order will probably be lost when you start removing, inserting and adding additional entries. You can find more details in answers of this question.

Here is sample code where items are removed

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace SO59038883
{
    internal class Program
    {
        static void Main()
        {
            // keep track of the key that have been inserted
            // so we can remove a random
            var trackedKeys = new List<int>();

            var rand = new Random();
            var map = new Dictionary<int, int>();
            for (var i = 0; i < 20; i++)
            {
                var key = rand.Next(1000000000);
                trackedKeys.Add(key);
                Console.WriteLine($"Key: {key}");

                if (map.ContainsKey(key))
                    continue;

                map.Add(key, i);

                // Let's remove three random keys
                // at set interval
                if (i == 5 || i == 10 || i == 15)
                {
                    // get a random key from our tracked key list
                    var index = rand.Next(trackedKeys.Count);
                    var keyToRemove = trackedKeys[index];
                    map.Remove(keyToRemove);
                }
            }

            Console.WriteLine("Reading Back..");
            foreach (var pair in map)
            {
                Console.WriteLine($"Key: {pair.Key} - Value: {pair.Value}");
            }

            if (Debugger.IsAttached)
            {
                Console.WriteLine("Press any key to continue...");
                Console.ReadKey();
            }
        }
    }
}

Output showing order once items have been removed.

David Ferenczy Rogožan
  • 23,966
  • 9
  • 79
  • 68
3

The way Dictionary is implemented doesn't store items directly into a hash table, but instead uses a hash table of integers which is then used to identify items within a growing array. If nothing is ever removed from a Dictionary, this array will contain items in the order that they were added; such behavior has been consistent from the beginnings of .NET. If items are removed, the order in which future items get placed in the array may be much less predictable, and I would not particularly expect it to be consistent among all .NET versions.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • And moreover, there is no promise for any behaviour. Experimantation gives you a snapshot that could change in the next release. – H H Nov 25 '19 at 19:45
  • @HenkHolterman: The described behavior is one that MS should document for the no-items-ever-deleted scenario, IMHO. In scenarios where a dictionary is built by importing data from a file, having dumps of the dictionary match the order of items in the file is nicer than having them output data in jumbled sequence, and having `Dictionary` keep things in order for the no-items-ever-deleted scenario is cheaper than any means via which user code could keep items in order while supporting random lookups. – supercat Nov 25 '19 at 19:53