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

المُنشئ Constructor في C#: الافتراضي وذو المعاملات والتسلسل بـ this() وbase()

كيف تعمل المُنشئات في C#: المُنشئ الافتراضي الضمني ومتى يختفي، والمُنشئات ذات المعاملات والمحمّلة، والتسلسل بـ this(...) وbase(...)، وترتيب تنفيذ الأشياء، والمُنشئات الساكنة والخاصة، والمُنشئات الأساسية في C# 12.

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

المُنشئ (constructor) هو الشيفرة التي تعمل حين تنشئ كائنًا بـ new. يحمل اسم الصنف نفسه، ولا نوع إرجاع له، ومهمته وضع الكائن الجديد في حالة صالحة قبل أن يستخدمه أحد.

المخرجات:

Mug: 8.50
Rejected: price

لأن المُنشئ يرفض السعر السالب، لا يمكن أن يوجد Product بسعر سالب. هذا الضمان هو السبب الرئيسي لكتابة المُنشئات بدل ضبط الحقول من الخارج.

المُنشئ الافتراضي، ومتى يختفي

إن لم يصرّح الصنف بأي مُنشئ، يقدّم المترجم مُنشئًا عامًا بلا معاملات لا يفعل شيئًا سوى تشغيل مهيّئات الحقول. ولهذا تعمل new BankAccount() على صنف ليس فيه إلا حقول.

وبمجرد أن تصرّح بأي مُنشئ، لا يعود ذلك الضمني يُولَّد:

class Product
{
    public string Name;
    public Product(string name) { Name = name; }
}

var p = new Product();   // error CS7036: There is no argument given that corresponds to the required parameter 'name'

هذا يفاجئ الناس حين تحتاج أداة تسلسل أو ORM أو قيد new() عام إلى مُنشئ بلا معاملات. إن أردت الاثنين فصرّح بالذي بلا معاملات بنفسك:

public Product() { }                       // or
public Product() : this("Unnamed") { }     // delegate to the other one (next section)

يمكن أن يكون المُنشئ public أو internal أو protected أو private. ويحدد وصوله من يستطيع إنشاء كائنات به.

التحميل الزائد والتسلسل بـ this(...)

يمكن أن يكون للصنف عدة مُنشئات بقوائم معاملات مختلفة. لتجنّب نسخ الإسنادات نفسها في كل واحد سلسلها: تستدعي : this(...) مُنشئًا آخر من الصنف نفسه قبل تنفيذ الجسم الحالي.

المخرجات:

new Pizza():
  full constructor: medium, classic, 0
  size-only constructor body
  parameterless constructor body
Result: medium classic with 0 toppings

تشغّل السلسلة المُنشئ الأكمل أولًا وتعود إلى الذي استدعيته. أبقِ العمل الحقيقي (التحقق والإسنادات) في مُنشئ واحد ودع الآخرين يقدّمون القيم الافتراضية فقط.

المعاملات الاختيارية بديل: تعطي public Pizza(string size = "medium", string crust = "classic", int toppings = 0) مُنشئًا واحدًا يغطي الاستدعاءات الثلاثة. لكن التسلسل يظل الأنسب حين تحتاج المُنشئات الأقصر إلى حساب شيء، أو حين يجب ألا يتطلب تغيير قيمة افتراضية إعادة ترجمة المستدعين (قيم المعاملات الاختيارية الافتراضية تُنسخ في الشيفرة المستدعية وقت الترجمة).

استدعاء مُنشئ الصنف الأساس بـ base(...)

الصنف المشتق لا يرث المُنشئات. يجب على كل مُنشئ في الصنف المشتق أن يشغّل مُنشئًا من الصنف الأساس أولًا. إن لم تكتب شيئًا يُدرج المترجم استدعاءً لمُنشئ الأساس الذي بلا معاملات؛ وإن لم يكن للأساس واحد فعليك اختيار واحد بـ : base(...).

ترتيب تنفيذ كل شيء يستحق أن تراه مرة:

المخرجات:

Truck field initializer
Vehicle field initializer
Vehicle constructor body
Truck constructor body
KL-204, 3 axles

تعمل مهيّئات الحقول أولًا، الصنف المشتق قبل الصنف الأساس، ثم تعمل أجسام المُنشئات من الأساس نزولًا. فحين يُنفَّذ جسم Truck تكون Plate مضبوطة أصلًا. الفخ الوحيد في هذا الترتيب: إن استدعى مُنشئ الأساس دالة virtual يتجاوزها Truck، يُنفَّذ التجاوز قبل جسم مُنشئ Truck، ويرى Axles ما زالت 0. تجنّب استدعاء الدوال الافتراضية من المُنشئات.

المُنشئات الساكنة

يهيّئ المُنشئ الساكن النوع لا الكائن. لا معاملات له ولا معدّل وصول، ويستدعيه وقت التشغيل مرة واحدة بالضبط، قبيل أول استخدام للنوع.

المخرجات:

Program started
Loading tax table (runs once)
123.00
119.00

يجعل وقت التشغيل المُنشئ الساكن آمنًا مع الخيوط: حتى لو لمست عدة خيوط النوع في الوقت نفسه يعمل مرة واحدة. وإن رمى استثناءً فكل استخدام لاحق للنوع يرمي TypeInitializationException طوال عمر العملية، فأبقه بسيطًا وخاليًا من عمليات الإدخال والإخراج التي قد تفشل.

المُنشئ مقابل مهيّئ الكائن

مهيّئ الكائن، new Pizza("large") { Toppings = 3 }، ليس مُنشئًا ثانيًا. يحوّله المترجم إلى "شغّل المُنشئ، ثم أسند هذه الأعضاء". استخدم كلًّا فيما يجيده:

  • معاملات المُنشئ للقيم التي لا يكون الكائن صالحًا دونها. يجب على كل مستدعٍ تمريرها، ويستطيع المُنشئ فحصها.
  • المهيّئ للإعدادات الاختيارية ذات القيم الافتراضية المعقولة.
var order = new Order(customerId: 42) { Note = "Leave at the door", GiftWrap = true };

منذ C# 11 يجب ضبط الخاصية المعلّمة بـ required في المهيّئ، وهذا يمنح صياغة المهيّئ بعض ضمانات المُنشئ. وهذا مشروح في صفحة الخصائص.

المُنشئات الخاصة

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

المخرجات:

21
100

خطأ المترجم لـ new Temperature(5m) خارج الصنف هو CS0122، وهو نفسه الذي تنتجه معدّلات الوصول لأي عضو خاص. تحلّ دوال المصنع المسمّاة قيدًا حقيقيًا: لا يمكن لمُنشئين أن يأخذا كلاهما decimal واحدًا، لكن FromCelsius وFromFahrenheit تستطيعان. أما الصنف الذي ليس فيه إلا أعضاء ساكنة (صنف أدوات) فيجب التصريح به static بدل إخفاء مُنشئه.

المُنشئات بجسم تعبيري

المُنشئ الذي جسمه عبارة واحدة يمكنه استخدام =>:

public Product(string name) => Name = name;

ومع الصفوف يمكنك حتى إسناد عدة حقول في سطر واحد: public Point(int x, int y) => (X, Y) = (x, y);.

