Get C# Dictionary Element By Index

Get Dictionary Element By Index Banner Image

Introduction

It may surprise you that dictionaries in C# also has a method to get the element at that index. While it may sound like this is a good idea and that it is possible to do. The dictionaries were not designed with this in mind and so they should be used sparingly or an alternate collection type should be used. First, let's look at how we can do this.

ElementAt Method

ElementAt will return the dictionary element at that position in the order in which it was inserted into the dictionary. This example has 4 entries and I can select each entry by ElementAt in the reverse order. See below.

Dictionary<string, string> contactInfo = new Dictionary<string, string>() { { "Liam", "383-212-2121" }, { "Olivia", "433-261-8483" }, { "Oliver", "189-833-2899" }, { "Emma", "3-212-2121" } };

Console.WriteLine($"ElementAt(4):" + contactInfo.ElementAt(3).Key);
Console.WriteLine($"ElementAt(3):" + contactInfo.ElementAt(2).Key);
Console.WriteLine($"ElementAt(2):" + contactInfo.ElementAt(1).Key);
Console.WriteLine($"ElementAt(1):" + contactInfo.ElementAt(0).Key);

Code Output
ElementAt(4):Emma
ElementAt(3):Oliver
ElementAt(2):Olivia
ElementAt(1):Liam

This works as expected to print the items in reverse order.

Performance

This most important consideration between ElementAt and Indexer. The testing methods are as follows.

Below is a summary table of the results.

Performance Summary Table
Dictionary SizeElementAt SpeedIndexer Speed
5000051996ms16ms

Since the dictionary was not optimized to use ElementAt so I'm going to test the performance of ElementAt compared to the Dictionary indexer. This test will loop through all the elements of the dictionary using the ElementAt or Indexer and see how they compare. Below are the test parameters for the comparison.

Test Parameters
Test ParametersTotal
Tests10
Function Calls Per Test10
Objects Per Function Call50 Thousand

ElementAt Speed Test Code

void TestMethod(Dictionary<string, DateTime> dictionary)
{
    for (int i = 0; i < dictionary.Count; i++)
    {
        DateTime dateTime = dictionary.ElementAt(i).Value.AddHours(1);//Get data through the ElementAt
        double ticks = dateTime.Ticks;
    }
}

Indexer Speed Test Code

void TestMethod(Dictionary<string, DateTime> dictionary)
{
    foreach (string key in keysList)
    {
        DateTime dateTime = dictionary[key].AddHours(1);//Get data through the Indexer
        double ticks = dateTime.Ticks;
    }
}
ElementAt Code Output
Test 1:Function Calls:10, In 0m 52s 445ms
Test 2:Function Calls:10, In 0m 51s 671ms
Test 3:Function Calls:10, In 0m 52s 291ms
Test 4:Function Calls:10, In 0m 51s 12ms
Test 5:Function Calls:10, In 0m 52s 206ms
Test 6:Function Calls:10, In 0m 51s 644ms
Test 7:Function Calls:10, In 0m 52s 622ms
Test 8:Function Calls:10, In 0m 52s 103ms
Test 9:Function Calls:10, In 0m 52s 284ms
Test 10:Function Calls:10, In 0m 51s 680ms
Dictionary ElementAt Average Speed:51996ms, In 10 Tests
Indexer Code Output
Test 1:Function Calls:10, In 0m 0s 19ms
Test 2:Function Calls:10, In 0m 0s 17ms
Test 3:Function Calls:10, In 0m 0s 15ms
Test 4:Function Calls:10, In 0m 0s 15ms
Test 5:Function Calls:10, In 0m 0s 15ms
Test 6:Function Calls:10, In 0m 0s 16ms
Test 7:Function Calls:10, In 0m 0s 15ms
Test 8:Function Calls:10, In 0m 0s 13ms
Test 9:Function Calls:10, In 0m 0s 14ms
Test 10:Function Calls:10, In 0m 0s 13ms
Dictionary Indexer Average Speed:16ms, In 10 Tests
ElemeentAt Full Code Example
using System.Diagnostics;

int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 10;//Number of function calls made per test
int numberOfObjectsToCreate = 50000;//Number test objects
string testName = "Dictionary ElementAt";//Test name to print to average
void TestMethod(Dictionary<string, DateTime> dictionary)
{
    for (int i = 0; i < dictionary.Count; i++)
    {
        DateTime dateTime = dictionary.ElementAt(i).Value.AddHours(1);//Get data through the ElementAt
        double ticks = dateTime.Ticks;
    }
}

List<double> testSpeedList = new List<double>();
for (int testIndex = 0; testIndex < numberOfTests; testIndex++)
{
    testSpeedList.Add(StartTest(testIndex));
}
Console.WriteLine($"{testName} Average Speed:{Math.Round(testSpeedList.Average())}ms, In {numberOfTests} Tests");

double StartTest(int testIndex)
{
    Stopwatch stopwatch = new Stopwatch();
    Dictionary<string, DateTime> testData = GetDictionaryData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Test {testIndex + 1}:Function Calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

Dictionary<string, DateTime> GetDictionaryData()
{
    Dictionary<string, DateTime> testData = new Dictionary<string, DateTime>();
    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        if (!testData.ContainsKey(dateString))
        {
            testData[dateString] = date;
        }
    }
    return testData;
}

int GetRandomInt(int minNumber, int maxNumber)
{
    Random random = new Random();//Create Random class
    int randomInt = random.Next(minNumber, maxNumber);//Get a random number between 1 and the maxnumber
    return randomInt;
}

DateTime GetDateTime()
{
    DateTime dateTime = new DateTime(GetRandomInt(1, 6000), GetRandomInt(1, 12), GetRandomInt(1, 29));
    return dateTime;
}

Indexer Full Code Example

using System.Diagnostics;

int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 10;//Number of function calls made per test
int numberOfObjectsToCreate = 50000;//Number test objects
string testName = "Dictionary Indexer";//Test name to print to average
List<string> keysList = new List<string>();
void TestMethod(Dictionary<string, DateTime> dictionary)
{
    foreach (string key in keysList)
    {
        DateTime dateTime = dictionary[key].AddHours(1);//Get data through the Indexer
        double ticks = dateTime.Ticks;
    }
}

List<double> testSpeedList = new List<double>();
for (int testIndex = 0; testIndex < numberOfTests; testIndex++)
{
    testSpeedList.Add(StartTest(testIndex));
}
Console.WriteLine($"{testName} Average Speed:{Math.Round(testSpeedList.Average())}ms, In {numberOfTests} Tests");

double StartTest(int testIndex)
{
    Stopwatch stopwatch = new Stopwatch();
    Dictionary<string, DateTime> testData = GetDictionaryData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData);//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Test {testIndex + 1}:Function Calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

Dictionary<string, DateTime> GetDictionaryData()
{
    Dictionary<string, DateTime> testData = new Dictionary<string, DateTime>();
    keysList = new List<string>();
    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        if (!testData.ContainsKey(dateString))
        {
            testData[dateString] = date;
            keysList.Add(dateString);
        }
    }
    return testData;
}

int GetRandomInt(int minNumber, int maxNumber)
{
    Random random = new Random();//Create Random class
    int randomInt = random.Next(minNumber, maxNumber);//Get a random number between 1 and the maxnumber
    return randomInt;
}

DateTime GetDateTime()
{
    DateTime dateTime = new DateTime(GetRandomInt(1, 6000), GetRandomInt(1, 12), GetRandomInt(1, 29));
    return dateTime;
}

List Alternative

Another way to go about this issue is to use a list. If you have numerical indices that you want to hit then a list or array would be better than trying to do that with a dictionary. See the example code below.

List Indexer Example Code

