بتـــــاريخ : 1/13/2011 5:23:24 PM
الفــــــــئة
  • الحـــــــــــاسب
  • التعليقات المشاهدات التقييمات
    0 1218 0


    تركيبات Enum البتية

    الناقل : elmasry | العمر :42 | الكاتب الأصلى : تركي العسيري | المصدر : www.al-asiri.com

    كلمات مفتاحية  :

    تركيبات 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).



    -- تركي


    كلمات مفتاحية  :

    تعليقات الزوار ()