通用字典的不区分大小写访问

369
我是一位有用的助手,可以为您翻译文本。以下是您需要翻译的内容:

我有一个应用程序使用托管的dll。其中一个dll返回一个通用字典:

Dictionary<string, int> MyDictionary;  

这个字典中包含大小写的键。

另一方面,我正在获取一个潜在的键列表(字符串),但无法保证大小写。我试图使用这些键从字典中获取值。但是,由于大小写不匹配,以下操作肯定会失败:

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

我希望TryGetValue有一个忽略大小写的标志,就像MSDN文档中提到的那样,但是似乎这对于通用字典无效。
有没有一种方法可以忽略键名大小写获取该字典的值? 除了使用正确的StringComparer.OrdinalIgnoreCase参数创建字典的新副本之外,是否有更好的解决方法?

4个回答

754

在尝试获取值的位置没有办法指定StringComparer。如果您仔细想一想,"foo".GetHashCode()"FOO".GetHashCode()是完全不同的,因此在区分大小写的哈希映射中实现不区分大小写的获取是没有合理方式的。

但是,您可以使用以下方法创建一个不区分大小写的字典:

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

或者使用现有的区分大小写的字典内容创建一个不区分大小写的新字典(如果您确定没有大小写冲突):

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

新词典使用 StringComparer.OrdinalIgnoreCase 上的 GetHashCode() 方法,所以 comparer.GetHashCode("foo")comparer.GetHashcode("FOO") 将给出相同的值。

或者,如果字典中只有几个元素,并且/或者您只需要查找一次或两次,您可以将原始字典视为 IEnumerable<KeyValuePair<TKey, TValue>> 并对其进行迭代:-

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

如果您喜欢,也可以不使用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;
  }
}

这种方法可以节省创建新数据结构的成本,但代价是查找的成本变为了O(n)而不是O(1)。


1
没有理由保留旧字典并实例化新字典,因为任何大小写冲突都会导致它崩溃。如果您知道不会发生冲突,那么最好从一开始就使用不区分大小写的方式。 - Rhys Bevilaqua
4
我已经使用.NET十年了,现在才弄清楚这个问题!!为什么要使用Ordinal而不是CurrentCulture? - Jordan
嗯,这取决于您想要的行为。如果用户通过UI提供密钥(或者您需要考虑ss和ß相等),那么您将需要使用不同的文化设置,但鉴于该值被用作来自外部依赖项的哈希映射的键,我认为“OrdinalCulture”是一个合理的假设。 - Iain Galloway
@RhysBevilaqua,这些字典通常由其他东西返回。知道你正在处理的东西不会发生大小写冲突并不能神奇地使现有系统将它们的字典创建为不区分大小写。 - Nyerguds
1
default(KeyValuePair<T, U>) 不是 null -- 它是一个 KeyValuePair,其中 Key=default(T)Value=default(U)。因此,在 LINQ 示例中不能使用 ?. 运算符;您需要获取 FirstOrDefault(),然后(对于这种特殊情况)检查是否 Key == null - asherber
显示剩余2条评论

73

对于那些从未使用常规字典构造函数的LINQ用户

myCollection.ToDictionary(x => x.PartNumber, x => x.PartDescription, StringComparer.OrdinalIgnoreCase)

2
C# 还有构造函数:Dictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey>? comparer);,它可以让您有效地使用新的比较器重新创建相同的字典。 - Max Hay

41

有一种更简单的方法:

using System;
using System.Collections.Generic;
....
var caseInsensitiveDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

1
这对于 Dictionary<string,T> 可以起作用,这里值的类型并不重要。而且,如果键不是字符串,大小写不敏感甚至意味着什么? - Matt Burland
1
作为一个有很多带有字符串键的排序字典的人,我不明白为什么这个问题没有得到更多的赞。 - Keith Vinson

12

如果您无法更改字典的创建方式,且只需要一种“脏”方法,那么以下方法也许不太优雅,但可以尝试:

var item = MyDictionary.Where(x => x.Key.ToLower() == MyIndex.ToLower()).FirstOrDefault();
    if (item != null)
    {
        TheValue = item.Value;
    }

16
你可以使用下面这段代码来创建一个新的字典对象,该对象将忽略键的大小写,并从现有的另一个字典对象中进行初始化:new Dictionary<string, int>(otherDict, StringComparer.CurrentCultureIgnoreCase); - Jordan
11
根据《在.NET Framework中使用字符串的最佳实践》,请使用ToUpperInvariant而不是ToLower。https://msdn.microsoft.com/zh-cn/library/dd465121(v=vs.110).aspx - Fred
这对我很有帮助,因为我需要以不区分大小写的方式回顾检查键。我进一步简化了它 var item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant()); - Jay
4
为什么不直接使用dict.Keys.Contains("bla", 适当的比较器)呢?另外,由于C#中的KeyValuePair是一个结构体,所以使用FirstOrDefault时不会返回null。 - nawfal
这是一个更好的答案,适用于想要在通常区分大小写的字典上进行单个不区分大小写查找的人。GetMemberBinder.IgnoreCase怎么样? - KatDevsGames

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接