האם מילת המפתח C # הפנימית היא ריח של קוד?

בפוסט זה אני הולך להראות מדוע אני חושב שמילת המפתח הפנימית, כאשר היא מציגה את חברי הכיתה, היא ריח קוד ומציעה חלופות טובות יותר.

מהי מילת המפתח הפנימית?

ב- C # ניתן להשתמש במילת המפתח הפנימית בכיתה או בחברים בה. זהו אחד ממשני הגישה של C #. ניתן לגשת לסוגים פנימיים או לחברים רק בתוך קבצים באותה מכלול . (תיעוד C # פנימי למילות מפתח).

מדוע אנו זקוקים למילת המפתח הפנימית?

"שימוש נפוץ בגישה פנימית הוא בפיתוח מבוסס רכיבים מכיוון שהוא מאפשר לקבוצת רכיבים לשתף פעולה באופן פרטי מבלי להיחשף לשאר קוד היישום . לדוגמא, מסגרת לבניית ממשקי משתמש גרפיים יכולה לספק מחלקות בקרה וטופס המשתפות פעולה על ידי שימוש בחברים בעלי גישה פנימית. מכיוון שחברים אלה הם פנימיים, הם אינם נחשפים לקוד המשתמש במסגרת. " (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 במבחנים

You may think “But this is just one line of code, I or anyone else can change it easily if needed”. Now you have one line of code that looks like that:

((MyClass)a).internalFunction

but if others will need to call this function too this line will be copy-pasted around inside the DLL.

My Conclusion

I think marking a class member with the internal keyword is a code smell. In the examples I showed above it is the easiest solution, BUT can cause problems in the future. Creating an internal interface is almost as easy and more explicit.

Compare to C++

The C++ “friend” keyword is similar to the C# internal keyword. It allows a class or a function to access private members of a class. The difference is it allows access to specific class or function and notall the classes in the same DLL. In my opinion, this is a better solution than the C# internal keyword.

Further Reading

Practical uses for the "internal" keyword in C#

Why does C# not provide the C++ style 'friend' keyword?