هل الكلمة الأساسية الداخلية C # هي رائحة كود؟

في هذا المنشور ، سأوضح لماذا أعتقد أن الكلمة الأساسية الداخلية ، عند وضعها على أعضاء الفصل ، هي رائحة كود وتقترح بدائل أفضل.

ما هي الكلمة الأساسية الداخلية؟

في C # ، يمكن استخدام الكلمة الأساسية الداخلية في الفصل أو أعضائه. إنه أحد معدّلات الوصول C #. يمكن الوصول إلى الأنواع الداخلية أو الأعضاء فقط داخل الملفات الموجودة في نفس التجميع . (C # وثائق الكلمات الرئيسية الداخلية).

لماذا نحتاج الكلمة الأساسية الداخلية؟

هناك استخدام شائع للوصول الداخلي في التطوير المستند إلى المكونات لأنه يمكّن مجموعة من المكونات من التعاون بطريقة خاصة دون التعرض لبقية كود التطبيق . على سبيل المثال ، يمكن لإطار عمل لبناء واجهات مستخدم رسومية أن يوفر فئات Control و Form التي تتعاون باستخدام أعضاء لديهم وصول داخلي. نظرًا لأن هؤلاء الأعضاء داخليون ، فلن يتعرضوا للتعليمات البرمجية التي تستخدم إطار العمل. " (C # وثائق الكلمات الرئيسية الداخلية)

هذه هي حالات الاستخدام التي رأيتها لاستخدام الكلمة الأساسية الداخلية على أحد أعضاء الفصل:

  • قم باستدعاء الوظيفة الخاصة للفصل داخل نفس التجمع.
  • من أجل اختبار وظيفة خاصة ، يمكنك تمييزها على أنها داخلية وعرض ملف dll لاختبار DLL عبر InternalsVisibleTo.

يمكن النظر إلى كلتا الحالتين على أنهما رائحة رمز ، حيث يجب أن تكون هذه الوظيفة الخاصة عامة.

دعونا نرى بعض الأمثلة

اليك مثال بسيط. تريد وظيفة في فئة ما الوصول إلى وظيفة خاصة لفئة أخرى.

class A{ public void func1(){ func2(); } private void func2(){} } class B{ public void func(A a){ a.func2(); //Compilation error 'A.func2()' is inaccessible due to its protection level } }

الحل بسيط - فقط ضع علامة A :: func2 على أنها عامة.

لنلقِ نظرة على مثال أكثر تعقيدًا:

public class A{ public void func1(){} private void func2(B b){} } internal class B{ public void func3(A a){ a.func2(this); //Compilation error 'A.func2(B)' is inaccessible due to its protection level } }

ما هي المشكلة؟ ما عليك سوى وضع علامة على func2 على أنها عامة كما فعلنا من قبل.

public class A{ public void func1(){ ... } public void func2(B b){ ...} // Compilation error: Inconsistent accessibility: parameter type 'B' is less accessible than method 'A.func2(B)' } internal class B{ public void func3(A a){ a.func2(this); } }

لكن لا نستطيع؟ B هي فئة داخلية لذا لا يمكن أن تكون جزءًا من توقيع وظيفة عامة لطبقة عامة.

هذه هي الحلول التي وجدتها مرتبة حسب السهولة:

