تخطَّ إلى المحتوى

الكلاسات في C#‎: الخصائص والوراثة و record

الدرس 7 من 11 · C#

C#‎ لغة كائنية التوجه من أساسها: كل شيء فيها يعيش داخل كلاسات. في Unity مثلاً، كل لاعب وعدو وعملة ذهبية في اللعبة هو كائن من كلاس. في هذا الدرس ستتعلم تصميم الكلاسات، ومعها ميزة مميزة في C#‎ اسمها الخصائص (Properties)، تجعل حماية البيانات أنيقة وسهلة.

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: داخل الكلاس فقط.

تخيّل الكلاس مثل استمارة تسجيل فارغة في مدرسة، وكل كائن استمارة ممتلئة لطالب محدد.

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);
الناتج
المبلغ يجب أن يكون أكبر من صفر
500

private set تعني: الجميع يقرأ Balance، لكن لا يغيّرها إلا الكلاس نفسه عبر Deposit الذي يتحقق من المبلغ. كتابة acc.Balance = 1000000; من الخارج يرفضها المترجم.

public override string ToString() => $"Student({Name}, {Age})";

بدونها، Console.WriteLine(s1) تطبع اسم الكلاس فقط. ومعها تطبع Student(سارة, 14).

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 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 }
True
Book { Title = البرمجة للمبتدئين, Pages = 300 }

سطر واحد يعطيك: مُنشئاً، وخصائص للقراءة، وطباعة مفهومة، ومقارنة بالمحتوى. والكلمة with تنشئ نسخة جديدة مع تعديل خاصية، والأصل يبقى كما هو. استخدم record لكل كلاس وظيفته حمل البيانات.

  1. صمم كلاس Product بخصائص Name و Price (تحقق ألا يكون سالباً) و Quantity، وخاصية محسوبة Total => Price * Quantity.
  2. أنشئ List<Product> من 3 منتجات، واطبع الإجمالي الكلي باستخدام LINQ: products.Sum(p => p.Total).
  3. أنشئ record Student(string Name, int Score) وقائمة طلاب، واطبع أسماء الناجحين مرتبين بالدرجة.
  4. تحدٍّ: كلاس Shape بدالة virtual double Area()، وكلاسان Circle و Rectangle يعيدان تعريفها، ثم اطبع مساحات قائمة أشكال.
  • نسيان virtual في الأب: لا يمكن استخدام override في الابن، والمترجم يعترض.
  • تعديل خاصية لها get فقط: خطأ ترجمة. أضف set، أو private set، أو غيّرها في المُنشئ.
  • حقول public بدل الخصائص: العُرف في C#‎ أن تكون البيانات العامة خصائص { get; set; }، والحقول خاصة.

سؤال

ماذا تعني public decimal Balance { get; private set; }؟

سؤال

ما فائدة with مع record؟

  • الكلاس قالب، والكائن نسخة منه بـ new، والمُنشئ يهيئه.
  • الخصائص { get; set; } واجهة أنيقة للبيانات، ويمكن إضافة تحقق داخل set مع value.
  • private set للقراءة العامة والتعديل الداخلي.
  • الوراثة بـ : Base و base(...)، مع virtual في الأب و override في الابن.
  • record لكلاسات البيانات: طباعة ومقارنة بالمحتوى و with.