المُنشئات الأساسية (C# 12)

تتيح C# 12 لصنف أو بنية التصريح بمعاملات مُنشئها على النوع نفسه. وتكون المعاملات في النطاق في كل عضو:

public class Customer(string name, int id)
{
    public string Name { get; } = name;          // copy into a property
    public string Label => $"#{id} {Name}";      // or read a parameter directly

    public Customer(string name) : this(name, 0) { }   // other constructors must chain to it
}

فرقان عن السجلات يفاجئان الناس. معاملات المُنشئ الأساسي في الصنف لا تصبح خصائص عامة؛ تكشفها بنفسك. والمعامل المستخدم داخل عضو يُلتقط في حقل مخفي يبقى قابلًا للتغيير، فتُترجم name = "x"; داخل دالة. المُنشئات الأساسية في بيئتها الطبيعية مع حقن الاعتماديات، حيث المعاملات خدمات لا يفعل الصنف سوى استدعائها:

public class OrderService(IOrderRepository repo, ILogger<OrderService> log)
{
    public Order Get(int id) => repo.Find(id);
}

أخطاء شائعة

  • فقدان المُنشئ الذي بلا معاملات. إضافة مُنشئ بمعاملات تزيل الضمني (CS7036 عند كل new X()).
  • نسيان : base(...) حين لا يملك الصنف الأساس مُنشئًا بلا معاملات. لا يستطيع المترجم إدراج الاستدعاء الضمني ويبلّغ بأنه لا وسيط للمعامل الإلزامي لمُنشئ الأساس.
  • استدعاء دوال افتراضية في مُنشئ. يُنفَّذ التجاوز قبل جسم المُنشئ المشتق.
  • عمل ثقيل في مُنشئ. استدعاءات الشبكة أو قراءة الملفات أو أي شيء بطيء تجعل الكائنات مكلفة والاستثناءات صعبة المعالجة. استخدم دالة مصنع ساكنة، أو دالة تهيئة غير متزامنة، لذلك العمل.
  • كتابة نوع إرجاع. public void Product() دالة عادية اسمها Product، ويرفضها المترجم لأن العضو لا يمكن أن يحمل اسم النوع المحيط به (CS0542).

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

ما هو المُنشئ في C#؟

المُنشئ دالة خاصة تعمل حين يُنشأ كائن بـ new. يحمل اسم الصنف نفسه ولا نوع إرجاع له: public Product(string name) { Name = name; }. مهمته أن يترك الكائن الجديد في حالة صالحة، عادة بإسناد الحقول من معاملاته ورفض المدخلات السيئة.

هل تنشئ C# مُنشئًا افتراضيًا تلقائيًا؟

فقط حين لا يصرّح الصنف بأي مُنشئ إطلاقًا. عندها يضيف المترجم مُنشئًا عامًا بلا معاملات يترك كل حقل عند مهيّئه أو قيمته الافتراضية. وبمجرد أن تكتب أي مُنشئ، مثلًا واحدًا يأخذ اسمًا، يختفي الضمني وتتوقف new Product() عن الترجمة (الخطأ CS7036). صرّح بـ public Product() { } بنفسك إن ظللت تحتاجه.

كيف أستدعي مُنشئًا من آخر في C#؟

استخدم : this(...) بعد قائمة معاملات المُنشئ: public Product(string name) : this(name, 0m) { }. يُنفَّذ المُنشئ الهدف أولًا، ثم جسم المُنشئ الذي استدعيته. ولاستدعاء مُنشئ الصنف الأساس استخدم : base(...) بالطريقة نفسها.

ما هو المُنشئ الساكن في C#؟

مُنشئ معلَّم بـ static، بلا معاملات ولا معدّل وصول، يعمل مرة واحدة لكل نوع قبل إنشاء أول مثيل أو استخدام أول عضو ساكن. يُستخدم لتهيئة الحقول الساكنة التي تحتاج إلى أكثر من مهيّئ في سطر واحد. لا يمكنك استدعاؤه بنفسك، وإن رمى استثناءً يصبح النوع غير قابل للاستخدام لبقية البرنامج (TypeInitializationException).

هل أستخدم مُنشئًا أم مهيّئ كائن؟

استخدم معاملات المُنشئ للقيم التي لا يكون الكائن صالحًا دونها، لأن المترجم يجبر كل مستدعٍ على تمريرها ويستطيع المُنشئ فحصها. واستخدم مهيّئ الكائن (new Product("Mug") { Color = "blue" }) للإعدادات الاختيارية. يعمل المهيّئ بعد انتهاء المُنشئ.

ما هي المُنشئات الأساسية في C# 12؟

معاملات تُكتب على تصريح الصنف نفسه، class Customer(string name, int id) { ... }، متاحة لكل عضو في الصنف. بخلاف المعاملات الموضعية للسجل لا تصبح خصائص عامة: اكشفها صراحة بـ public string Name => name; أو public string Name { get; } = name;.

Coddy programming languages illustration

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

ابدأ الآن