Properties ו-Access Modifiers
מה זה?
בשיעור הקודם השדות היו 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 — 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 עם ניהול מלאי מוגן.
דרישות:
- Properties:
Name({ get; set; }חופשי),Price(getחופשי,setדוחה ערכים שליליים),Stock(getחופשי,private set) - מתודות
AddStock(int amount)ו-RemoveStock(int amount)—RemoveStockדוחה הסרה של יותר מהמלאי הקיים (זורקתInvalidOperationException) - הדגימו: יצירת מוצר, הוספת מלאי, הסרת מלאי תקינה, וניסיון הסרה שחורג מהמלאי (תפוס עם
try/catch)
בדיקה: ניסיון product.Price = -10 זורק חריגה; RemoveStock שחורג מהמלאי הקיים זורק InvalidOperationException ותפוס בהצלחה בקוד הקורא.
מה בפרק הבא
בפרק הבא — ירושה: איך מחלקה אחת “יורשת” שדות והתנהגות ממחלקה אחרת, במקום לשכפל קוד דומה בין כמה מחלקות קרובות.