تعلم سي شارب البنى Structures والمعدودات Enums في لغة سي شارب #C


حسام برهان

تشبه البنية structure الصنف class إلى حدٍّ كبير باستثناء أنّها تُعتبر نوع قيمة value type وليس نوعًا مرجعيًّا كما هو الحال بالنسبة للأصناف. أمّا بالنسبة للمعدودة enum فهي تقنيّة تسمح لنا بتعيين مجموعة من الثوابت الرقميّة التي يمكن تسميّتها، وينحصر دورها في تنظيم البرنامج وجعل الشيفرة أكثر قابليّة للفهم والتعديل.

البنى Structures

تستطيع اعتبار البنية على أنّها نسخة مخفّفة من الصنف class. يكمن الفرق الأساسي بينهما في أنّ البنية هي نوع قيمة كما ذكرنا، فعند إنشاء بنية سيتم حجز المكان المخصّص لها في المكدّس stack وليس في الكومة heap كما هو الحال مع الأنواع المرجعيّة reference types.

ستُنشئ الشيفرة التالية بنية اسمها Student على الشكل التالي:

struct Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Mark { get; set; }

    public override string ToString()
    {
        return string.Format("{0} {1} ({2})",FirstName,LastName,Mark);
    }
}

لاحظ مدى الشبه بينها وبين الأصناف. الآن إذا أردنا استخدام هذه البنية في الشيفرة يمكننا ذلك من خلال العبارة التالية:

Student student = new Student();

علينا أن نتذكّر دائمًا أنّ الكائن الذي يقوم العامل new بإنشائه سيكون في المكدّس stack.

يمكننا أن نتابع ونستخدم المتغيّر student من نوع البنية Student كما كنّا نفعل من قبل مع متغيّرات الأصناف:

student.FirstName = "Maher";
student.LastName = "Rajab";
student.Mark = 100;

Console.WriteLine(student.ToString());

بعد تنفيذ الشيفرة السابقة ستحصل على الخرج:

Maher Rajab (100)

توجد الكثير من البنى المهمّة والمفيدة. فمثلًا هناك البنية DateTime التي تسمح لنا بالتعامل مع الزمن والتاريخ، وهي بنية مهمّة للغاية. انظر إلى البرنامج Lesson17_01 الذي يوضّح بعض المزايا التي توفّرها.


 

1	using System;
2	using System.Globalization;
3
4	namespace Lesson17_01
5	{
6	    class Program
7	    {
8	        static void Main(string[] args)
9	        {
10	            DateTime myBirthDate = new DateTime(1979, 12, 25);
11	
12	            //display birth date in simple form:
13	            Console.WriteLine(myBirthDate.ToString("dd/MM/yyyy"));
14	
15	            //display birth date in more elegent way:
16	            Console.WriteLine(myBirthDate.ToString("D", CultureInfo.InvariantCulture));
17	
18	            //add time info:
19	            myBirthDate = myBirthDate.AddHours(4);
20	            myBirthDate = myBirthDate.AddMinutes(30);
21	
22	            //display completed birth date info:
23	            Console.WriteLine(myBirthDate.ToString("dddd, dd MMMM yyyy - hh:mm:ss tt", CultureInfo.InvariantCulture));
24	        }
25	    }
26	}

بعد تنفيذ البرنامج السابق ستحصل على الخرج التالي:

fig01.png

أنشأنا في السطر 10 كائنًا جديدًا من البنية DateTime حيث مرّرنا ثلاثة وسائط للبانية هي على الترتيب من اليسار إلى اليمين: العام والشهر واليوم (الميلادي). في الحقيقة تمتلك بانية البنية DateTime اثنا عشرة شكلًا مختلفًا، نستطيع من خلال بعض هذه الأشكال ضبط الوقت أيضًا.

يؤدّي الاستدعاء في السطر 13:

myBirthDate.ToString("dd/MM/yyyy")

إلى الحصول على تمثيل نصّي مختصر من التاريخ الذي مرّرناه إلى البانية قبل قليل. حيث ترمز dd إلى اليوم، و MM (حرف كبير) إلى الشهر، وyyyy إلى العام، ويمكنك بالتأكيد تغيير هذا الترتيب إذا أحببت (انظر إلى السطر الأوّل من الخرج في الشكل السابق).

أمّا الاستدعاء في السطر 16:

myBirthDate.ToString("D", CultureInfo.InvariantCulture)

فيؤدّي إلى الحصول على نفس التاريخ ولكن بشكل أنيق. حيث سنحصل على القيم الكتابيّة للتاريخ (انظر السطر الثاني من الخرج في الشكل السابق). ولكن لاحظ أنّنا نمرّر إلى التابع ToString وسيطين: الأوّل هو النص التنسيقي "D" الذي يُخبر التابع ToString أنّنا نريد التنسيق على شكل تاريخ ولكن بصورة تفصيليّة أنيقة، أمّا الوسيط الثاني CultureInfo.InvariantCulture فهو يُرجع كائن من النوع CultureInfo. الهدف منه هو اعتماد الإعدادات الإقليميّة العالميّة في تنسيق التاريخ. وسبب ذلك أنّ كل بلد أو ثقافة culture لها أسلوب معيّن في تنسيق التاريخ (وكذلك الأمر بالنسبة للأرقام والعملات). لقد اخترت التنسيق العالميّ InvariantCulture لأنّني لا أريد من البرنامج استخدام الإعدادات الإقليميّة الخاصّة بنظام التشغيل في حاسوبي الشخصي الذي يعمل عليه البرنامج، لأنّ الخرج سيظهر مشوّهًا نتيجة لذلك. يكمن السبب في أنّ نافذة موجّه الأوامر console window لا تدعم اللغة العربية بشكل افتراضيّ، لذلك آثرت استخدام الإعدادات العالميّة (تشبه إلى حدٍّ كبير الإعدادات الخاصّة بالولايات المتحدّة).

أخيرًا الاستدعاء في السطر 23:

Console.WriteLine(myBirthDate.ToString("dddd, dd MMMM yyyy - hh:mm:ss tt", CultureInfo.InvariantCulture));

سيؤدّي هذا الاستدعاء إلى الحصول على النص الذي يظهر في السطر الثالث من الخرج، وهو مزيج بين التاريخ والوقت. لاحظ أنّنا قد أسبقنا هذا الاستدعاء باستدعاءين في السطرين 19 و20 للتابعين AddHours وAddMinutes على الترتيب. أتصوّر أنّ وظيفتهما واضحة، وهي إضافة قيمة ساعات ودقائق محدّدة إلى المتغيّر myBirthDate، مع الانتباه إلى أنّ كل منهما ستُرجع كائن DateTime جديد يمثّل التعديل الذي حصل. أي أنّ المتغيّر myBirthDate الأصلي لن يُمس في كلتا الحالتين. إلّا أنّنا أعدنا إسناد القيم المُعادة إلى المتغيّر myBirthDate لتجنّب إنشاء متغيّرات جديدة كما هو واضح من السطرين 19 و20. بقي أن أخبرك ما يلي:

  • ترمز dddd إلى اليوم ولكن بصيغة كتابيّة.
  • ترمز dd إلى اليوم ولكن بصيغة رقميّة.
  • ترمز MMMM إلى الشهر ولكن بصيغة كتابيّة.
  • ترمز yyyy إلى العام بصيغة رقميّة.
  • ترمز hh إلى الساعات.
  • ترمز mm (أحرف صغيرة) إلى الدقائق.
  • ترمز ss إلى الثواني.
  • ترمز tt إلى AM أو PM.

ملاحظة

هناك العديد من التوابع الأخرى التي لم نتناولها بالنسبة للبنية DateTime. كما أنّ هناك بنية أخرى اسمها TimeSpan متعلّقة بالبنية DateTime ويمكن من خلالها تمثيل فترة زمنيّة بدلًا من تاريخ محدّد.

