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

حدث مثل هذا المشروع في حياتي. واحد آخر. وأنا أفعل ذلك تحت إشراف طوعي ، حيث اتبع كل سطر. وفقًا لذلك ، لم أرغب في ذلك فحسب ، بل كان علي فعل كل شيء بشكل صحيح. أحد "اليمين" كان "احترام التغليف وقريبًا من الحد الأقصى ، نظرًا لأن لديك دائمًا وقت للفتح ، وبعد ذلك سيكون الوقت متأخرًا جدًا للإغلاق". وبالتالي ، حيثما أمكن ، بدأت في استخدام معدل الوصول الداخلي بدلاً من العامة للفئات. وبالطبع ، عندما تبدأ في استخدام ميزة لغة جديدة بشكل نشط لك ، تنشأ بعض الفروق الدقيقة. أريد أن أتحدث عنها بالترتيب.
المساعدة الأساسية الهجوميةفقط للتذكير والتسمية.
- التجميع هو أصغر وحدة نشر في .NET وأحد وحدات الترجمة الأساسية. كما هو ، هذا إما .dll أو .exe. يقولون أنه يمكن تقسيمها إلى عدة ملفات تسمى الوحدات النمطية.
- معدِّل الوصول العام ، مما يعني أنه في متناول كل شخص به.
- معدل الوصول الداخلي ، مما يعني أنه تم وضع علامة عليه فقط داخل التجمع.
- محمي - معدل وصول يشير إلى أنه تم وضع علامة متاحًا فقط لورثة الفئة التي توجد فيها العلامة.
- خاص - معدل وصول يشير إلى أنه تم وضع علامة متاح فقط للفئة التي يوجد بها. ولا أحد غيرك
اختبارات وحدة والبنية ودية
في C ++ ، كانت هناك ميزة غريبة مثل الدروس الصديقة. يمكن تعيين الفصول الدراسية كأصدقاء ، ثم تم مسح حدود التغليف بينهما. أظن أن هذه ليست أغرب ميزة في C ++. ربما لم يتم تضمين حتى العشرة الأوائل الأكثر غرابة. لكن أن تطلق النار على قدمك من خلال ربط العديد من الفصول بإحكام ، فهي سهلة للغاية بطريقة ما ، ومن الصعب للغاية إيجاد حالة مناسبة لهذه الميزة.
كان الأمر الأكثر إثارة للدهشة هو معرفة أنه في .NET هناك مجموعات ودية ، وهو نوع من إعادة التفكير. وهذا هو ، يمكنك جعل تجميع واحد معرفة ما هو مخفي وراء القفل الداخلي في تجميع آخر. عندما اكتشفت ذلك ، فوجئت إلى حد ما. حسنًا ، كيف؟ ما هي النقطة؟ من سيُربط الرابطين بإحكام ويشارك في انفصالهما؟ الحالات التي يكون فيها الجمهور في أي موقف غير مفهوم ، لا نعتبره في هذه المقالة.
ثم في نفس المشروع ، بدأت أتعلم أحد فروع مسار الساموراي الحقيقي: اختبار الوحدة. وفي اختبارات وحدة فنغ شوي يجب أن تكون في مجموعة منفصلة. لنفس الشيء فنغ شوي التي يمكن أن تكون مخفية داخل التجمع ، تحتاج إلى الاختباء داخل التجمع. لقد واجهت خيار غير سارة للغاية. إما أن الاختبارات ستقع جنبًا إلى جنب وتذهب إلى العميل مع الكود المفيد له ، أو سيتم تغطية كل شيء بكلمة رئيسية عامة ، إلى متى يكمن الخبز في الرطوبة.
وهنا ، من مكان ما في صناديق ذاكرتي ، تم الحصول على شيء حول التجمعات الودية. اتضح أنه إذا كان لديك التجميع "YourAssemblyName" ، فيمكنك الكتابة هكذا:
[assembly: InternalsVisibleTo("YourAssemblyName.Tests")]
وسيشاهد التجميع "YourAssemblyName.Tests" ما هو محدد بالكلمة الأساسية الداخلية في "YourAssemblyName". يمكن إدخال هذا السطر ، قليلاً ، في AssemblyInfo.cs ، والذي ينشئه VS خصيصًا لتخزين هذه السمات.
العودة المسيئة إلى المساعدة الأساسيةفي .NET ، بالإضافة إلى السمات أو الكلمات الأساسية المضمنة بالفعل مثل الملخص والعامة والداخلية والثابتة ، يمكنك إنشاء صفحتك الخاصة. وقم بتعليقها على أي شيء تريده: الحقول والخصائص والفصول والأساليب والأحداث والمجاميع بالكامل. في C # ، لهذا يمكنك ببساطة كتابة اسم السمة بين قوسين معقوفين قبل ما تشبث به. الاستثناء هو التجميع نفسه ، حيث لا يوجد أي إشارة مباشرة في أي مكان في الكود "التجميع يبدأ هنا". هناك ، قبل اسم السمة ، تحتاج إلى إضافة التجميع:
وبالتالي ، تظل الذئاب ممتلئة ، والأغنام آمنة ، وكل شيء ممكن ما زال مختبئًا داخل التجميع ، واختبارات الوحدات تعيش في مجموعة منفصلة ، كما ينبغي أن تكون ، وتتميز ميزة بالكاد تذكرت بوجود سبب لاستخدامها. ربما السبب الوحيد القائم.
لقد نسيت تقريبا نقطة واحدة مهمة. تصرف الخاصية InternalsVisibleTo أحادي الاتجاه.
محمية <الداخلية؟
إذاً الوضع: أ و ب كانا جالسين على الأنبوب.
using System; namespace Pipe { public class A { public String SomeProperty { get; protected set; } } internal class B {
تم تدمير A في عملية مراجعة التعليمات البرمجية ، لأنه لا يستخدم خارج التجميع ، ولكن لسبب ما يسمح لنفسه أن يكون لديه معدل وصول عام ، تسبب B في خطأ في الترجمة ، مما قد يؤدي إلى حدوث ذهول في الدقائق الأولى.
في الأساس ، رسالة الخطأ منطقية. لا يمكن للكشف عن خاصية الكشف عن أكثر من الخاصية نفسها. سوف يتفاعل أي شخص بفهم إذا كان المترجم يعطي رأسًا لهذا:
internal String OtherProperty { get; public set; }
ولكن يدعي هذا الخط كسر على الفور الدماغ:
internal String OtherProperty { get; protected set; }
لاحظت أنه لن تكون هناك شكاوى حول هذا الخط:
internal String OtherProperty { get; private set; }
إذا كنت لا تفكر كثيرًا ، فإن التسلسل الهرمي التالي مدمج في رأسك:
public > internal > protected > private
ويبدو أن هذا التسلسل الهرمي يعمل. باستثناء مكان واحد. حيث الداخلية> المحمية. لفهم جوهر مطالبات المترجم ، دعنا نتذكر القيود التي تفرضها الداخلية والمحمية. داخلي - فقط داخل التجمع. المحمية - الورثة فقط. لاحظ أي ورثة. وإذا تم تصنيف الفئة B على أنها عامة ، فيمكنك في مجموعة أخرى تحديد أحفادها. وبعد ذلك يحصل الموصل المحدد حقًا على حق الوصول إلى المكان الذي لا تتوفر فيه الخاصية بالكامل. نظرًا لأن المترجم C # بجنون العظمة ، فإنه لا يمكن حتى السماح بهذا الاحتمال.
شكرًا له على هذا ، لكننا نحتاج إلى منح الورثة حق الوصول إلى الموصل. وعلى وجه التحديد لمثل هذه الحالات ، هناك معدل وصول داخلي محمي.
هذه المساعدة ليست مسيئة للغاية- داخلي محمي - هو معدّل وصول يشير إلى أن العلامة المميزة متاحة داخل المجموعة أو لورثة الفئة التي يوجد بها العلامة المميزة.
لذلك إذا أردنا أن يسمح لنا المترجم باستخدام هذه الخاصية وتعيينها في الورثة ، نحتاج إلى القيام بذلك:
using System; namespace Pipe { internal class B { protected internal String OtherProperty { get; protected set; } } }
ويبدو التسلسل الهرمي الصحيح لمعدلات الوصول إلى شيء مثل هذا:
public > protected internal > internal/protected > private
واجهات
لذلك ، فإن الوضع: A ، I ، B كانوا جالسين على الأنبوب.
namespace Pipe { internal interface I { void SomeMethod(); } internal class A : I { internal void SomeMethod() {
جلسنا بالضبط ولم نتدخل خارج الجمعية. ولكن تم رفضها من قبل المترجم. هنا جوهر المطالبات واضح من رسالة الخطأ. يجب أن يكون تطبيق الواجهة مفتوحًا. حتى لو كانت الواجهة نفسها مغلقة. سيكون من المنطقي ربط الوصول إلى تنفيذ الواجهة بتوفرها ، ولكن ما هو غير ذلك ، ليس كذلك. يجب أن يكون تنفيذ الواجهة عامًا.
ولدينا طريقتان للخروج. أولاً: من خلال صرير الأسنان وصريرها ، علق معدّل وصول عام على تنفيذ الواجهة. الثاني: التنفيذ الصريح للواجهة. يبدو مثل هذا:
namespace Pipe { internal interface I { void SomeMethod(); } internal class A : I { public void SomeMethod() { } } internal class B : I { void I.SomeMethod() { } } }
يرجى ملاحظة أنه في الحالة الثانية لا يوجد معدل وصول. لمن في هذه الحالة يكون تنفيذ الطريقة متاحًا؟ دعنا نقول فقط لا أحد. من الأسهل الظهور بمثال:
B b = new B();
التنفيذ الصريح للواجهة يعني أنه إلى أن نلقي المتغير صراحةً بالكتابة I ، لا توجد طرق لتطبيق هذه الواجهة. يمكن الكتابة (ب كما أنا) .SomeMethod () في كل مرة يكون التحميل الزائد. مثل ((I) ب) .SomeMethod (). ووجدت طريقتين للالتفاف حول هذا. فكرت في شخص واحد ، وبصراحة غوغل الثانية.
الطريقة الأولى هي المصنع:
internal class Factory { internal I Create() { return new B(); } }
حسنًا ، أو أي نمط آخر يسمح لك بإخفاء هذا الفارق الدقيق.
الطريقة الثانية - طرق التمديد:
internal static class IExtensions { internal static void SomeMethod(this I i) { i.SomeMethod(); } }
والمثير للدهشة أنه يعمل. هذه الخطوط تتوقف عن رمي خطأ:
B b = new B(); b.SomeMethod();
بعد كل شيء ، تأتي المكالمة ، كما يخبرنا IntelliSense في Visual Studio ، ليس عن طرق لتطبيق الواجهة بشكل صريح ، ولكن أساليب التمديد. ولا أحد يحرم اللجوء إليهم. ويمكن استدعاء أساليب تمديد الواجهة على جميع تطبيقاتها.
ولكن لا يزال هناك تحذير واحد. داخل الفصل نفسه ، تحتاج إلى الوصول إلى هذه الطريقة من خلال هذه الكلمة الأساسية ، وإلا فلن يفهم المترجم أننا نريد الإشارة إلى طريقة الامتداد:
internal class B : I { internal void OtherMethod() {
وهكذا ، وهكذا ، لدينا أو الجمهور ، حيث لا ينبغي أن يكون ، ولكن يبدو أن هناك أي ضرر ، أو رمز إضافي صغير لكل واجهة داخلية. اختيار الشر أقل ترضيك.
انعكاس
لقد صدمت هذا الأمر بشكل مؤلم عندما حاولت إيجاد مُنشئ من خلال التفكير ، والذي ، بالطبع ، تم وضع علامة داخلية في الفصل الداخلي. واتضح أن التفكير لن يعطي أي شيء لن يكون علنياً. وهذا ، من حيث المبدأ ، منطقي.
أولاً ، التفكير ، إذا كنت أتذكر بشكل صحيح ما كتبه الأشخاص الأذكياء في الكتب الذكية ، فهو يتعلق بالعثور على معلومات في بيانات تعريف التجميع. والتي ، من الناحية النظرية ، لا ينبغي أن نعطيه الكثير (اعتقدت ذلك ، على الأقل). ثانياً ، الاستخدام الرئيسي للتأمل هو جعل برنامجك قابلاً للتوسعة. أنت تقدم نوعًا ما من الواجهة للأجانب (ربما حتى في شكل واجهات ، fiy-ha!). ويقومون بتنفيذه وتوفير الإضافات والتعديلات والإضافات في شكل مجموعة يتم تحميلها أثناء التنقل ، والتي يحصل عليها الانعكاس. وفي حد ذاته ، ستكون API الخاصة بك عامة. وهذا يعني أن النظر إلى الداخل من خلال التفكير ليس تقنيًا ولا معنى له من الناحية العملية.
التحديث. هنا ، في التعليقات ، اتضح أن الانعكاس يسمح ، إذا طلبت ذلك صراحة ، أن يعكس كل شيء. سواء كان ذلك الداخلية ، حتى الخاصة. إذا كنت لا تكتب نوعًا من أداة تحليل الشفرة ، فحاول عدم القيام بذلك ، من فضلك. لا يزال النص أدناه ذا صلة بالحالات التي نبحث فيها عن أنواع الأعضاء المفتوحة. وبشكل عام ، لا تمر تعليقات ، هناك الكثير من الأشياء المثيرة للاهتمام.
يمكن الانتهاء من ذلك بالتأمل ، ولكن دعنا نعود إلى المثال السابق ، حيث كان A ، I ، B يجلسون على أنبوب:
namespace Pipe { internal interface I { void SomeMethod(); } internal static class IExtensions { internal static void SomeMethod(this I i) { i.SomeMethod(); } } internal class A : I { public void SomeMethod() { } internal void OtherMethod() { } } internal class B : I { internal void OtherMethod() { } void I.SomeMethod() { } } }
قرر مؤلف الفئة (أ) أنه لن يحدث أي شيء سيء إذا تم وضع علامة على طريقة الفئة الداخلية على أنها عامة ، بحيث لم يتحول المترجم ، ولم تكن هناك حاجة لوضع المزيد من التعليمات البرمجية فيها. يتم تمييز الواجهة على أنها داخلية ، والطبقة التي تنفذها على أنها داخلية ، ويبدو من الخارج أنه لا توجد وسيلة للوصول إلى الطريقة التي تم وضع علامة عليها باعتبارها عامة.
ثم يفتح الباب وينعكس بهدوء في:
using Pipe; using System; using System.Reflection; namespace EncapsulationTest { public class Program { public static void Main(string[] args) { FindThroughReflection(typeof(I), "SomeMethod"); FindThroughReflection(typeof(IExtensions), "SomeMethod"); FindThroughReflection(typeof(A), "SomeMethod"); FindThroughReflection(typeof(A), "OtherMethod"); FindThroughReflection(typeof(B), "SomeMethod"); FindThroughReflection(typeof(B), "OtherMethod"); Console.ReadLine(); } private static void FindThroughReflection(Type type, String methodName) { MethodInfo methodInfo = type.GetMethod(methodName); if (methodInfo != null) Console.WriteLine($"In type {type.Name} we found {methodInfo}"); else Console.WriteLine($"NULL! Can't find method {methodName} in type {type.Name}"); } } }
ادرس هذا الكود ، وادخله في الاستوديو ، إذا كنت ترغب في ذلك. هنا نحاول استخدام الانعكاس للعثور على جميع الأساليب من جميع أنواع الأنابيب لدينا (مساحة الاسم الأنابيب). وهنا النتائج التي يعطينا:
في النوع الأول ، وجدنا Void SomeMethod ()
NULL! لا يمكن العثور على طريقة SomeMethod في نوع IExtensions
في النوع A ، وجدنا Void SomeMethod ()
NULL! لا يمكن العثور على طريقة OtherMethod في النوع A
NULL! لا يمكن العثور على طريقة SomeMethod في النوع B
NULL! لا يمكن العثور على طريقة OtherMethod في النوع B
يجب أن أقول على الفور أنه باستخدام كائن من النوع MethodInfo ، يمكن استدعاء الأسلوب الموجود. وهذا هو ، إذا وجد الانعكاس شيئًا ما ، فإن التغليف يمكن أن ينتهك من الناحية النظرية. وقد وجدنا شيئا. أولاً ، باطل الجمهور نفسه SomeMethod () من الفئة A. كان متوقعًا ، ماذا أقول. هذا التساهل قد لا تزال لها عواقب. ثانياً ، باطل SomeMethod () من الواجهة I. هذا بالفعل أكثر إثارة للاهتمام. بغض النظر عن كيفية حبس أنفسنا ، فإن الطرق المجردة الموضوعة في الواجهة (أو ما يضعه CLR بالفعل هناك) مفتوحة فعليًا. ومن هنا جاءت النتيجة الواردة في فقرة منفصلة:
انظر بعناية إلى من وإلى أي نوع من نوع System.Type الذي تتخلى عنه.
ولكن هناك فارق واحد بسيط مع هاتين الطريقتين ، والتي أود أن أعتبرها. يمكن العثور على أساليب واجهة الداخلية والأساليب العامة للفئات الداخلية باستخدام التفكير. كشخص معقول ، سأستنتج أنهم يقعون في البيانات الوصفية. كشخص متمرس ، سوف أتحقق من هذا الاستنتاج. وفي هذا ILDasm سوف يساعدنا.
نلقي نظرة خاطفة على حفرة الأرنب في البيانات الوصفية للأنابيب لديناتم تجميع الجمعية في الإصدار
TypeDef #2 (02000003)
-------------------------------------------------------
TypDefName: Pipe.I (02000003)
Flags : [NotPublic] [AutoLayout] [Interface] [Abstract] [AnsiClass] (000000a0)
Extends : 01000000 [TypeRef]
Method #1 (06000004)
-------------------------------------------------------
MethodName: SomeMethod (06000004)
Flags : [Public] [Virtual] [HideBySig] [NewSlot] [Abstract] (000005c6)
RVA : 0x00000000
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
TypeDef #3 (02000004)
-------------------------------------------------------
TypDefName: Pipe.IExtensions (02000004)
Flags : [NotPublic] [AutoLayout] [Class] [Abstract] [Sealed] [AnsiClass] [BeforeFieldInit] (00100180)
Extends : 01000011 [TypeRef] System.Object
Method #1 (06000005)
-------------------------------------------------------
MethodName: SomeMethod (06000005)
Flags : [Assem] [Static] [HideBySig] [ReuseSlot] (00000093)
RVA : 0x00002134
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
ReturnType: Void
1 Arguments
Argument #1: Class Pipe.I
1 Parameters
(1) ParamToken : (08000004) Name : i flags: [none] (00000000)
CustomAttribute #1 (0c000011)
-------------------------------------------------------
CustomAttribute Type: 0a000001
CustomAttributeName: System.Runtime.CompilerServices.ExtensionAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()
CustomAttribute #1 (0c000010)
-------------------------------------------------------
CustomAttribute Type: 0a000001
CustomAttributeName: System.Runtime.CompilerServices.ExtensionAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()
TypeDef #4 (02000005)
-------------------------------------------------------
TypDefName: Pipe.A (02000005)
Flags : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100000)
Extends : 01000011 [TypeRef] System.Object
Method #1 (06000006)
-------------------------------------------------------
MethodName: SomeMethod (06000006)
Flags : [Public] [Final] [Virtual] [HideBySig] [NewSlot] (000001e6)
RVA : 0x0000213c
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Method #2 (06000007)
-------------------------------------------------------
MethodName: OtherMethod (06000007)
Flags : [Assem] [HideBySig] [ReuseSlot] (00000083)
RVA : 0x0000213e
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Method #3 (06000008)
-------------------------------------------------------
MethodName: .ctor (06000008)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886)
RVA : 0x00002140
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
InterfaceImpl #1 (09000001)
-------------------------------------------------------
Class : Pipe.A
Token : 02000003 [TypeDef] Pipe.I
TypeDef #5 (02000006)
-------------------------------------------------------
TypDefName: Pipe.B (02000006)
Flags : [NotPublic] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100000)
Extends : 01000011 [TypeRef] System.Object
Method #1 (06000009)
-------------------------------------------------------
MethodName: OtherMethod (06000009)
Flags : [Assem] [HideBySig] [ReuseSlot] (00000083)
RVA : 0x00002148
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Method #2 (0600000a)
-------------------------------------------------------
MethodName: Pipe.I.SomeMethod (0600000A)
Flags : [Private] [Final] [Virtual] [HideBySig] [NewSlot] (000001e1)
RVA : 0x0000214a
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Method #3 (0600000b)
-------------------------------------------------------
MethodName: .ctor (0600000B)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886)
RVA : 0x0000214c
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
MethodImpl #1 (00000001)
-------------------------------------------------------
Method Body Token : 0x0600000a
Method Declaration Token : 0x06000004
InterfaceImpl #1 (09000002)
-------------------------------------------------------
Class : Pipe.B
Token : 02000003 [TypeDef] Pipe.I
تظهر نظرة سريعة أن كل شيء يدخل في البيانات الوصفية ، بغض النظر عن كيفية تمييزها. لا يزال التأمل يخفي عنا بعناية أنه ليس من المفترض أن يرى الغرباء. لذلك قد يكون أن الأسطر الخمسة الإضافية من الشفرة لكل طريقة من الواجهة الداخلية ليست شرًا كبيرًا. ومع ذلك ، فإن الاستنتاج الرئيسي لا يزال هو نفسه:
انظر بعناية إلى من وإلى أي نوع من نوع System.Type الذي تتخلى عنه.
ولكن هذا هو ، بالطبع ، المستوى التالي ، بعد انضمام الكلمة الداخلية في جميع الأماكن التي لا توجد فيها حاجة للجمهور.
PS
هل تعلم أن أروع شيء حول استخدام الكلمة الأساسية الداخلية هو في كل مكان داخل التجمع؟ عندما ينمو ، عليك تقسيمه إلى قسمين أو أكثر. وفي هذه العملية عليك أن تأخذ استراحة لجعل بعض الأنواع مفتوحة. وعليك أن تفكر بالضبط في الأنواع التي تستحق أن تكون مفتوحة. على الأقل لفترة وجيزة.
هذا يعني ما يلي: هذه الممارسة لكتابة التعليمات البرمجية ستجعلك تفكر مرة أخرى حول شكل الحدود المعمارية بين التجمعات الحديثة الولادة. ماذا يمكن أن يكون أكثر جمالا؟
PPS
بدءًا من الإصدار C # 7.2 ، ظهر معدّل وصول جديد ومحمي خاص. وما زلت ليس لدي أي فكرة عما هو وماذا يؤكل. منذ لم تصادف في الممارسة. لكنني سأكون سعيداً لمعرفة ذلك في التعليقات. لكن لا تقم بلصق النسخ من الوثائق ، ولكن الحالات الحقيقية عندما تكون هناك حاجة لمعدل الوصول هذا.