Dictionary vs Hashtable في C#: لماذا يتفوق القاموس دائمًا؟

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

Dictionary vs Hashtable: معركة المجموعات في C#

\n\n

تخيل الموقف: أنت تكتب كودًا بلغة C# وتحتاج إلى حفظ واسترجاع البيانات بسرعة باستخدام مفتاح. تفتح التوثيق وترى صنفين متشابهين — Dictionary و Hashtable. كلاهما يعمل بمبدأ "مفتاح-قيمة"، وكلاهما يعد بوصول فائق السرعة للبيانات. لكن أيهما تختار؟

\n\n

إذا كنت قد طرحت هذا السؤال من قبل، فأنت لست وحدك. على Stack Overflow، موضوع "لماذا يُفضل Dictionary على Hashtable في C#؟" حصد آلاف المشاهدات ومئات الأصوات. واليوم سنحسم هذا الأمر نهائيًا. المفسد: Dictionary يفوز دائمًا تقريبًا. لكن دعنا نفهم لماذا.

\n\n

نبذة تاريخية: من أين جاء Hashtable؟

\n\n

لفهم جوهر المنافسة، يجب أن ننظر إلى تاريخ C#. عندما ظهرت منصة .NET لأول مرة (الإصدار 1.0)، كان بإمكان المطورين استخدام الصنف Hashtable من مساحة الأسماء System.Collections. كانت هذه جدول تجزئة كلاسيكي يمكنه تخزين أي كائنات. يبدو رائعًا، أليس كذلك؟ لكن كانت هناك مشكلة واحدة — غياب التحديد النوعي (typing).

\n\n

مع إصدار .NET 2.0 وظهور الأصناف العامة (generics)، قدمت Microsoft مجموعة جديدة — Dictionary<TKey, TValue> من مساحة الأسماء System.Collections.Generic. وهنا بدأ السحر الحقيقي.

\n\n

أمان الأنواع: الميزة الرئيسية لـ Dictionary

\n\n

لنلقِ نظرة على مثال كود محدد. إليك كيف يبدو العمل مع Hashtable:

\n\n
// الطريقة القديمة: Hashtable\nHashtable hashtable = new Hashtable();\nhashtable.Add("مفتاح1", 123);\nhashtable.Add("مفتاح2", "نص"); // خطأ؟ لا، سيتم التجميع!\nhashtable.Add(456, "نص آخر"); // المفتاح يمكن أن يكون أي كائن أيضًا\n\n// الآن لنحاول استرجاع البيانات\nint value = (int)hashtable["مفتاح2"]; // أوه! InvalidCastException في وقت التشغيل!
\n\n

هل ترى المشكلة؟ Hashtable يقبل أي أنواع — سواء للمفاتيح أو القيم. المترجم (compiler) سيمرر أي خطأ، وستكتشفه فقط أثناء تشغيل البرنامج. هذا طريق مباشر للأخطاء (bugs) التي يصعب تتبعها.

\n\n

والآن لننظر إلى Dictionary:

\n\n
// الطريقة الجديدة والصحيحة: Dictionary\nDictionary<string, int> dictionary = new Dictionary<string, int>();\ndictionary.Add("مفتاح1", 123);\n// dictionary.Add("مفتاح2", "نص"); // خطأ في التجميع! النوع غير متطابق\n// dictionary.Add(456, "نص آخر"); // خطأ أيضًا — المفتاح يجب أن يكون string\n\n// استرجاع البيانات دون تحويل\nint value = dictionary["مفتاح1"]; // نظيف، آمن، جميل
\n\n

Dictionary آمن من حيث الأنواع. أنت تحدد بوضوح أنواع المفاتيح والقيم. المترجم يتحقق من ذلك في مرحلة البناء. لا استثناءات غير متوقعة أثناء التشغيل — الكود إما يعمل أو لا يتم تجميعه.

\n\n

الأداء: من الأسرع؟

\n\n

الجانب المهم الثاني هو الأداء. وهنا يتفوق Dictionary مرة أخرى. لماذا؟ الأمر كله يتعلق بـ التغليف (boxing) وإلغاء التغليف (unboxing).

\n\n

عندما تعمل مع Hashtable، يتم تغليف جميع الأنواع القيمية (int, double, struct، إلخ) في كائنات. هذا يخلق عبئًا على جامع القمامة (garbage collector) ويبطئ الأداء. دعنا نختبر ذلك عمليًا:

\n\n
// Hashtable: تغليف/إلغاء تغليف\nHashtable ht = new Hashtable();\nStopwatch sw = Stopwatch.StartNew();\nfor (int i = 0; i < 1000000; i++)\n{\n    ht.Add(i, i); // كل int يتم تغليفه في object\n}\nsw.Stop();\nConsole.WriteLine($"Hashtable إضافة: {sw.ElapsedMilliseconds} مللي ثانية");\n\n// Dictionary: بدون تغليف\nDictionary<int, int> dict = new Dictionary<int, int>();\nsw.Restart();\nfor (int i = 0; i < 1000000; i++)\n{\n    dict.Add(i, i); // لا تغليف — نعمل مع int مباشرة\n}\nsw.Stop();\nConsole.WriteLine($"Dictionary إضافة: {sw.                

المدونات

توصيات الكتب