ملاحظة

توجد خاصيّة ضمن البنية DateTime مفيدة كثيرًا وهي الخاصيّة Now. تُرجع هذه الخاصيّة التوقيت الحالي الذي يمثّل التاريخ والوقت الحاليين من ساعة الحاسوب. طريقة استخدامها بسيطة:

DateTime current = DateTime.Now;

المعدودات Enums

المعدودة هي نوع قيمة value type خاص يسمح لنا بتعيين مجموعة من الثوابت الرقميّة ذات تسميّة. انظر مثلًا إلى تصريح المعدودة BorderSide التالي:

public enum BorderSide { Left, Right, Top, Bottom }

يمكن لهذا التصريح أن يوضع ضمن صنف ما، أو خارج أيّ صنف. عندها من الممكن استخدام هذه المعدودة بالشكل التالي:

BorderSide topSide = BorderSide.Top;

في الحقيقة إنّ لكلّ ثابت من ثوابت المعدودة (مثل Left أو Right أو غيرها) قيمة رقميّة ضمنيّة. تأخذ هذه الثوابت القيم الرقميّة الافتراضيّة 0، 1، 2، ... الخ بنفس الترتيب الذي تكون فيه هذه الثوابت ضمن تصريح المعدودة. فمثلًا في المعدودة BorderSide السابقة، يأخذ الثابت Left القيمة 0، والثابت Rigth القيمة 1، وهكذا. كما ويمكننا أيضًا أن نسند لثوابت المعدودة قيمًا رقميّة مباشرةً أثناء التصريح عنها. انظر إلى المعدودة BorderSide بشكلها المعدّل:

public enum BorderSide { Left = 1, Right = 5, Top = 7, Bottom = 8 }

أي ليس من الضروري أن تكون القيم الرقميّة متسلسلة أو تبدأ من الصفر. المهم فقط أن تكون صحيحة (بدون فاصلة عشريّة).

يمكن الحصول على القيمة الرقميّة المُسندة إلى أيّ ثابت ضمن المعدودة بالشكل البسيط التالي:

int i = (int)BorderSide.Left;

بعد تنفيذ العبارة السابقة ستصبح قيمة المتغيّر i تساوي 1.

تمارين داعمة

تمرين 1

عرّف متغيّرين من البنية DateTime، سمّ الأوّل first وأسند إليه التاريخ 01/01/2016، وسمّ الثاني second وأسند إليه التاريخ 13/05/2016. وبعد ذلك نفّذ العبارة التالية وحاول تفسير الخرج:

Console.WriteLine(second - first);

تمرين 2

أنشئ برنامجًا يستخدم معدودةً اسمها LevelEnum تحتوي على الثوابت: Weak، Middle، Good، VeryGood. أضف هذه المعدودة إلى الصنف Student التالي:

class Student

{

	public string FirstName { get; set; }

	public string LastName { get; set; }

	public int Mark { get; set; }

}

ثمّ أضف لنفس الصنف السابق الخاصيّة Level من نوع المعدودة LevelEnum، حيث تُعبّر هذه الخاصيّة عن مستوى الطالب Student.

الخلاصة

تعرّفنا في هذا الدرس على البنى Structures والمعدودات Enums وكيفيّة استخدامهما. ستصادف في برامجك المستقبليّة هاتين التقنيّتين بشكل متكرّر. فإمّا أن تنشئهما بنفسك أو أن تفرض عليك أصناف دوت نت أو غيرها استخدامهما. تُعتبر البنى خفيفة، حيث أنّها سريعة في الإنشاء والمعالجة أكثر من الأصناف، كونها تتوضّح في مكدّس البرنامج كما أسلفنا. وتُعتبر المعدودات أداةً رائعة لتنظيم برامجك في حال كانت تتطلّب تعريف ثوابت عدديّة ضمن مجموعات منطقيّة.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن