Dictionary vs Hashtable: معركة المجموعات في C#
\n\nتخيل الموقف: أنت تكتب كودًا بلغة C# وتحتاج إلى حفظ واسترجاع البيانات بسرعة باستخدام مفتاح. تفتح التوثيق وترى صنفين متشابهين — Dictionary و Hashtable. كلاهما يعمل بمبدأ "مفتاح-قيمة"، وكلاهما يعد بوصول فائق السرعة للبيانات. لكن أيهما تختار؟
إذا كنت قد طرحت هذا السؤال من قبل، فأنت لست وحدك. على Stack Overflow، موضوع "لماذا يُفضل Dictionary على Hashtable في C#؟" حصد آلاف المشاهدات ومئات الأصوات. واليوم سنحسم هذا الأمر نهائيًا. المفسد: Dictionary يفوز دائمًا تقريبًا. لكن دعنا نفهم لماذا.
\n\nنبذة تاريخية: من أين جاء Hashtable؟
\n\nلفهم جوهر المنافسة، يجب أن ننظر إلى تاريخ C#. عندما ظهرت منصة .NET لأول مرة (الإصدار 1.0)، كان بإمكان المطورين استخدام الصنف Hashtable من مساحة الأسماء System.Collections. كانت هذه جدول تجزئة كلاسيكي يمكنه تخزين أي كائنات. يبدو رائعًا، أليس كذلك؟ لكن كانت هناك مشكلة واحدة — غياب التحديد النوعي (typing).
مع إصدار .NET 2.0 وظهور الأصناف العامة (generics)، قدمت Microsoft مجموعة جديدة — Dictionary<TKey, TValue> من مساحة الأسماء System.Collections.Generic. وهنا بدأ السحر الحقيقي.
أمان الأنواع: الميزة الرئيسية لـ Dictionary
\n\nلنلقِ نظرة على مثال كود محدد. إليك كيف يبدو العمل مع Hashtable:
// الطريقة القديمة: 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\nDictionary آمن من حيث الأنواع. أنت تحدد بوضوح أنواع المفاتيح والقيم. المترجم يتحقق من ذلك في مرحلة البناء. لا استثناءات غير متوقعة أثناء التشغيل — الكود إما يعمل أو لا يتم تجميعه.
\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.