366

I have an application that use managed dlls. One of those dlls return a generic dictionary:

Dictionary<string, int> MyDictionary;  

The dictionary contains keys with upper and lower case.

On another side I am getting a list of potential keys (string) however I cannot guarantee the case. I am trying to get the value in the dictionary using the keys. But of course the following will fail since I have a case mismatch:

bool Success = MyDictionary.TryGetValue( MyIndex, out TheValue );  

I was hoping the TryGetValue would have an ignore case flag like mentioned in the MSDN doc, but it seems this is not valid for generic dictionaries.

Is there a way to get the value of that dictionary ignoring the key case? Is there a better workaround than creating a new copy of the dictionary with the proper StringComparer.OrdinalIgnoreCase parameter?

TocToc
  • 4,739
  • 4
  • 29
  • 34
  • Related posts - [Case-INsensitive Dictionary with string key-type in C#](https://stackoverflow.com/q/13988643/465053), [c# Dictionary: making the Key case-insensitive through declarations](https://stackoverflow.com/q/6676245/465053) – RBT Apr 06 '18 at 11:22

4 Answers4

748

There's no way to specify a StringComparer at the point where you try to get a value. If you think about it, "foo".GetHashCode() and "FOO".GetHashCode() are totally different so there's no reasonable way you could implement a case-insensitive get on a case-sensitive hash map.

You can, however, create a case-insensitive dictionary in the first place using:-

var comparer = StringComparer.OrdinalIgnoreCase;
var caseInsensitiveDictionary = new Dictionary<string, int>(comparer);

Or create a new case-insensitive dictionary with the contents of an existing case-sensitive dictionary (if you're sure there are no case collisions):-

var oldDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var newDictionary = new Dictionary<string, int>(oldDictionary, comparer);

This new dictionary then uses the GetHashCode() implementation on StringComparer.OrdinalIgnoreCase so comparer.GetHashCode("foo") and comparer.GetHashcode("FOO") give you the same value.

Alternately, if there are only a few elements in the dictionary, and/or you only need to lookup once or twice, you can treat the original dictionary as an IEnumerable<KeyValuePair<TKey, TValue>> and just iterate over it:-

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var value = myDictionary.FirstOrDefault(x => String.Equals(x.Key, myKey, comparer)).Value;

Or if you prefer, without the LINQ:-

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
int? value;
foreach (var element in myDictionary)
{
  if (String.Equals(element.Key, myKey, comparer))
  {
    value = element.Value;
    break;
  }
}

This saves you the cost of creating a new data structure, but in return the cost of a lookup is O(n) instead of O(1).

Iain Galloway
  • 18,669
  • 6
  • 52
  • 73
  • 1
    There is no reason to keep the old dictionary around and instantiate the new one as any case-collisions will cause it to explode. If you know you won't get collisions then you may as well use case insensitive from the start. – Rhys Bevilaqua Jun 20 '13 at 03:44
  • 4
    It's been ten years that I've been using .NET and I now just figured this out!! Why do you use Ordinal instead of CurrentCulture? – Jordan Feb 19 '15 at 20:04
  • Well, it depends on the behaviour you want. If the user is providing the key via the UI (or if you need to consider e.g. ss and ß equal) then you'll need to use a different culture, but given that the value is being used as the key for a hashmap coming from an external dependency, I think 'OrdinalCulture' is a reasonable assumption. – Iain Galloway May 18 '15 at 16:18
  • @RhysBevilaqua These dictionaries are often returned by other stuff, though. Knowing there will be no case collisions in the stuff you're dealing with doesn't magically make existing systems create their dictionaries as case insensitive. – Nyerguds Mar 10 '16 at 08:20
  • "You can, however, create a case-insensitive dictionary in the first place." But what if the Dictionary is part of an Interface? Is there any way to force this attribute? – Mike Lowery Apr 28 '17 at 15:44
  • You mean is there any way to define a property of an interface such that implementations of that interface must provide a case-insensitive implementation? If so, you should ask a new question, and link to it here. – Iain Galloway Apr 30 '17 at 10:14
  • 1
    `default(KeyValuePair)` is not `null` -- it's a `KeyValuePair` where `Key=default(T)` and `Value=default(U)`. So you can't use the `?.` operator in the LINQ example; you'll need to grab `FirstOrDefault()` and then (for this particular case) check to see if `Key == null`. – asherber Jul 29 '19 at 15:30
71

For you LINQers out there that never use a regular dictionary constructor

myCollection.ToDictionary(x => x.PartNumber, x => x.PartDescription, StringComparer.OrdinalIgnoreCase)
Derpy
  • 1,458
  • 11
  • 15
  • 2
    C# also has the Constructor: `Dictionary(IDictionary dictionary, IEqualityComparer? comparer);` that lets you effectively recreate the same dictionary with a new Comparer. – Max Hay Aug 08 '22 at 14:07
39

There is much simpler way:

using System;
using System.Collections.Generic;
....
var caseInsensitiveDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
Major
  • 5,948
  • 2
  • 45
  • 60
  • 1
    This would work for `Dictionary`, the type of the values isn't important here. Also, what would case insensitive even mean if the key wasn't a string? – Matt Burland Nov 06 '20 at 18:44
  • 1
    As someone who has lots of sorted dictionaries with string keys, why this didn't get more upvote is lost on me... – Keith Vinson Apr 16 '21 at 19:16
12

Its not very elegant but in case you cant change the creation of dictionary, and all you need is a dirty hack, how about this:

var item = MyDictionary.Where(x => x.Key.ToLower() == MyIndex.ToLower()).FirstOrDefault();
    if (item != null)
    {
        TheValue = item.Value;
    }
Shoham
  • 7,014
  • 8
  • 40
  • 40
  • 16
    or just this: new Dictionary(otherDict, StringComparer.CurrentCultureIgnoreCase); – Jordan Feb 19 '15 at 20:05
  • 11
    As per "Best Practices for Using Strings in the .NET Framework" use `ToUpperInvariant` instead of `ToLower`. https://msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspx – Fred Apr 25 '16 at 10:59
  • This was good for me, where I had to retrospectively check keys in an insensitive manner. I streamlined it a bit more `var item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());` – Jay Jul 12 '16 at 14:41
  • 3
    Why not just `dict.Keys.Contains("bla", appropriate comparer)` ? Furthermore, you wont get null for FirstOrDefault since keyvaluepair in C# is a struct. – nawfal Jul 23 '19 at 11:32
  • This is a BETTER answer for folks who want to do a SINGLE case-insensitive lookup on a dictionary that is normally case-sensitive. GetMemberBinder.IgnoreCase much? – KatDevsGames May 08 '23 at 02:16