Menu
flag Ar iconالعربيةdown icon

الخصائص في C#: get وset والخصائص التلقائية وinit وrequired

كيف تعمل الخصائص في C#: موصّلا get وset فوق حقل داعم، والخصائص التلقائية، وprivate set والخصائص للقراءة فقط، والخصائص المحسوبة، والتحقق في الضوابط، والكلمتان init وrequired من C# 9 و11.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

تبدو الخاصية (property) كحقل للشيفرة التي تستخدمها (order.Total وorder.Total = 5) لكنها في الحقيقة زوج من الدوال: موصّل get يعيد قيمة وموصّل set يستقبل واحدة. هذه الوساطة تتيح للصنف فحص القيم أو حسابها أو رفض الكتابة، دون تغيير طريقة استخدام المستدعين لها.

الحقول مقابل الخصائص

الحقل العام يمنح وصولًا مباشرًا إلى تخزين الكائن. يستطيع أي أحد وضع أي شيء فيه:

public class Account
{
    public decimal Balance;   // any code can write -500 here
}

تضع الخاصية شيفرة بين المستدعي والتخزين. هذا هو الشكل الكامل المكتوب يدويًا: حقل داعم خاص مع خاصية لها الموصّلان. داخل set تكون الكلمة value القيمة التي يُسندها المستدعي.

المخرجات:

250
Rejected a negative balance
250

بعيدًا عن التحقق، تهمّ الخصائص لأن معظم منظومة .NET تعمل معها وتتجاهل الحقول: يمكن للواجهات التصريح بخصائص لا بحقول مثيل، وربط البيانات في WPF وMAUI يرتبط بالخصائص، ويسلسل System.Text.Json الخصائص العامة افتراضيًا ويتخطى الحقول.

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

حين لا يفعل الموصّلان سوى قراءة الحقل وكتابته، دع المترجم يكتبهما. { get; set; } خاصية منفّذة تلقائيًا: يولّد المترجم الحقل الداعم المخفي عنك.

المخرجات:

Notebook: 9.99, 10 left

تبدو كحقل، فلماذا العناء؟ لأن تحويل حقل عام إلى خاصية لاحقًا تغيير كاسر للشيفرة المترجمة التي تستخدمه (ولوسائط ref وout)، بينما تغيير خاصية تلقائية إلى خاصية كاملة بتحقق غير مرئي للمستدعين. البدء بـ { get; set; } يبقي هذا الخيار مفتوحًا مجانًا.

private set والخصائص للقراءة فقط

معظم الحالة يجب أن يقرأها الجميع ولا يغيّرها إلا الكائن نفسه. امنح الضابط معدّل وصول أضيق:

المخرجات:

Ines: 2 items, total 15.75

ثلاثة أنواع من القراءة فقط هنا:

  • { get; private set; }: يستطيع الصنف تغييرها في أي وقت، ولا تستطيع الشيفرة الخارجية.
  • { get; } (خاصية تلقائية للقراءة فقط، C# 6): لا يمكن الإسناد إليها إلا في مُنشئ أو مهيّئ. بعد الإنشاء لا يستطيع أحد تغييرها، ولا حتى الصنف. هكذا تصنع خاصية غير قابلة للتغيير.
  • => expression (خاصية محسوبة): لا تخزين إطلاقًا. يُنفَّذ التعبير في كل قراءة، فتكون Total دائمًا محدّثة مع القائمة.

يجب أن تكون الخاصية المحسوبة رخيصة وبلا آثار جانبية، لأن المستدعين يقرؤون الخصائص بلا تحفّظ، في الحلقات وفي المصحّح. إن كان الحصول على القيمة يتطلب عملًا حقيقيًا (استعلام قاعدة بيانات، حساب كبير) فاجعله دالة مثل CalculateTotal() كي تكون الكلفة ظاهرة.

الموصّلات بجسم تعبيري

الخصائص الكاملة ذات الموصّلات من سطر واحد يمكنها استخدام => لكل موصّل:

private string title;

public string Title
{
    get => title;
    set => title = value?.Trim() ?? "";
}

public decimal Total => SumPrices(); اختصار لخاصية لها موصّل get فقط؛ وget => ... الفكرة نفسها داخل خاصية لها ضابط أيضًا.

منطق الموصّلات: الإشعارات والقيم الكسولة

لأن الموصّلات دوال فيمكنها فعل أكثر من تخزين قيمة. نمطان شائعان: إطلاق إشعار تغيير من الضابط، وحساب قيمة عند أول قراءة وتخزينها مؤقتًا.

المخرجات:

(building summary)
Theme: light
Theme: light
Theme changed
(building summary)
Theme: dark

Changed حدث، ونمط الضابط هذا هو شكل تنفيذات INotifyPropertyChanged في WPF وMAUI. تبقي nameof(Theme) النص متزامنًا إن أُعيدت تسمية الخاصية.

خطأ الاستدعاء الذاتي اللانهائي

أشيع خطأ في الخصائص ضابط يُسند إلى الخاصية بدل الحقل الداعم:

public string Name
{
    get { return Name; }        // calls get again, forever
    set { Name = value; }       // calls set again, forever
}

كل موصّل يستدعي نفسه، ويموت البرنامج بـ StackOverflowException، الذي لا يمكن التقاطه. الإصلاح القراءة والكتابة في حقل منفصل (name بحرف صغير)، أو استخدام خاصية تلقائية. تضيف C# 14 الكلمة field لهذه الحالة بالضبط: داخل موصّل تشير field إلى الحقل الداعم الذي يولّده المترجم، فتعمل set => field = value.Trim(); دون التصريح بواحد.

موصّلات init (C# 9)

الخاصية للقراءة فقط تجبرك على تمرير كل قيمة عبر مُنشئ. أضافت C# 9 الموصّل init، وهو ضابط مسموح به فقط أثناء إنشاء الكائن: في مُنشئ، أو في مهيّئ كائن.

public class Product
{
    public string Sku { get; init; }
    public decimal Price { get; init; }
}

var p = new Product { Sku = "MUG-01", Price = 8.50m };   // fine: during creation
p.Price = 4m;   // error CS8852: Init-only property or indexer 'Product.Price' can only be assigned
                // in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor

النتيجة كائن غير قابل للتغيير بصياغة المهيّئ، وهو بالضبط ما تستخدمه أصناف السجلات لخصائصها الموضعية. لـ "تغيير" مثل هذا الكائن أنشئ واحدًا جديدًا؛ وتجعل السجلات ذلك سطرًا واحدًا بـ with.

وفي C# 7 أقرب مكافئ خاصية للقراءة فقط مع معامل مُنشئ:

public class Product
{
    public string Sku { get; }
    public decimal Price { get; }
    public Product(string sku, decimal price) { Sku = sku; Price = price; }
}

الأعضاء required (C# 11)

لا يستطيع مهيّئ الكائن إجبار المستدعين على ضبط أي شيء: تُترجم new Product { } حتى حين يجب ألا تكون Sku فارغة أبدًا. أضافت C# 11 المعدّل required:

public class User
{
    public required string Email { get; init; }
    public string DisplayName { get; init; } = "";
}

var ok = new User { Email = "ana@example.com" };
var bad = new User { DisplayName = "Ana" };   // error CS9035: Required member 'User.Email'
                                              // must be set in the object initializer or attribute constructor.

تعمل required مع set وinit. والمُنشئ الذي يضبط الأعضاء الإلزامية بنفسه يمكنه قول ذلك بالسمة [SetsRequiredMembers]، فلا يُطلب من المستدعين الذين يستخدمون ذلك المُنشئ ضبطها مجددًا.

أخطاء شائعة

  • الاستدعاء الذاتي في موصّل. تستدعي set { Name = value; } نفسها. استخدم حقلًا داعمًا أو خاصية تلقائية.
  • موصّلات get مكلفة أو ذات آثار جانبية. يتوقع القرّاء أن تكون قراءة الخاصية سريعة وآمنة للتكرار. استخدم دالة للعمل.
  • حقول عامة "مؤقتًا". تحويل حقل إلى خاصية لاحقًا يكسر التوافق الثنائي، وأدوات التسلسل تتجاهل الحقول افتراضيًا. ابدأ بخاصية تلقائية.
  • ضوابط عامة على كل شيء. { get; set; } على رصيد أو حالة يدعو إلى حالات غير صالحة. استخدم private set أو init أو القراءة فقط، وغيّر الحالة عبر دوال.
  • إعادة مجموعة قابلة للتغيير من خاصية للقراءة فقط. تمنع public List<string> Tags { get; } المستدعين من استبدال القائمة، لا من الإضافة إليها. أعد IReadOnlyList<string> حين يجب ألا تتغيّر المجموعة من الخارج.

الأسئلة الشائعة

ماذا تعني { get; set; } في C#؟

تصرّح بخاصية منفّذة تلقائيًا: ينشئ المترجم حقلًا خاصًا مخفيًا وموصّل get يعيده وموصّل set يُسند إليه. تتصرف public string Name { get; set; } كحقل عام بالنسبة للمستدعين، لكنها زوج من الدوال، فيمكنك لاحقًا إضافة تحقق أو تغييرها إلى private set دون تغيير الشيفرة التي تستخدمها.

ما الفرق بين الحقل والخاصية في C#؟

الحقل متغيّر مخزّن في الكائن. والخاصية زوج من الدوال (get وset) تبدو كحقل من الخارج. تتيح الخصائص للصنف التحقق من القيم أو حساب النتائج أو تقييد الكتابة، وهي ما يعمل معه ربط البيانات وأدوات التسلسل والواجهات. يجب كشف البيانات العامة كخصائص؛ وتبقى الحقول عادة خاصة.

ما هي private set في C#؟

تتيح public int Stock { get; private set; } لأي شيفرة قراءة الخاصية لكن لا يغيّرها إلا الشيفرة داخل الصنف. هكذا يكشف الصنف حالة يملكها، مثل رصيد أو عدد، مع إبقاء كل التغييرات خلف دوال تفرض القواعد.

ماذا تفعل init في C#؟

init (C# 9) ضابط لا يعمل إلا أثناء إنشاء الكائن: في مُنشئ أو مهيّئ كائن. تسمح public string Sku { get; init; } بـ new Product { Sku = "A-1" } لكنها ترفض product.Sku = "B-2" بعد ذلك بالخطأ CS8852. تمنح الكائنات غير القابلة للتغيير صياغة المهيّئ.

ما هي الخاصية required في C#؟

الخاصية المعلّمة بـ required (C# 11) يجب أن يضبطها كل مهيّئ كائن ينشئ النوع؛ وحذفها خطأ ترجمة (CS9035). وتتّحد جيدًا مع init: يجب تقديم public required string Email { get; init; } عند الإنشاء ولا يمكن أن تتغيّر لاحقًا.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن