تحدد معدّلات الوصول (access modifiers) أي شيفرة تستطيع استخدام نوع أو عضو. بها يفصل الصنف الجزء الذي تعتمد عليه الشيفرة الأخرى (واجهته العامة) عن الجزء الذي يحق له تغييره (تفاصيله الداخلية). في C# ستة منها، إضافة إلى القيم الافتراضية التي تحصل عليها حين لا تكتب شيئًا.
المعدّلات الستة
| المعدّل | النوع المحتوي | نوع مشتق، التجميع نفسه | شيفرة أخرى، التجميع نفسه | نوع مشتق، تجميع آخر | شيفرة أخرى، تجميع آخر |
|---|---|---|---|---|---|
public | نعم | نعم | نعم | نعم | نعم |
protected internal | نعم | نعم | نعم | نعم | لا |
protected | نعم | نعم | لا | نعم | لا |
internal | نعم | نعم | نعم | لا | لا |
private protected | نعم | نعم | لا | لا | لا |
private | نعم | لا | لا | لا | لا |
التجميع (assembly) مشروع مترجم واحد: ملف .dll أو .exe الذي ينتجه. "التجميع نفسه" تعني "المشروع نفسه"، ولهذا تعمل internal بمعنى "مرئي لشيفرتي، مخفي عن مستخدمي مكتبتي".
private وpublic: التغليف
private هو الافتراضي للأعضاء والخيار الصحيح لمعظم الحقول. يكشف الصنف ما يحتاجه المستدعون عبر دوال وخصائص public، وتبقي تلك الدوال بيانات الكائن متّسقة.
المخرجات:
False
True
Priya: 180
لأن balance خاص، فالطريقتان الوحيدتان لتغييره هما Deposit وTryWithdraw، وكلتاهما تفرض القواعد. لا تستطيع أي شيفرة أخرى في البرنامج إنشاء رصيد سالب، أيًّا كانت طريقة كتابتها. هذا ما يمنحه التغليف: القواعد تعيش في مكان واحد.
private على مستوى النوع لا الكائن. يمكن لدالة في Account قراءة other.balance على مثيل Account آخر، وهكذا تُكتب عادة Equals ودوال المقارنة.
protected: مرئي للأصناف المشتقة
العضو protected مخفي عن العالم الخارجي لكنه متاح للأصناف التي ترث من الصنف المصرّح به.
المخرجات:
Sam: 4000.00
Rita: 6400.00
هناك قاعدة واحدة تفاجئ الناس. داخل Manager يمكنك استخدام baseSalary على this أو على Manager آخر، لكن لا على Employee اعتباطي:
class Manager : Employee
{
public decimal Compare(Employee other)
{
return baseSalary - other.baseSalary;
// error CS1540: Cannot access protected member 'Employee.baseSalary' via a qualifier
// of type 'Employee'; the qualifier must be of type 'Manager' (or derived from it)
}
}
السبب: قد يكون other من نوع Contractor مشتق أيضًا من Employee، وprotected تمنح الوصول إلى فرعك من التسلسل الهرمي، لا إلى فروع أشقائك.
الحقل المحمي يربط كل صنف مشتق بذلك الحقل إلى الأبد. تفضّل قواعد شيفرة كثيرة خاصية أو دالة protected، أو تبقي الحقول خاصة وتمنح الأصناف المشتقة ما تحتاجه فقط.
internal: مرئي داخل التجميع
يمكن لأي شيفرة في المشروع نفسه استخدام الأعضاء والأنواع internal، ولا يستطيع شيء خارجه ذلك. تستخدمها المكتبة للدوال المساعدة التي تعتمد عليها أصنافها العامة:
// In the MyShop.Pricing library project
public class PriceCalculator
{
public decimal Total(Cart cart) => TaxRules.Apply(cart.Subtotal);
}
internal static class TaxRules // callers of the library cannot see this class
{
internal static decimal Apply(decimal amount) => amount * 1.2m;
}
الأنواع عالية المستوى تكون internal حين لا تكتب معدّلًا. ولهذا يكون الصنف الذي نسيت تعليمه public في مكتبة أصناف "مفقودًا" من المشروع الذي يشير إليها.
مشاريع اختبار الوحدات تجميعات منفصلة، فلا تستطيع هي أيضًا رؤية شيفرة internal. الإصلاح القياسي سمة في المكتبة:
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]
protected internal وprivate protected
يجمع هذان المعدّلات السابقة، بطريقتين متعاكستين، ويسهل الخلط بين اسميهما:
protected internalهي protected أو internal: أي شيفرة في التجميع نفسه، إضافة إلى الأصناف المشتقة في تجميعات أخرى. هي الأوسع.private protected(C# 7.2) هي protected و internal: فقط الأصناف المشتقة الموجودة أيضًا في التجميع نفسه. هي الأضيق، لصنف أساس يجب أن تبقى نقاط توسيعه داخل مكتبتك.
public class ReportBase
{
protected internal string Title; // same assembly, or subclasses anywhere
private protected int RowLimit = 500; // subclasses in this assembly only (C# 7.2)
}
القيم الافتراضية حين لا تكتب شيئًا
| المكان | الافتراضي | المسموح |
|---|---|---|
| نوع مصرّح به في فضاء أسماء | internal | public، internal، file (C# 11) |
| عضو صنف | private | الستة كلها |
| عضو بنية | private | public، internal، private (لا يمكن وراثة البنى) |
| نوع متداخل | private | الستة كلها |
| عضو واجهة | public | المعدّلات الصريحة مسموحة منذ C# 8 |
| عضو تعداد | public | لا شيء |
الصراحة تكلّف كلمة واحدة وتزيل سؤالًا من ذهن القارئ التالي، لذا تطلب معظم أدلة الأسلوب كتابة المعدّل حتى حين يطابق الافتراضي.
الوصول على مستوى الموصّل
يمكن للخاصية أو المفهرس منح أحد الموصّلين معدّلًا أضيق من الخاصية نفسها:
المخرجات:
report.pdf: 100%
يجب أن يكون معدّل الموصّل أكثر تقييدًا من معدّل الخاصية، ولا يمكن أن يملك معدّلًا إلا واحد من الموصّلين.
الوصول غير المتّسق
لا يمكن لعضو عام أن يكشف نوعًا أقل ظهورًا منه، لأن المستدعين سيتلقون شيئًا لا يُسمح لهم بتسميته:
internal class Discount { }
public class Checkout
{
public Discount Current() => null;
// error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}
القاعدة نفسها تعطي CS0051 لأنواع المعاملات وCS0053 لأنواع الخصائص. أصلحها بجعل النوع مرئيًا بقدر العضو، أو العضو مخفيًا بقدر النوع.
اختيار معدّل
ابدأ بأكثر المعدّلات تقييدًا مما يعمل ولا توسّعه إلا حين يحتاجه مستدعٍ. جعل شيء أكثر ظهورًا لاحقًا سهل ولا يكسر أحدًا أبدًا؛ أما جعله أقل ظهورًا فيكسر كل مستدعٍ استخدمه. عمليًا: الحقول private، والأعضاء التي تشكّل غاية الصنف public، والدوال المساعدة private، والأنواع الخاصة بالمكتبة internal.
الأسئلة الشائعة
ما هي معدّلات الوصول في C#؟
هناك ستة: public (أي شيفرة)، وprivate (النوع المحتوي فقط)، وprotected (النوع المحتوي والأنواع المشتقة منه)، وinternal (أي شيفرة في التجميع نفسه)، وprotected internal (التجميع نفسه، أو الأنواع المشتقة في أي مكان)، وprivate protected (الأنواع المشتقة في التجميع نفسه، C# 7.2). وأضافت C# 11 المعدّل file للأنواع المرئية في ملف مصدر واحد فقط.
ما هو معدّل الوصول الافتراضي في C#؟
أعضاء الصنف أو البنية (الحقول والدوال والخصائص والأنواع المتداخلة) تكون private افتراضيًا. والأنواع عالية المستوى (صنف مصرّح به مباشرة في فضاء أسماء) تكون internal افتراضيًا. أما أعضاء الواجهات وأعضاء التعدادات فعامة.
ماذا تعني protected في C#؟
العضو protected مرئي داخل صنفه وداخل أي صنف مشتق منه، لكن لا لشيفرة أخرى. لا يستطيع الصنف المشتق استخدامه إلا عبر نوعه الخاص: داخل Manager تعمل this.baseSalary، لكن الوصول إلى baseSalary على كائن Employee آخر هو الخطأ CS1540.
ما هو internal في C#؟
يجعل internal النوع أو العضو مرئيًا لكل الشيفرة في التجميع نفسه (ملف .dll أو .exe المترجم للمشروع نفسه) وغير مرئي خارجه. هكذا تبقي المكتبة الأصناف المساعدة خارج واجهتها العامة. وتتيح [assembly: InternalsVisibleTo("MyLib.Tests")] لمشروع اختبار أن يراها أيضًا.
كيف أصلح "is inaccessible due to its protection level"؟
هذا هو الخطأ CS0122: الشيفرة تستخدم عضوًا غير مسموح لها برؤيته، غالبًا حقلًا تُرك على الافتراضي private. إن كان يجب على الشيفرة الخارجية قراءته فاكشف خاصية أو دالة عامة؛ لا تجعل الحقل عامًا ببساطة. وإن كان نوعًا من مشروع آخر فهو على الأرجح internal.