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

الناقل : elmasry | الكاتب الأصلى : تركي العسيري | المصدر : 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).



-- تركي