Menu

Access Modifiers ב-C#: public, private, protected ו-internal

ששת מגדירי הגישה של C# (public, private, protected, internal, protected internal, private protected), מה כל אחד מהם מתיר, מה ברירת המחדל כשלא כותבים אף אחד, ואילו שגיאות קומפילציה הם מייצרים.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

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

ששת המגדירים

מגדירהטיפוס המכילטיפוס יורש, אותו assemblyקוד אחר, אותו assemblyטיפוס יורש, assembly אחרקוד אחר, assembly אחר
publicכןכןכןכןכן
protected internalכןכןכןכןלא
protectedכןכןלאכןלא
internalכןכןכןלאלא
private protectedכןכןלאלאלא
privateכןלאלאלאלא

assembly הוא פרויקט מקומפל אחד: קובץ ה-.dll או ה-.exe שהוא מייצר. "אותו assembly" פירושו "אותו פרויקט", ולכן internal מתפקד כ"גלוי לקוד שלי, מוסתר ממשתמשי הספרייה שלי".

private ו-public: כימוס (encapsulation)

private היא ברירת המחדל לאיברים והבחירה הנכונה לרוב השדות. המחלקה חושפת את מה שהקוד הקורא צריך דרך מתודות ומאפיינים public, והמתודות האלה שומרות על עקביות הנתונים של האובייקט.

פלט:

False
True
Priya: 180

מכיוון ש-balance הוא private, הדרכים היחידות לשנות אותו הן 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 קושר כל מחלקה יורשת לשדה הזה לתמיד. בהרבה בסיסי קוד מעדיפים מאפיין או מתודה protected, או משאירים שדות private ונותנים למחלקות היורשות רק את מה שהן צריכות.

internal: גלוי בתוך ה-assembly

איברים וטיפוסים 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 בספריית מחלקות "חסרה" בפרויקט שמפנה אליה.

פרויקטים של בדיקות יחידה הם assemblies נפרדים, ולכן גם הם לא רואים קוד internal. הפתרון המקובל הוא attribute בספרייה:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]

protected internal ו-private protected

שני אלה משלבים את המגדירים הקודמים, בדרכים הפוכות, וקל לבלבל בין השמות שלהם:

  • protected internal הוא protected או internal: כל קוד באותו assembly, ובנוסף מחלקות יורשות ב-assemblies אחרים. זה הרחב מביניהם.
  • private protected (C# 7.2) הוא protected וגם internal: רק מחלקות יורשות שנמצאות גם הן באותו assembly. זה הצר מביניהם, למחלקת בסיס שנקודות ההרחבה שלה צריכות להישאר בתוך הספרייה שלכם.
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)
}

ברירות מחדל כשלא כותבים כלום

איפהברירת מחדלמותר
טיפוס שמוצהר ב-namespaceinternalpublic, internal, file (C# 11)
איבר של classprivateכל השישה
איבר של structprivatepublic, internal, private (אי אפשר לרשת מ-struct)
טיפוס מקונןprivateכל השישה
איבר של Interfacepublicמגדירים מפורשים מותרים מאז C# 8
איבר של enumpublicאף אחד

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

גישה ברמת ה-accessor

מאפיין או אינדקסר יכולים לתת ל-accessor אחד מגדיר צר יותר מזה של המאפיין עצמו:

פלט:

report.pdf: 100%

המגדיר של ה-accessor חייב להיות מגביל יותר מזה של המאפיין, ורק לאחד משני ה-accessors מותר שיהיה מגדיר כזה.

נגישות לא עקבית

איבר public לא יכול לחשוף טיפוס שגלוי פחות ממנו, כי הקוד הקורא יקבל משהו שאסור לו אפילו לנקוב בשמו:

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 (כל קוד באותו assembly), protected internal (אותו assembly, או טיפוסים יורשים בכל מקום) ו-private protected (טיפוסים יורשים באותו assembly, C# 7.2). C# 11 הוסיפה את file לטיפוסים שנראים רק בקובץ מקור אחד.

מהו מגדיר הגישה של ברירת המחדל ב-C#?

איברים של class או struct (שדות, מתודות, מאפיינים, טיפוסים מקוננים) הם private כברירת מחדל. טיפוסים ברמה העליונה (class שמוצהר ישירות ב-namespace) הם internal כברירת מחדל. איברים של interface ושל enum הם public.

מה המשמעות של protected ב-C#?

איבר protected נראה בתוך המחלקה שלו ובתוך כל מחלקה שיורשת ממנה, אבל לא לקוד אחר. מחלקה יורשת יכולה להשתמש בו רק דרך הטיפוס שלה עצמה: בתוך Manager, this.baseSalary עובד, אבל גישה ל-baseSalary דרך אובייקט Employee אחר היא שגיאה CS1540.

מה זה internal ב-C#?

internal הופך טיפוס או איבר לגלוי לכל הקוד באותו assembly (ה-.dll או ה-.exe שהפרויקט מקמפל) ולבלתי נראה מחוצה לו. כך ספרייה שומרת מחלקות עזר מחוץ ל-API הציבורי שלה. [assembly: InternalsVisibleTo("MyLib.Tests")] מאפשר גם לפרויקט בדיקות לראות אותן.

איך מתקנים את השגיאה "is inaccessible due to its protection level"?

זו שגיאה CS0122: הקוד משתמש באיבר שאסור לו לראות, לרוב שדה שנשאר בברירת המחדל private. אם קוד חיצוני צריך לקרוא אותו, חשפו מאפיין או מתודה public; אל תהפכו פשוט את השדה ל-public. אם זה טיפוס מפרויקט אחר, כנראה שהוא internal.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל