DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Optimizing Lookups in C# Dictionaries with Case-Insensitive Keys

When working with collections in C#, dictionaries provide an efficient way to store and retrieve data using keys. However, one common issue is case sensitivity in dictionary lookups. By default, dictionaries in C# use case-sensitive key comparisons, which can lead to failed lookups when keys are entered in a different case.

In this article, we'll explore how to solve this problem by using StringComparer.OrdinalIgnoreCase, making dictionary lookups case-insensitive.


Why Use a Dictionary Instead of a List?

Initially, we might store countries in a List<Country>, but searching through a list has a linear time complexity of O(n). As the dataset grows, lookups become slower.

A dictionary (Dictionary<TKey, TValue>) improves lookup efficiency by providing O(1) time complexity, meaning searches remain fast even with large datasets.


Example Scenario: Storing and Looking Up Countries by Code

Let's assume we have a Country class that stores country information:

public class Country
{
    public string Code { get; }
    public string Name { get; }

    public Country(string code, string name)
    {
        Code = code;
        Name = name;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, we need a way to store and retrieve countries by their country code efficiently.


Step 1: Using a Dictionary for Quick Lookups

Instead of using a List<Country>, we can store the data in a dictionary for fast lookups.

public class AppData
{
    public Dictionary<string, Country> AllCountriesByKey { get; private set; }

    public void Initialize()
    {
        var allCountries = new List<Country>
        {
            new Country("AUS", "Australia"),
            new Country("USA", "United States"),
            new Country("CAN", "Canada")
        };

        // Convert List to Dictionary
        AllCountriesByKey = allCountries.ToDictionary(country => country.Code);
    }
}
Enter fullscreen mode Exit fullscreen mode

How It Works:

  • We create a list of Country objects.
  • We use ToDictionary to convert the list into a dictionary.
  • The country code (Code) is used as the key.

This allows us to look up a country in O(1) time instead of scanning a list.


Step 2: Implementing a Lookup Method

Now, let's implement a method to retrieve a country by its code.

public Country GetCountryWithCode(string code)
{
    AllCountriesByKey.TryGetValue(code, out var country);
    return country; // Returns null if the key is not found
}
Enter fullscreen mode Exit fullscreen mode

Why Use TryGetValue Instead of Square Brackets?

Using square brackets (AllCountriesByKey[code]) would throw an exception if the key does not exist. Instead, TryGetValue:
Prevents exceptions

Returns null if the key is missing

Runs in O(1) time complexity


Step 3: Handling Case Sensitivity

Right now, our dictionary is case-sensitive. If a user enters "aus" instead of "AUS", the lookup fails because "aus" is not the same as "AUS".

To fix this, we must make the dictionary case-insensitive when it is created:

public void Initialize()
{
    var allCountries = new List<Country>
    {
        new Country("AUS", "Australia"),
        new Country("USA", "United States"),
        new Country("CAN", "Canada")
    };

    // Convert list to dictionary with case-insensitive keys
    AllCountriesByKey = allCountries.ToDictionary(
        country => country.Code, 
        StringComparer.OrdinalIgnoreCase  // Enables case-insensitive lookups
    );
}
Enter fullscreen mode Exit fullscreen mode

How Does This Fix Case Sensitivity?

  • The second argument to ToDictionary is StringComparer.OrdinalIgnoreCase, which:
    • Ignores case differences ("AUS" == "aus" == "Aus")
    • Optimizes lookups internally

Now, our lookups will work regardless of case.


Step 4: Testing Case-Insensitive Lookups

Now, let's test our updated method:

static void Main()
{
    var appData = new AppData();
    appData.Initialize();

    Console.WriteLine(appData.GetCountryWithCode("AUS")?.Name ?? "Not Found");  // Australia
    Console.WriteLine(appData.GetCountryWithCode("aus")?.Name ?? "Not Found");  // Australia
    Console.WriteLine(appData.GetCountryWithCode("Usa")?.Name ?? "Not Found");  // United States
    Console.WriteLine(appData.GetCountryWithCode("xyz")?.Name ?? "Not Found");  // Not Found
}
Enter fullscreen mode Exit fullscreen mode

Expected Output

Australia
Australia
United States
Not Found
Enter fullscreen mode Exit fullscreen mode

✔ Now, aus, AUS, and Aus all return Australia.

✔ The lookup remains O(1) in performance.

✔ If a country code is not found, it safely returns "Not Found".


Bonus: Trimming Whitespace

What if a user enters " AUS " with spaces? We can trim the input before lookup:

public Country GetCountryWithCode(string code)
{
    AllCountriesByKey.TryGetValue(code.Trim(), out var country);
    return country;
}
Enter fullscreen mode Exit fullscreen mode

Handles extra spaces automatically

Ensures cleaner input before searching


Conclusion

By switching from List<T>.Find() to a dictionary with TryGetValue, we:

  • Improved lookup efficiency from O(n) to O(1).
  • Handled case-insensitive keys using StringComparer.OrdinalIgnoreCase.
  • Made our method safer by avoiding exceptions.
  • Added whitespace trimming for a better user experience.

This approach ensures that country lookups are fast, reliable, and user-friendly! 🚀

Top comments (0)