void TestMethod(List<DateTime> list)
{
    for (int i = 0; i < list.Count; i++)
    {
        DateTime dateTime = list[i].AddHours(1);//Get data through the list indexer
        double ticks = dateTime.Ticks;
    }
}
Full List Indexer Speed Code Example
using System.Diagnostics;

int numberOfTests = 10;//Number of tests 
int numberOfFunctionCalls = 10;//Number of function calls made per test
int numberOfObjectsToCreate = 50000;//Number test objects
string testName = "List Indexer";//Test name to print to average
void TestMethod(List<DateTime> list)
{
    for (int i = 0; i < list.Count; i++)
    {
        DateTime dateTime = list[i].AddHours(1);//Get data through the list indexer
        double ticks = dateTime.Ticks;
    }
}

List<double> testSpeedList = new List<double>();
for (int testIndex = 0; testIndex < numberOfTests; testIndex++)
{
    testSpeedList.Add(StartTest(testIndex));
}
Console.WriteLine($"{testName} Average Speed:{Math.Round(testSpeedList.Average())}ms, In {numberOfTests} Tests");

double StartTest(int testIndex)
{
    Stopwatch stopwatch = new Stopwatch();
    Dictionary<string, DateTime> testData = GetDictionaryData();//Get intial random generated data
    for (int i = 0; i < numberOfFunctionCalls; i++)
    {
        stopwatch.Start();//Start the Stopwatch timer
        TestMethod(testData.Values.ToList());//
        stopwatch.Stop();//Stop the Stopwatch timer
    }
    stopwatch.Stop();//Stop the Stopwatch timer
    Console.WriteLine($"Test {testIndex + 1}:Function Calls:{numberOfFunctionCalls}, In {stopwatch.Elapsed.Minutes}m {stopwatch.Elapsed.Seconds}s {stopwatch.Elapsed.Milliseconds}ms");
    return stopwatch.Elapsed.TotalMilliseconds;
}

Dictionary<string, DateTime> GetDictionaryData()
{
    Dictionary<string, DateTime> testData = new Dictionary<string, DateTime>();
    while (numberOfObjectsToCreate > testData.Count)
    {
        DateTime date = GetDateTime();
        string dateString = date.ToShortDateString();
        if (!testData.ContainsKey(dateString))
        {
            testData[dateString] = date;
        }
    }
    return testData;
}

int GetRandomInt(int minNumber, int maxNumber)
{
    Random random = new Random();//Create Random class
    int randomInt = random.Next(minNumber, maxNumber);//Get a random number between 1 and the maxnumber
    return randomInt;
}

DateTime GetDateTime()
{
    DateTime dateTime = new DateTime(GetRandomInt(1, 6000), GetRandomInt(1, 12), GetRandomInt(1, 29));
    return dateTime;
}
List Indexer Example Code
Test 1:Function Calls:10, In 0m 0s 5ms
Test 2:Function Calls:10, In 0m 0s 5ms
Test 3:Function Calls:10, In 0m 0s 3ms
Test 4:Function Calls:10, In 0m 0s 3ms
Test 5:Function Calls:10, In 0m 0s 5ms
Test 6:Function Calls:10, In 0m 0s 3ms
Test 7:Function Calls:10, In 0m 0s 3ms
Test 8:Function Calls:10, In 0m 0s 3ms
Test 9:Function Calls:10, In 0m 0s 3ms
Test 10:Function Calls:10, In 0m 0s 3ms
List Indexer Average Speed:4ms, In 10 Tests

List Indexer is a good alternative to ElementAt because it only takes a few milliseconds to complete the test which is still way better than ElementAt.

Conclusion

ElementAt is extremely slow and should be avoided. The difference in speed between ElementAt and the indexer is night and day. From 51 seconds to just milliseconds is the difference in speed in my testing. If you need a collection type to access numeric indexes then you should use arrays or lists.

Get Latest Updates