الكلاسات في C#: الخصائص والوراثة و record
C# لغة كائنية التوجه من أساسها: كل شيء فيها يعيش داخل كلاسات. في Unity مثلاً، كل لاعب وعدو وعملة ذهبية في اللعبة هو كائن من كلاس. في هذا الدرس ستتعلم تصميم الكلاسات، ومعها ميزة مميزة في C# اسمها الخصائص (Properties)، تجعل حماية البيانات أنيقة وسهلة.
الكلاس والكائن
Section titled “الكلاس والكائن”class Student{ public string Name { get; } public int Age { get; set; }
public Student(string name, int age) { Name = name; Age = age; }
public void Introduce() { Console.WriteLine($"أنا {Name} وعمري {Age} سنة"); }}var s1 = new Student("سارة", 14);var s2 = new Student("عمر", 15);s1.Introduce();s2.Introduce();أنا سارة وعمري 14 سنةأنا عمر وعمري 15 سنة- الكلاس قالب، والكائن نسخة منه تُنشأ بـ
new. - المُنشئ دالة اسمها نفس اسم الكلاس، تُنفَّذ عند الإنشاء.
public: متاح من أي مكان. وprivate: داخل الكلاس فقط.
تخيّل الكلاس مثل استمارة تسجيل فارغة في مدرسة، وكل كائن استمارة ممتلئة لطالب محدد.
الخصائص (Properties)
Section titled “الخصائص (Properties)”Name { get; } و Age { get; set; } ليست حقولاً عادية، بل خصائص:
get: يسمح بالقراءة.set: يسمح بالتعديل.Nameلهاgetفقط، فلا يمكن تغييرها بعد الإنشاء إلا داخل المُنشئ.
وقوتها الحقيقية أنك تستطيع إضافة تحقق عند التعديل:
class Student{ private int age;
public int Age { get => age; set { if (value < 0) { Console.WriteLine("العمر لا يمكن أن يكون سالباً"); return; } age = value; } } // ...}s2.Age = 16;s2.Age = -3;Console.WriteLine(s2.Age);العمر لا يمكن أن يكون سالباً16لاحظ: تستخدمها كأنها متغير عادي s2.Age = 16، لكن في الخلفية تُنفَّذ دالة set وتتحقق من القيمة. والكلمة value هي القيمة الجديدة التي يحاول أحدهم إسنادها. والحقل الخاص age يحمل البيانات فعلاً. هذا هو التغليف بأسلوب C#: أنيق في الاستخدام، ومحمي في الداخل.
private set: قراءة للجميع وتعديل من الداخل
Section titled “private set: قراءة للجميع وتعديل من الداخل”class BankAccount{ public string Owner { get; } public decimal Balance { get; private set; }
public BankAccount(string owner) { Owner = owner; }
public void Deposit(decimal amount) { if (amount <= 0) { Console.WriteLine("المبلغ يجب أن يكون أكبر من صفر"); return; } Balance += amount; }}var acc = new BankAccount("منى");acc.Deposit(500);acc.Deposit(-20);Console.WriteLine(acc.Balance);المبلغ يجب أن يكون أكبر من صفر500private set تعني: الجميع يقرأ Balance، لكن لا يغيّرها إلا الكلاس نفسه عبر Deposit الذي يتحقق من المبلغ. كتابة acc.Balance = 1000000; من الخارج يرفضها المترجم.
ToString
Section titled “ToString”public override string ToString() => $"Student({Name}, {Age})";بدونها، Console.WriteLine(s1) تطبع اسم الكلاس فقط. ومعها تطبع Student(سارة, 14).
الوراثة: virtual و override
Section titled “الوراثة: virtual و override”class Animal{ protected string name;
public Animal(string name) { this.name = name; }
public virtual void Speak() { Console.WriteLine($"{name} يصدر صوتاً"); }}
class Cat : Animal{ public Cat(string name) : base(name) { }
public override void Speak() { Console.WriteLine($"{name} يقول: مياو"); }}List<Animal> animals = [new Animal("الحصان"), new Cat("مشمش")];foreach (var a in animals){ a.Speak();}الحصان يصدر صوتاًمشمش يقول: مياوclass Cat : Animal: القطة ترث من الحيوان.: base(name): استدعاء مُنشئ الأب.virtualفي الأب: تسمح بإعادة تعريف الدالة، وoverrideفي الابن: تعيد تعريفها.protected: متاح للكلاس وللكلاسات الوارثة فقط.this.name: الحقل، للتمييز بينه وبين المعامل الذي يحمل نفس الاسم.
قائمة من نوع Animal تحمل قطة، وعند استدعاء Speak تعمل نسخة القطة. هذا هو تعدد الأشكال (Polymorphism).
record: كلاس البيانات
Section titled “record: كلاس البيانات”record Book(string Title, int Pages);var b1 = new Book("البرمجة للمبتدئين", 250);var b2 = new Book("البرمجة للمبتدئين", 250);Console.WriteLine(b1);Console.WriteLine(b1 == b2);var b3 = b1 with { Pages = 300 };Console.WriteLine(b3);Book { Title = البرمجة للمبتدئين, Pages = 250 }TrueBook { Title = البرمجة للمبتدئين, Pages = 300 }سطر واحد يعطيك: مُنشئاً، وخصائص للقراءة، وطباعة مفهومة، ومقارنة بالمحتوى. والكلمة with تنشئ نسخة جديدة مع تعديل خاصية، والأصل يبقى كما هو. استخدم record لكل كلاس وظيفته حمل البيانات.
جرّب بنفسك
Section titled “جرّب بنفسك”- صمم كلاس
ProductبخصائصNameوPrice(تحقق ألا يكون سالباً) وQuantity، وخاصية محسوبةTotal => Price * Quantity. - أنشئ
List<Product>من 3 منتجات، واطبع الإجمالي الكلي باستخدام LINQ:products.Sum(p => p.Total). - أنشئ
record Student(string Name, int Score)وقائمة طلاب، واطبع أسماء الناجحين مرتبين بالدرجة. - تحدٍّ: كلاس
Shapeبدالةvirtual double Area()، وكلاسانCircleوRectangleيعيدان تعريفها، ثم اطبع مساحات قائمة أشكال.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان virtual في الأب: لا يمكن استخدام
overrideفي الابن، والمترجم يعترض. - تعديل خاصية لها get فقط: خطأ ترجمة. أضف
set، أوprivate set، أو غيّرها في المُنشئ. - حقول public بدل الخصائص: العُرف في C# أن تكون البيانات العامة خصائص
{ get; set; }، والحقول خاصة.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا تعني public decimal Balance { get; private set; }؟
get عام فيسمح بالقراءة للجميع، و private set يقصر التعديل على الكلاس نفسه.
سؤال
ما فائدة with مع record؟
b1 with { Pages = 300 } تنشئ كائناً جديداً بنفس بيانات b1 مع تغيير Pages، و b1 لا يتغير.
الخلاصة
Section titled “الخلاصة”- الكلاس قالب، والكائن نسخة منه بـ
new، والمُنشئ يهيئه. - الخصائص
{ get; set; }واجهة أنيقة للبيانات، ويمكن إضافة تحقق داخلsetمعvalue. private setللقراءة العامة والتعديل الداخلي.- الوراثة بـ
: Baseوbase(...)، معvirtualفي الأب وoverrideفي الابن. recordلكلاسات البيانات: طباعة ومقارنة بالمحتوى وwith.