Menu
flag Ar iconالعربيةdown icon

القاموس Dictionary في C#: Add وTryGetValue والمرور والفرز

يربط Dictionary<TKey, TValue> المفاتيح بالقيم مع بحث سريع. تعلّم كيف تضيف المدخلات وتحدّثها، وتقرأ بأمان بـ TryGetValue، وتمرّ على مدخلات KeyValuePair، وتعدّ التكرارات، وتتجاهل حالة الأحرف في المفاتيح، وتبقي المفاتيح مرتّبة بـ SortedDictionary.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

يخزّن Dictionary<TKey, TValue> القيم تحت مفاتيح فريدة ويجد القيمة بمفتاحها في زمن ثابت تقريبًا، مهما كان عدد المدخلات. وهو مكافئ جدول التجزئة (hash map) في C#: دليل هاتف من الاسم إلى الرقم، أو ذاكرة مؤقتة من المعرّف إلى السجل، أو عدد لكل كلمة.

إنشاء قاموس وقراءة القيم

المخرجات:

12
2.50
2
True
False

شكلا التهيئة يفعلان الشيء نفسه. يستخدم الشكل ["key"] = value (C# 6) المفهرس، فيستبدل المفتاح المكرّر؛ ويستدعي الشكل { key, value } الدالة Add، فيرمي المفتاح المكرّر استثناءً حين يُنفَّذ السطر.

ContainsKey بحث بالتجزئة وهي سريعة. أما ContainsValue فعليها المرور على كل مدخل، لأن القيم غير مفهرسة.

Add مقابل المفهرس مقابل TryAdd

هناك ثلاث طرق لإدخال مدخل، ولا تختلف إلا فيما يحدث حين يكون المفتاح موجودًا:

المخرجات:

26
Caught ArgumentException
True
False
31

رمي Add لاستثناء عند التكرار ميزة: يكشف البيانات التي كان يجب أن تكون فريدة ولم تكن. استخدم المفهرس حين تقصد "أدرج أو حدّث"، وTryAdd (.NET Core 2.0 وما بعده) حين يجب أن تفوز القيمة الأولى.

لا يمكن أن تكون المفاتيح null. ترمي Add(null, ...) أو dict[null] استثناء ArgumentNullException. ويمكن أن تكون القيم null حين يسمح نوع القيمة بذلك.

KeyNotFoundException وTryGetValue

قراءة مفتاح غير موجود بالمفهرس ترمي KeyNotFoundException. هذا أشيع خطأ في القواميس، والإصلاح دائمًا تقريبًا TryGetValue.

المخرجات:

Caught KeyNotFoundException
Found ana@example.com
Missing, value is null: True
no email

تجري TryGetValue بحثًا واحدًا بالتجزئة وتبلّغ عن النجاح كقيمة bool. النمط if (dict.ContainsKey(k)) { var v = dict[k]; } يعمل لكنه يبحث عن المفتاح مرتين. وحين يكون المفتاح غائبًا يحصل متغيّر out على القيمة الافتراضية للنوع (null و0 وfalse).

في .NET Core 2.0 وما بعده توجد أيضًا GetValueOrDefault(key, fallback)، التي تعيد القيمة البديلة حين يكون المفتاح غائبًا: emails.GetValueOrDefault(103, "no email").

تحديث المدخلات وحذفها

المخرجات:

2
True
False
1
0

ترمي cart["milk"] += 1 استثناء KeyNotFoundException إن لم يكن milk في القاموس بعد، لأنها تقرأ قبل أن تكتب. وتعيد Remove القيمة false بدل رمي استثناء لمفتاح غائب، فلا حاجة لفحص ContainsKey أولًا.

المرور: KeyValuePair وKeys وValues

ينتج foreach على قاموس عناصر KeyValuePair<TKey, TValue>، لكل منها Key وValue.

المخرجات:

Ana: 88
Ben: 72
Chloe: 95
Ana Ben Chloe 
Total 255
77

pair.Value للقراءة فقط، فتحديث القيم يعني الكتابة عبر المفهرس. تمرّ الحلقة الأخيرة على نسخة List<string> من المفاتيح، وهذا آمن دائمًا؛ أما المرور على scores.Keys مباشرة مع استبدال قيم موجودة فمسموح في .NET Core 3.0 وما بعده لكنه يرمي InvalidOperationException في .NET Framework.

إضافة مفتاح جديد داخل foreach على القاموس نفسه ترمي InvalidOperationException في كل إصدار. والحذف أثناء التعداد يرمي في .NET Framework ومسموح منذ .NET Core 3.0. والشيفرة التي يجب أن تعمل في كل مكان تجمع المفاتيح المراد حذفها أولًا، ثم تحذفها بعد الحلقة.

مع C# 7 و.NET Core 2.0 أو أحدث يمكن تفكيك KeyValuePair في ترويسة الحلقة:

foreach (var (name, score) in scores)
{
    Console.WriteLine($"{name}: {score}");
}

العدّ بقاموس

عدّ التكرارات هو الاستخدام النموذجي. اقرأ العدد الحالي بـ TryGetValue (المفتاح الغائب يعطي 0)، ثم اكتب النتيجة.

المخرجات:

the 3
cat 1
and 2
dog 1
bird 1

الشكل نفسه يجمّع العناصر: Dictionary<string, List<Order>>، حيث تجلب القائمة بـ TryGetValue، وتنشئ واحدة وتخزّنها إن غابت، ثم تضيف إليها بـ Add. وللعدّ والتجميع لمرة واحدة يفعل LINQ ذلك في تعبير واحد: words.GroupBy(w => w).ToDictionary(g => g.Key, g => g.Count()). راجع LINQ.

مفاتيح غير حساسة لحالة الأحرف بأداة مقارنة

تُقارن مفاتيح النصوص بدقة افتراضيًا: "Apple" و"apple" مفتاحان. مرّر IEqualityComparer<string> إلى المُنشئ لتغيير ذلك.

المخرجات:

False
text/html
1

StringComparer.OrdinalIgnoreCase هي الخيار الصحيح للمعرّفات مثل ترويسات HTTP وامتدادات الملفات وأسماء المستخدمين. استدعاء .ToLower() على كل مفتاح قبل تخزينه يعمل أيضًا، لكن يسهل نسيانه في مكان ما.

في مفاتيح من صنفك يستدعي القاموس GetHashCode وEquals للمفتاح. الصنف الذي لا يتجاوزهما يقارن بالمرجع، فيكون كائنان منفصلان بالحقول نفسها مفتاحين مختلفين. راجع HashSet لطريقة كتابة هذا الزوج.

الترتيب والفرز وSortedDictionary

لا يعد Dictionary بأي ترتيب تعداد. عمليًا يُعدَّد القاموس الذي لم تُضف إليه إلا مدخلات بترتيب الإدراج، لكن بعد Remove قد تعيد Add لاحقة استخدام الخانة المحرّرة وتظهر مبكرًا. يجب ألا تعتمد الشيفرة على ذلك أبدًا.

حين تحتاج إلى ترتيب، رتّب في موضع الاستخدام أو استخدم مجموعة مرتّبة:

المخرجات:

Cairo 210
Lima 340
Oslo 520
By value, highest first:
Oslo 520
Lima 340
Cairo 210
Berlin, Cairo, Lima, Oslo

يبقي SortedDictionary<TKey, TValue> مفاتيحه مرتّبة دائمًا (هو شجرة متوازنة)، فيكون البحث والإدراج O(log n) بدل O(1). استخدمه حين تعدّد بترتيب المفاتيح كثيرًا؛ ورتّب قاموسًا عاديًا بـ LINQ حين لا تحتاج إلى الترتيب إلا مرة. وSortedList<TKey, TValue> خيار ثالث يستخدم ذاكرة أقل لكن الإدراج فيه بطيء حين يكبر.

مرجع سريع

المهمةالشيفرة
الإنشاءnew Dictionary<string, int>()
الإدراج أو الاستبدالd[k] = v
الإدراج مع الرمي عند التكرارd.Add(k, v)
الإدراج فقط إن كان جديدًاd.TryAdd(k, v)
القراءة مع الرمي عند الغيابd[k]
القراءة بأمانd.TryGetValue(k, out var v)
وجود المفتاحd.ContainsKey(k)
الحذفd.Remove(k) (تعيد bool)
الحجمd.Count
المفاتيح والقيمd.Keys، d.Values
مرتّب بالمفتاحd.OrderBy(p => p.Key) أو SortedDictionary
تجاهل حالة الأحرفnew Dictionary<string, T>(StringComparer.OrdinalIgnoreCase)

أخطاء شائعة

  • قراءة مفتاح غائب بـ d[k]. يرمي KeyNotFoundException؛ استخدم TryGetValue.
  • استدعاء Add لمفتاح قد يكون موجودًا. يرمي ArgumentException؛ استخدم المفهرس أو TryAdd.
  • إضافة مفاتيح داخل foreach على القاموس. يرمي InvalidOperationException؛ اجمع التغييرات وطبّقها بعدها.
  • الاعتماد على ترتيب التعداد. رتّب، أو استخدم SortedDictionary.
  • تغيير حقول كائن المفتاح بعد إدراجه. يتغيّر رمز التجزئة ولا يعود العثور على المدخل ممكنًا.
  • ContainsKey ثم المفهرس. بحثان؛ وTryGetValue تجري بحثًا واحدًا.

الأسئلة الشائعة

ما الفرق بين Dictionary.Add والمفهرس في C#؟

تُدرج dict.Add(key, value) مدخلًا جديدًا وترمي ArgumentException إن كان المفتاح موجودًا. أما dict[key] = value فتُدرج المدخل إن كان المفتاح جديدًا وتستبدل القيمة إن كان موجودًا، ولا ترمي أبدًا للتكرار. وتُدرج TryAdd(key, value) فقط حين يكون المفتاح جديدًا وتعيد false في غير ذلك.

كيف تعمل TryGetValue في C#؟

تعيد dict.TryGetValue(key, out var value) القيمة true وتضبط value حين يكون المفتاح موجودًا، وتعيد false وتضبط value على القيمة الافتراضية لنوعها حين لا يكون. تجري بحثًا واحدًا، بينما يجري ContainsKey ثم dict[key] بحثين، ولا ترمي أبدًا KeyNotFoundException.

كيف أمرّ على Dictionary في C#؟

تعطي foreach (KeyValuePair<string, int> pair in dict) كل مدخل مع pair.Key وpair.Value. للمرور على المفاتيح فقط أو القيم فقط استخدم dict.Keys أو dict.Values. لا تضف مفاتيح إلى القاموس داخل تلك الحلقة: هذا يرمي InvalidOperationException.

هل القاموس في C# مرتّب؟

لا ترتيب مضمونًا. القاموس الذي لم تُضف إليه إلا مدخلات يُعدَّد عادة بترتيب الإدراج، لكن بعد Remove يمكن للمدخلات الجديدة ملء الخانة المحرّرة، فيتغيّر الترتيب. رتّب حين تحتاج إلى ترتيب: dict.OrderBy(p => p.Key)، أو استخدم SortedDictionary<TKey, TValue>، الذي يُعدَّد دائمًا بحسب المفتاح.

كيف أجعل مفاتيح Dictionary غير حساسة لحالة الأحرف؟

مرّر أداة مقارنة إلى المُنشئ: new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase). عندها يكون "Apple" و"apple" المفتاح نفسه في البحث وAdd وContainsKey. وتُثبَّت أداة المقارنة حين يُنشأ القاموس.

ما هو KeyValuePair في C#؟

KeyValuePair<TKey, TValue> هي البنية التي يسلّمك إياها القاموس لكل مدخل حين تعدّده. لها الخاصيتان Key وValue للقراءة فقط، فلا تستطيع تغيير مدخل عبرها؛ اكتب dict[pair.Key] = newValue بدلًا من ذلك (بعد الحلقة، أو على نسخة من المفاتيح).

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن