C#: معدِّلات الوصول والخصائص

1. مُعدِّلات الوصول

تتحكم مُعدِّلات الوصول في إمكانية الوصول إلى عناصر الفئة، وهي الآلية الأساسية للتغليف. توفر لغة C# ستة مستويات للوصول:

المُعدِّل نفس الفئة الفئة الفرعية (نفس التجميع) الفئة الفرعية (تجميع مختلف) نفس التجميع تجميع مختلف
public
private
protected
internal
protected internal
private protected
💡 protected internal هي علاقة من نوع "أو" (OR): يمكن للفئات الفرعية أو نفس التجميع الوصول إليها. private protected هي علاقة من نوع "و" (AND): يجب أن تكون كلا الفئة الفرعية و نفس التجميع.

▶ مثال

CSHARP
public class Animal
{
    public string Species;
    private int health = 100;
    protected int energy = 90;
    internal string category = "Mammal";

    public void ShowInfo()
    {
        Console.WriteLine($"Species: {Species}, Health: {health}, Energy: {energy}, Category: {category}");
    }

    public void SetHealth(int h) { health = h; }
}

public class Dog : Animal
{
    public void Run()
    {
        energy -= 10;
        category = "Mammal";
    }
}

class Program
{
    static void Main()
    {
        Animal a = new Dog { Species = "Dog" };
        a.ShowInfo();
        ((Dog)a).Run();
        a.ShowInfo();
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Species: Dog, Health: 100, Energy: 90, Category: Mammal
Species: Dog, Health: 100, Energy: 80, Category: Mammal

2. مبادئ التغليف

يتطلب التغليف تعيين الحقول إلى private والتحكم في الوصول للقراءة/الكتابة من خلال خصائص public، مما يمنع الكود الخارجي من تعديل البيانات الداخلية بشكل مباشر.

▶ مثال

CSHARP
public class BankAccount
{
    private decimal balance;

    public decimal Balance
    {
        get { return balance; }
        set
        {
            if (value < 0)
                throw new ArgumentException("Balance cannot be negative");
            balance = value;
        }
    }
}
▶ جرّب الكود
CSHARP
var account = new BankAccount();
account.Balance = 1000m;
Console.WriteLine($"Balance: {account.Balance}");
TEXT 📖 للعرض فقط
Balance: 1000
⚠️ إن إنشاء الحقول باستخدام public يؤدي مباشرةً إلى كسر مبدأ التغليف ويمنع إضافة منطق التحقق من الصحة لاحقًا. استخدم دائمًا الحقول private + الخصائص public.


3. الخصائص

تُعد «الخصائص» غلافات آمنة للحقول، حيث تتحكم في منطق القراءة/الكتابة من خلال واصلات الوصول get وset. ويُعد value المعلمة الضمنية لواصلة الوصول set، وهو يمثل القيمة التي يعيّنها المُستدعي.

▶ مثال

CSHARP
public class Student
{
    private string name;

    public string Name
    {
        get { return name; }
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Name cannot be empty");
            name = value;
        }
    }
}
▶ جرّب الكود
CSHARP
var s = new Student();
s.Name = "Alice";
Console.WriteLine(s.Name);
TEXT 📖 للعرض فقط
Alice

4. الخصائص التلقائية

عندما لا تكون هناك حاجة إلى منطق إضافي، استخدم الخصائص التلقائية { get; set; }، حيث يقوم المُجمِّع تلقائيًّا بإنشاء حقل دعم مخفي.

▶ مثال

CSHARP
public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}
▶ جرّب الكود
CSHARP
var p = new Product { Name = "Pen", Price = 2.5m };
Console.WriteLine($"{p.Name}: ${p.Price}");
TEXT 📖 للعرض فقط
Pen: $2.5
📌 تقلل الخصائص التلقائية من الكود النمطي. وعند الحاجة إلى التحقق من الصحة، يمكن توسيعها لتصبح خصائص كاملة في أي وقت دون التأثير على المستدعين.


5. مُعالج التهيئة (C# 9)

تسمح أداة الوصول init بتعيين خاصية أثناء تهيئة الكائن، وبعد ذلك تصبح هذه الخاصية للقراءة فقط، مما يتيح إنشاء كائنات غير قابلة للتغيير.

▶ مثال

CSHARP
public class Config
{
    public string Host { get; }
    public int Port { get; }
    public Config(string host, int port)
    {
        Host = host;
        Port = port;
    }
}

class Program
{
    static void Main()
    {
        var cfg = new Config("localhost", 8080);
        System.Console.WriteLine($"{cfg.Host}:{cfg.Port}");
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
localhost:8080
⚠️ يؤدي تعيين قيمة بعد التهيئة إلى حدوث خطأ في التحويل البرمجي: cfg.Port = 9090; غير صحيح.


6. الخصائص المخصصة للقراءة فقط

الخاصية التي تحتوي على get فقط هي خاصية للقراءة فقط. ولا يمكن تعيين قيمتها إلا في مُنشئ الكائن من خلال الحقل الداعم.

▶ مثال

CSHARP
public class Circle
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;
    }
}
▶ جرّب الكود
CSHARP
var c = new Circle(5.0);
Console.WriteLine($"Radius: {c.Radius}");
TEXT 📖 للعرض فقط
Radius: 5

7. الخصائص المحسوبة

الخصائص المحسوبة لا تحتوي على حقل داعم. فهي تُحسب وتُرجع القيم ديناميكيًا باستخدام العناصر التي تتكون من تعبيرات =>، دون تخزين البيانات.

▶ مثال

CSHARP
public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }

    public double Area => Width * Height;
    public double Perimeter => 2 * (Width + Height);
}
▶ جرّب الكود
CSHARP
var r = new Rectangle { Width = 3, Height = 4 };
Console.WriteLine($"Area: {r.Area}, Perimeter: {r.Perimeter}");
TEXT 📖 للعرض فقط
Area: 12, Perimeter: 14

