מה זה?

בשיעור הקודם השדות היו public — כל קוד חיצוני יכול לשנות אותם בלי שום בקרה. מה מונע bankAccount.Balance = -500;? כלום — וזו בעיה אמיתית: יתרת חשבון בנק שלילית לא אמורה להיות אפשרית. Properties פותרות את זה: נותנות שליטה מלאה על מה קורה כשקוראים או משנים ערך, תוך שמירה על התחביר הנוח של גישה ישירה (account.Balance, לא account.GetBalance()).

מילות מפתח שחשוב לזכור

• Property — “שדה חכם”: נראה כמו שדה רגיל בגישה (obj.Name), אבל בפועל מריץ קוד (get/set) מאחורי הקלעים

• get — הקוד שרץ כשקוראים לערך של ה-Property

• set — הקוד שרץ כשמנסים לשנות את הערך של ה-Property; יכול לדחות ערכים לא חוקיים

• Access Modifier — קובע מי יכול לגשת לחבר מסוים: public (כולם), private (רק בתוך המחלקה עצמה), protected (המחלקה ויורשיה)

• Auto-Property — Property מקוצרת בלי לוגיקה מותאמת אישית, כשה-get/set רק “מעבירים” ערך כרגיל

class BankAccount
{
    private double _balance;   // private — accessible only within the class itself

    public double Balance
    {
        get { return _balance; }
        set
        {
            if (value < 0)
                throw new ArgumentException("Balance cannot be negative");
            _balance = value;
        }
    }
}

BankAccount account = new BankAccount();
account.Balance = 500;    // valid — goes through the set
// account.Balance = -100;  // throws ArgumentException
// Auto-Property — when there's no special logic, no need for a separate manual field
class Student
{
    public string Name { get; set; }           // freely readable and writable
    public int Age { get; private set; }       // readable from outside, but only writable inside the class

    public Student(string name, int age)
    {
        Name = name;
        Age = age;   // valid — this.Age = age inside the class itself
    }
}

הסבר עיקרי

למה private set שימושי — { get; private set; } אומר: “כל אחד יכול לקרוא את Age, אבל רק קוד בתוך Student עצמה יכול לשנות אותו” — למשל רק בקונסטרוקטור, או במתודה ייעודית כמו Birthday(). זה מונע מקוד חיצוני “לשנות גיל תלמיד” ישירות באופן שרירותי, בלי לחסום קריאה חופשית שלו.

Property מול שדה רגיל — מבחוץ, account.Balance = 500 נראה בדיוק אותו דבר בין אם Balance הוא שדה או Property. ההבדל הוא ש-Property מריצה קוד משלה (set) בכל פעם — כאן: בדיקת תקינות לפני שמירה. זו הסיבה ש-C# ממליצה על Properties (לא שדות public) לכל דבר שנגיש מחוץ למחלקה.

Access Modifiers — private הוא ברירת המחדל הבטוחה: פרט מימוש פנימי שאין סיבה שקוד חיצוני יגע בו ישירות (כמו _balance בדוגמה). public נחשף החוצה במפורש כשזה חלק מה-API של המחלקה. protected (יילמד בשיעור הירושה) נמצא באמצע — נגיש למחלקה ולכל מה שיורש ממנה.

יתרונות

Properties מאפשרות ולידציה ולוגיקה בכל גישה/שינוי, בלי לשנות את התחביר בצד הקורא; private set מגן על שדות מפני שינוי חיצוני בלתי-רצוי בלי לחסום קריאה; Auto-Properties נותנות תחביר קצר כשאין צורך בלוגיקה מיוחדת.

חסרונות

Property עם לוגיקה כבדה מדי ב-get (למשל קריאת רשת) עלולה להטעות קוד קורא שמצפה לגישה מהירה כמו שדה רגיל; יותר מדי Properties עם set פתוח לכולם מאבד את היתרון של הגנה על מצב פנימי.

נקודות חשובות

• Property = “שדה חכם” עם get/set — נראה כמו שדה, מריץ קוד מאחורי הקלעים

• private = נגיש רק בתוך המחלקה; public = נגיש מכל מקום; protected = המחלקה ויורשיה

• { get; private set; } מאפשר קריאה חופשית אך שינוי רק מבפנים

• Auto-Property ({ get; set; } בלי גוף) היא קיצור כשאין לוגיקה מיוחדת נדרשת

טעויות נפוצות

• להשאיר שדות public בלי Properties — מאבד כל אפשרות לולידציה או בקרה עתידית

• להשתמש ב-set פתוח לכולם על שדה שאמור להשתנות רק בתנאים מבוקרים (כמו יתרת חשבון)

• לשכוח שקריאה ל-Property (get) יכולה בעצמה להריץ לוגיקה — לא תמיד “בחינם” כמו שדה רגיל

סיכום

Properties נותנות “שדה חכם” — תחביר גישה זהה לשדה רגיל (obj.Name), אבל עם אפשרות להריץ לוגיקה (ולידציה, חישוב) בכל get/set. Access Modifiers (private/public/protected) קובעים מי יכול לגשת לכל חבר במחלקה — ברירת המחדל הבטוחה היא private, וחושפים public רק את מה שבאמת צריך להיות חלק מה-API החיצוני.

דוקומנטציה רשמית

Microsoft Learn — Properties

Microsoft Learn — Access modifiers


תרגילים

תרגיל 1 — Property עם ולידציה

המשימה: הוסיפו למחלקה Student (משיעור קודם) Property בשם Age עם set שדוחה גילאים שליליים (זורק ArgumentException).

בדיקה: student.Age = 20 עובד; student.Age = -5 זורק חריגה.

תרגיל 2 — private set

המשימה: הפכו את BankAccount.Balance ל-{ get; private set; }, והוסיפו מתודות Deposit(double amount) ו-Withdraw(double amount) שהן היחידות שיכולות לשנות את היתרה.

בדיקה: ניסיון לכתוב account.Balance = 1000; מחוץ למחלקה גורם לשגיאת קומפילציה; account.Deposit(1000) עובד ומעדכן את היתרה כראוי.


פרויקט מסכם

המשימה: בנו מחלקה Product עם ניהול מלאי מוגן.

דרישות:

  1. Properties: Name ({ get; set; } חופשי), Price (get חופשי, set דוחה ערכים שליליים), Stock (get חופשי, private set)
  2. מתודות AddStock(int amount) ו-RemoveStock(int amount) — RemoveStock דוחה הסרה של יותר מהמלאי הקיים (זורקת InvalidOperationException)
  3. הדגימו: יצירת מוצר, הוספת מלאי, הסרת מלאי תקינה, וניסיון הסרה שחורג מהמלאי (תפוס עם try/catch)

בדיקה: ניסיון product.Price = -10 זורק חריגה; RemoveStock שחורג מהמלאי הקיים זורק InvalidOperationException ותפוס בהצלחה בקוד הקורא.

מה בפרק הבא

בפרק הבא — ירושה: איך מחלקה אחת “יורשת” שדות והתנהגות ממחלקה אחרת, במקום לשכפל קוד דומה בין כמה מחלקות קרובות.