Akses case sensitif untuk kamus umum


244

Saya memiliki aplikasi yang menggunakan dll yang dikelola. Salah satu dari itu dll mengembalikan kamus generik:

Dictionary<string, int> MyDictionary;  

Kamus berisi tombol dengan huruf besar dan kecil.

Di sisi lain saya mendapatkan daftar kunci potensial (string) namun saya tidak dapat menjamin kasus ini. Saya mencoba mendapatkan nilai dalam kamus menggunakan tombol. Tetapi tentu saja hal berikut ini akan gagal karena saya memiliki kasus ketidakcocokan:

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

Saya berharap TryGetValue akan memiliki flag kasus abaikan seperti yang disebutkan dalam dokumen MSDN , tetapi tampaknya ini tidak valid untuk kamus umum.

Apakah ada cara untuk mendapatkan nilai dari kamus itu dengan mengabaikan case utama? Apakah ada solusi yang lebih baik daripada membuat salinan kamus baru dengan parameter StringComparer.OrdinalIgnoreCase yang tepat ?


Jawaban:


514

Tidak ada cara untuk menentukan StringComparerpada titik di mana Anda mencoba untuk mendapatkan nilai. Jika Anda memikirkannya, "foo".GetHashCode()dan"FOO".GetHashCode() sama sekali berbeda sehingga tidak ada cara yang masuk akal Anda bisa menerapkan get-case sensitif pada peta hash yang case-sensitive.

Anda dapat, bagaimanapun, membuat kamus case-sensitive di tempat pertama menggunakan: -

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

Atau buat kamus case-sensitive baru dengan isi dari kamus case-sensitive yang ada (jika Anda yakin tidak ada tabrakan kasus): -

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

Kamus baru ini kemudian menggunakan GetHashCode()implementasinya StringComparer.OrdinalIgnoreCasejadi comparer.GetHashCode("foo")dan comparer.GetHashcode("FOO")memberi Anda nilai yang sama.

Sebagai alternatif, jika hanya ada beberapa elemen dalam kamus, dan / atau Anda hanya perlu mencari sekali atau dua kali, Anda dapat memperlakukan kamus asli sebagai IEnumerable<KeyValuePair<TKey, TValue>>dan hanya mengulanginya: -

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

Atau jika Anda suka, tanpa 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;
  }
}

Ini menghemat biaya pembuatan struktur data baru, tetapi sebagai imbalannya biaya pencarian adalah O (n) bukan O (1).


Memang masuk akal. Terima kasih banyak atas penjelasannya.
TocToc

1
Tidak ada alasan untuk menjaga kamus lama dan instantiate yang baru karena setiap kasus tabrakan akan menyebabkannya meledak. Jika Anda tahu Anda tidak akan mendapatkan tabrakan maka Anda mungkin juga menggunakan huruf besar-kecil dari awal.
Rhys Bevilaqua

2
Sudah sepuluh tahun saya menggunakan NET. Dan sekarang saya baru tahu ini !! Mengapa Anda menggunakan Ordinal alih-alih CurrentCulture?
Jordan

Yah, itu tergantung pada perilaku yang Anda inginkan. Jika pengguna menyediakan kunci melalui UI (atau jika Anda perlu mempertimbangkan misalnya ss dan ß sama) maka Anda harus menggunakan budaya yang berbeda, tetapi mengingat bahwa nilai tersebut digunakan sebagai kunci untuk hashmap yang berasal dari ketergantungan eksternal, saya pikir 'OrdinalCulture' adalah asumsi yang masuk akal.
Iain Galloway

1
default(KeyValuePair<T, U>)bukan null- itu adalah di KeyValuePairmana Key=default(T)dan Value=default(U). Jadi Anda tidak dapat menggunakan ?.operator dalam contoh LINQ; Anda harus mengambil FirstOrDefault()dan kemudian (untuk kasus khusus ini) periksa untuk melihat apakah Key == null.
asherber

38

Bagi Anda para LINQer di luar sana yang tidak pernah menggunakan konstruktor kamus reguler:

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

8

Ini tidak terlalu elegan tetapi jika Anda tidak dapat mengubah pembuatan kamus, dan yang Anda butuhkan adalah hack kotor, bagaimana dengan ini:

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

13
atau hanya ini: Kamus baru <string, int> (otherDict, StringComparer.CurrentCultureIgnoreCase);
Jordan

6
Sesuai "Praktik Terbaik untuk Menggunakan Strings dalam .NET Framework" gunakan ToUpperInvariantsebagai gantinya ToLower. msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspx
Fred

Ini bagus untuk saya, di mana saya harus secara retrospektif memeriksa kunci dengan cara yang tidak sensitif. Saya merampingkannya sedikit lebihvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
Jay

Kenapa tidak adil dict.Keys.Contains("bla", appropriate comparer)? Selain itu, Anda tidak akan mendapatkan null untuk FirstOrDefault karena keyvaluepair di C # adalah sebuah struct.
nawfal
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.