8. المُعدِّل المطلوب (C# 11)

يُجبر المُعدِّل required المُستدعين على تعيين قيمة للخاصية أثناء التهيئة، وإلا فسيحدث خطأ في التحويل البرمجي.

▶ مثال

CSHARP
public class User
{
    public string Name { get; }
    public string Email { get; }
    public int Age { get; }
    public User(string name, string email, int age)
    {
        Name = name;
        Email = email;
        Age = age;
    }
}

class Program
{
    static void Main()
    {
        var u = new User("Bob", "bob@test.com", 25);
        System.Console.WriteLine($"{u.Name} ({u.Email})");
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Bob (bob@test.com)
💡 عادةً ما يُستخدم required مقترنًا بـ init لضمان عدم إغفال الحقول الإلزامية في الكائنات غير القابلة للتغيير.


9. مقدمة عن السجلات

record هو نوع مرجعي تم تقديمه في C# 9، وهو يدعم بشكل أصلي خاصية الثبات والمساواة على أساس القيمة، مما يجعله مثاليًّا لكائنات البيانات البحتة.

▶ مثال

CSHARP
public class Person : IEquatable<Person>
{
    public string Name { get; }
    public int Age { get; }
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    public bool Equals(Person other)
    {
        return other != null && Name == other.Name && Age == other.Age;
    }
    public override bool Equals(object obj) => Equals(obj as Person);
    public override int GetHashCode() => System.HashCode.Combine(Name, Age);
    public override string ToString() => $"Person {{ Name = {Name}, Age = {Age} }}";
}

class Program
{
    static void Main()
    {
        var p1 = new Person("Alice", 30);
        var p2 = new Person("Alice", 30);
        System.Console.WriteLine($"Equal: {p1 == p2}");
        System.Console.WriteLine(p1);
    }
}
▶ جرّب الكود
CSHARP
var p1 = new Person("Alice", 30);
var p2 = new Person("Alice", 30);
Console.WriteLine($"Equal: {p1 == p2}");
Console.WriteLine(p1);
TEXT 📖 للعرض فقط
Equal: True
Person { Name = Alice, Age = 30 }
🔥 تُنشئ قائمة معلمات السجل تلقائيًّا خصائص init للقراءة فقط، كما تُجري == المقارنة حسب القيمة وليس حسب المرجع. وسيتم تناول هذا الموضوع بالتفصيل في الدروس اللاحقة.


❓ أسئلة شائعة

س ما الفرق بين protected internal وprivate protected؟
ج protected internal هي علاقة "أو" (يمكن الوصول إليها عن طريق الفئات الفرعية أو نفس التجميع)، وprivate protected هي علاقة "و" (يجب أن تكون فئة فرعية وفي نفس التجميع).
س ما الفرق بين الخصائص التلقائية والحقول العامة؟
ج تحتوي الخصائص التلقائية على حقل داعم، ويمكن توسيعها لإضافة منطق في أي وقت، كما أنها تدعم تنفيذ الواجهة وربط البيانات، وهي أمور لا تستطيع الحقول العامة القيام بها.
س ما الفرق بين init وset؟
ج يمكن تعيين set في أي وقت، أما init فلا يمكن تعيينه إلا أثناء تهيئة الكائن (من خلال منشئ الكائن أو مُهيئ الكائن)، وبعد ذلك يصبح للقراءة فقط.
س هل يمكن تعيين قيمة لخاصية للقراءة فقط { get; } خارج المنشئ؟
ج لا. لا يمكن تعيينها إلا داخل المنشئ من خلال الحقل الداعم، ولا يمكن تعديلها بعد ذلك.
س ما هو الفرق الأساسي بين record وclass؟
ج record يقارن التساوي حسب القيمة بشكل افتراضي، ويدعم التعبير with لإنشاء نسخ، وهو مناسب للبيانات غير القابلة للتغيير؛ أما class فيقارن بالمرجع.

📖 ملخص

📝 تمارين

  1. قم بإنشاء فئة Temperature تحتوي على حقل celsius تم تعيين قيمته إلى private، واستخدم واصلة الوصول set الخاصة بالخاصية Celsius لتقييد درجة الحرارة بحيث لا تقل عن -273.15، وتوفير خاصية محسوبة Fahrenheit تُرجع القيمة المحولة
  2. أنشئ فئة Book باستخدام الخصائص التلقائية (Title، Author، Price)، وأضف التحقق من أن القيمة غير سالبة في set الخاص بـ Price (تلميح: قم بتوسيعها إلى خاصية كاملة)
  3. قم بتعريف فئة ServerConfig حيث تستخدم Host وPort واصلات الوصول الخاصة بـ init ويتم تمييزهما بـ required، وحاول تعديلهما بعد التهيئة ولاحظ خطأ التحويل البرمجي
  4. عرّف Point(double X, double Y) باستخدام record، وأنشئ مثيلين بنفس الإحداثيات، وتأكد من أن مقارنة == تُرجع True
  5. قم بإنشاء فئة Employee تحتوي على Id كخاصية للقراءة فقط (يتم تعيينها في المنشئ)، وName التي تستخدم required init، وSalary باستخدام خاصية set مع التحقق من الصحة، وAnnualSalary كخاصية محسوبة
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%