  1. قم بتمييز الوظيفة بالكلمة الأساسية الداخلية
public class A{ public void func1(){ } internal void func2(B b){} } internal class B{ public void func3(A a){ a.func2(this); } }

2. إنشاء واجهة داخلية

internal interface IA2{ void func2(B b); } public class A:IA2{ public void func1(){ var b = new B(); b.func3(this); } void IA2.func2(B b){} //implement IA2 explicitly because func2 can't be public } internal class B{ public void func3(A a){ ((IA2)a).func2(this); //use interface instead of class to access func2 } }

3. استخراج A.func2 إلى فئة داخلية أخرى واستخدامها بدلاً من A.func2.

internal class C{ public void func2(B b){ //extract A:func2 to here } } public class A{ public void func1(){} private void func2(B b){ new C().func2(b); } } internal class B{ public void func3(){ //a is no longer needed new C().func2(this); //use internal class instead of private function } }

4. فصل الوظيفة عن الطبقات الداخلية وجعلها عامة. هذا يعتمد إلى حد كبير على ما تفعله الوظيفة بمدخلاتها. يمكن أن يكون فصل الطبقات الداخلية أمرًا سهلاً للغاية وصعبًا جدًا وحتى مستحيلًا (دون التأثير على التصميم).

لكن ليس لدينا فصول عامة نستخدم واجهات ...

لنلقِ نظرة على بعض الأمثلة الواقعية:

public interface IA{ void func1(); } internal class A : IA { public void func1(){} private void func2(B b){} } internal class B{ public void func3(IA a){ a.func2(this); //Compilation error IA' does not contain a definition for 'func2' and no extension method 'func2' accepting a first argument of type 'IA' could be found } }

دعونا نرى كيف يتم تكييف الحلول السابقة مع هذا المثال:

  1. وضع علامة على وظيفة داخلية. هذا يعني أنك ستحتاج إلى الإرسال إلى الفئة لاستدعاء الوظيفة ، لذا لن يعمل هذا إلا إذا كانت الفئة A هي الوحيدة التي تنفذ الواجهة ، مما يعني أن IA لا يتم الاستهزاء بها في الاختبارات وليس هناك فئة إنتاج أخرى تنفذ IA .
public interface IA{ void func1(); } internal class A : IA { public void func1(){} internal void func2(B b){} } internal class B{ public void func3(IA a){ ((A)a).func2(this); //cast to A in order to accses func2 } }

2. إنشاء واجهة داخلية تعمل على توسيع الواجهة العامة.

internal interface IExtendedA : IA{ void func2(B b); } public interface IA{ void func1(); } internal class A : IExtendedA { public void func1(){} public void func2(B b){} } internal class B{ public void func3(IExtendedA a){ a.func2(this); } }

3. استخراج A.func2 إلى فئة داخلية أخرى واستخدامها بدلاً من A.func2.

4. افصل الوظيفة عن الفئات الداخلية وأضفها إلى الواجهة العامة.

يمكننا أن نرى أن الكلمة الأساسية الداخلية هي الحل الأسهل ، ولكن هناك حلول أخرى تستخدم اللبنات الأساسية التقليدية لـ OOP: الفئات والواجهات . يمكننا أن نرى أن الحل الثاني - إضافة واجهة داخلية ليس أصعب بكثير من تمييز الوظيفة بالكلمة الأساسية الداخلية.

لماذا لا تستخدم الكلمة الأساسية الداخلية؟

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

  • انقل الفئة العامة A إلى DLL أخرى (لأن الكلمة الأساسية الداخلية لن تنطبق على نفس dll)
  • قم بإنشاء فئة إنتاج أخرى تنفذ IA
  • IA وهمية في الاختبارات

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

((MyClass)a).internalFunction

ولكن إذا احتاج الآخرون إلى استدعاء هذه الوظيفة أيضًا ، فسيتم نسخ هذا السطر داخل ملف DLL.

استنتاجي

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

قارن بـ C ++

الكلمة الأساسية C ++ "صديق" مشابهة للكلمة الرئيسية الداخلية C #. يسمح للفصل أو الوظيفة بالوصول إلى الأعضاء الخاصين في الفصل. الاختلاف هو أنه يسمح بالوصول إلى فئة أو وظيفة معينة وليس كل الفئات في نفس DLL. في رأيي ، هذا حل أفضل من الكلمة الأساسية الداخلية C #.

قراءة متعمقة

الاستخدامات العملية للكلمة الرئيسية "الداخلية" في C #

لماذا لا تقدم C # الكلمة الأساسية "صديق" على نمط C ++؟