تركيبات Enum البتية تمكنك من محاكاة إسناد اكثر من قيمة الى حامل قيمة واحد (حامل القيمة قد يكون متغير او حقل لجدول بقاعدة بيانات). في المقال التالي سنتعرف على هذه التركيبات وكيف يمكن التعامل معها. عندما تعرف خاصية Property في فئة Class او حقل Field في جدول بقاعدة البيانات، فان نوع الحقل تحدده وفقا لمجال القيم التي تنوي الاحتفاظ به، فمثلا لو كنت تتوقع قيمة واحدة من قيمتين، فان النوع Boolean هو الأوفر: VB Dim var As Boolean var = True var = False C# bool var; var = true; var = false; ومن منظور قاعدة البيانات فهو يماثل النوع bit في SQL Server او True/False في Access. من ناحية اخرى، عندما نتوقع مجموعة محددة ومعروفة لمجال القيم، فان المبرمجين يفضلون تعريف تركيبات من نوع enum (تسمى في عالم البرمجة Enumerator Types)، فمثلا هذا تركيب يمثل مستوى الطالب الجامعي: VB Public Enum StudentLevelENM Orientation Freshman Sophomore Junior Senior End Enum ... ... Dim Heema, Ali, Mohd As StudentLevelENM Heema = StudentLevelENM.Freshman Ali = StudentLevelENM.Sophomore Mohd = StudentLevelENM.Senior C#: public enum StudentLevelENM { Orientation, Freshman, Sophomore, Junior, Senior } ... ... StudentLevelENM Heema, Ali, Mohd; Heema = StudentLevelENM.Freshman; Ali = StudentLevelENM.Sophomore; Mohd = StudentLevelENM.Senior; تقنيا، التركيبات من نوع enum هي عددية من النوع Integer (يمكنك تغيير النوع رغم انك غير محتاج لتغييره غالبا)، عناصر التركيب تبدأ بالرقم 0 وتزيد بواحد، مع ذلك يمكنك تخصيص القيم وفقا لاحتياجاتك، فيمكننا مثلا تعريف تركيب يمثل الجنس Gender (والذي –كما هو معلوم- يكون اما ذكر، أنثى، او أنثى جميلة): Basic: Public Enum GenderENM Male = 10 Female = 20 BeautifulFemale = 30 End Enum ... ... Dim Turki, NaDa As GenderENM Turki = GenderENM.Male NaDa = GenderENM.BeautifulFemale C#: public enum GenderENM { Male = 10, Female = 20, BeautifulFemale = 30 } ... ... GenderENM Turki, NaDa; Turki = GenderENM.Male ; NaDa = GenderENM.BeautifulFemale; التركيبات البتية Bit-Coded Enums: من الامثلة السابقة رأينا ان كل متغير تعرفه من التركيب يمكن ان تسند اليه قيمة ((واحدة فقط)) من احد عناصر التركيب، ولكنك في حالات كثيرة تود ان تدمج اكثر من قيمة، فمثلا لو اردنا معرفة لغات البرمجة التي يتقنها شخص: Basic: Public Enum LanguagesENM NoLanguage VisualBasic CSharp CPlusPlus Java Delphi End Enum ... ... Dim Maram, NooRa, Loly As LanguagesENM Maram = LanguagesENM.VisualBasic NooRa = LanguagesENM.Delphi Loly = LanguagesENM.CSharp C#: public enum LanguagesENM { NoLanguage, VisualBasic, CSharp, CPlusPlus, Java, Delphi } ... ... LanguagesENM Maram, NooRa, Loly; Maram = LanguagesENM.VisualBasic; NooRa = LanguagesENM.Delphi; Loly = LanguagesENM.CSharp; فالمشكلة اننا لا نستطيع ان نسند اكثر من لغة لكل شخص، والحل (الغير احترافي) يقتضي بالغاء فكرة التركيبات وتحويلها الى متغيرات من النوع Boolean (ونفس الشيء في قاعدة البيانات) لكل لغة: Basic: Dim IsVisualBasic As Boolean Dim IsCSharp As Boolean Dim IsCPlusPlus As Boolean Dim IsJava As Boolean Dim IsDelphi As Boolean C#: bool IsVisualBasic; bool IsCSharp; bool IsCPlusPlus; bool IsJava; bool IsDelphi; مع ذلك لا ينصح ابدا باتباع هذا الاسلوب الممل حيث سيستهلك الكثير من الوقت واستنزاف اكثر للموارد، اما الحل الاحترافي فهو بالاعتماد على التركيبات البتية Bit-Coded Enumerators (تسمى ايضا Bitwise Enumerators) والتي تمكنك من محاكاة عملية اسناد اكثر من قيمة الى متغير من نوع التركيب. في الحقيقة، التركيبات البتية ليست اختراعا جديدا ولا ميزة اضافية في لغة البرمجة، فكل التركيبات يمكن ان تكون تركيبات بتية شريطة ان يتم ترقيم عناصرها بطريقة خاصة، والطريقة الخاصة تقول قيمة العنصر = 2^(ترتيب العنصر-1) بإستثناء العنصر الاول فلابد ان يكون دائما صفر، فتركيب لغات البرمجة LanguagesENM السابق سيكون: Basic: Public Enum LanguagesENM NoLanguage = 0 ' 0 VisualBasic = 1 ' 2^0 CSharp = 2 ' 2^1 CPlusPlus = 4 ' 2^2 Java = 8 ' 2^3 Delphi = 16 ' 2^4 End Enum C#: public enum LanguagesENM { NoLanguage = 0, // 0 VisualBasic = 1, // 2^0 CSharp = 2, // 2^1 CPlusPlus = 4, // 2^2 Java = 8, // 2^3 Delphi = 16 // 2^4 } يمكننا بكل ثقة ان نقول الآن بأن التركيب LanguagesENM هو تركيب بتي Bit-Coded، ولكن من المفيد تكحيله بالمواصفة Flags Attribute (حتى تفهم باقي فئات اطار عمل .NET Framework بأنه تركيب بتي خاصة مع الطريقة ToString() ): Basic: <Flags()> _ Public Enum LanguagesENM NoLanguage = 0 ' 0 VisualBasic = 1 ' 2^0 ... ... C#: [Flags()] public enum LanguagesENM { NoLanguage = 0, // 0 VisualBasic = 1, // 2^0 ... ... التعامل مع التركيبات البتية: التركيبات البتية تعتمد على المنطق بشكل جنوني، واي خطأ او عدم فهم لفكرتها ستؤدي بك الى كوارث برمجية لا يعلم عقباها احد، تبدأ عملية التعامل باسناد القيم والذي يمكنك استخدام المعامل البتي Or (او | في C#): Basic: Dim Ibrahim As LanguagesENM Ibrahim = LanguagesENM.VisualBasic Or LanguagesENM.CSharp Console.WriteLine(Ibrahim.ToString()) ' Visual Basic, CSharp C#: LanguagesENM Ibrahim; Ibrahim = LanguagesENM.VisualBasic | LanguagesENM.CSharp; Console.WriteLine(Ibrahim); // Visual Basic, CSharp تحذير: إياك ثم إياك ان تستخدم المعاملات المنطقية AndAlso او OrElse، فهي تقوم بتحويل القيم الى Boolean دون اختبار بتات القيم Value Bits. منطقيا، تكرار اسناد نفس القيم لا يقدم ولا يؤخر وكأن شيئا لم يحدث: Basic: Ibrahim = LanguagesENM.VisualBasic Or LanguagesENM.CSharp Or LanguagesENM.VisualBasic Console.WriteLine(Ibrahim.ToString()) ' Visual Basic, CSharp C#: Ibrahim = LanguagesENM.VisualBasic | LanguagesENM.CSharp | LanguagesENM.VisualBasic; Console.WriteLine(Ibrahim); // Visual Basic, CSharp عندما تنوي ((اضافة)) قيمة جديدة الى متغير من نوع التركيب، لا تنسى استخدام القيمة الاصلية: Basic: Ibrahim = Ibrahim Or LanguagesENM.Delphi Console.WriteLine(Ibrahim); // Visual Basic, CSharp, Delphi C#: Ibrahim = Ibrahim | LanguagesENM.Delphi; Console.WriteLine(Ibrahim); // Visual Basic, CSharp, Delphi كان هذا حول اسناد القيم، اما عملية قراءة القيم فتستخدم المعامل And (المعامل & في C#)عند عملية التحقق مع ((القيمة الاصلية)) للمتغير، فلو أردنا معرفة هل Ibrahim مبرمج بلغة Visual Basic قد نكتب شرطا شبيها بـ: Basic: If CBool(Ibrahim And LanguagesENM.VisualBasic) Then Console.WriteLine("True") End If C#: if (Convert.ToBoolean(Ibrahim & LanguagesENM.VisualBasic)) Console.WriteLine("True"); بنفس الاسلوب السابق يمكننا معرفة كافة القيم الاخرى: Basic: Console.WriteLine("Visual Basic: " & CBool(Ibrahim And LanguagesENM.VisualBasic)) Console.WriteLine("CSharp: " & CBool(Ibrahim And LanguagesENM.CSharp)) Console.WriteLine("CPlusPlus: " & CBool(Ibrahim And LanguagesENM.CPlusPlus)) Console.WriteLine("Java: " & CBool(Ibrahim And LanguagesENM.Java)) Console.WriteLine("Delphi: " & CBool(Ibrahim And LanguagesENM.Delphi)) C#: Console.WriteLine("Visual Basic: " + Convert.ToBoolean(Ibrahim & LanguagesENM.VisualBasic)); Console.WriteLine("CSharp: " + Convert.ToBoolean(Ibrahim & LanguagesENM.CSharp)); Console.WriteLine("CPlusPlus: " + Convert.ToBoolean(Ibrahim & LanguagesENM.CPlusPlus)); Console.WriteLine("Java: " + Convert.ToBoolean(Ibrahim & LanguagesENM.Java)); Console.WriteLine("Delphi: " + Convert.ToBoolean(Ibrahim & LanguagesENM.Delphi)); مخرجات الكود السابق يتوقع ان تكون: Visual Basic: True CSharp: True CPlusPlus: False Java: False Delphi: True من ناحية اخرى، يمكنك الغاء قيمة من قيم التركيب باستخدام الرابط And مع معامل النفي Not (الرابط & والنفي ~ في لغة C#): Basic: Ibrahim = Ibrahim And Not LanguagesENM.CSharp Console.WriteLine(Ibrahim.ToString()) ' Visual Basic, Delphi C#: Ibrahim = Ibrahim & ~LanguagesENM.CSharp; Console.WriteLine(Ibrahim); // Visual Basic, Delphi اخيرا، يمكنك استخدام الرابط XOR ((لعكس)) وجود القيمة، بمعنى ان كانت القيمة موجودة سيتم الغائها وان لم تكن سيتم اضافتها: Basic: Ibrahim = Ibrahim Xor LanguagesENM.Delphi Ibrahim = Ibrahim Xor LanguagesENM.CPlusPlus Console.WriteLine(Ibrahim.ToString()) ' Visual Basic, CPlusPlus C#: Ibrahim = Ibrahim ^ LanguagesENM.Delphi; Ibrahim = Ibrahim ^ LanguagesENM.CPlusPlus; Console.WriteLine(Ibrahim); // Visual Basic, CPlusPlus خاتمة كما رأيت، فان التركيبات البتية Bit-Coded Enums ليست سوى تركيبات تحمل قيم مرتبة بطريقة خاصة تمكننا من تمثيل مجموعة قيم في مكان وواحد (سواء متغير او حقل جدولي بقاعدة بيانات)، نعتمد على المنطق بشكل حذر لتعديل وقراءة القيم، فأي خطأ قد يؤدي الى شوائب واخطاء خطيرة في البرنامج. اجراءات Windows API وفئات اطار عمل .NET Framework والكثير من مكتبات الفئات Class Libraries تعتمد على التركيبات البتية بشكل كبير جدا، وهناك آلاف الأمثلة التطبيقية لها (لعل ابرزها دالة MessageBox). -- تركي