מה זה?

שינוי פרמטר בתוך פונקציה — האם זה משפיע על המשתנה המקורי בחוץ, או לא? התשובה תלויה בטיפוס: int (Value Type) שנשלח לפונקציה מקבל עותק — שינוי בפנים לא משפיע בחוץ. אבל אובייקט (Reference Type) שנשלח לפונקציה משתף את אותה הפניה — שינוי בתוך האובייקט (לא החלפתו לגמרי) כן ישפיע בחוץ. זה ההבדל שכבר נרמז בשיעור על טיפוסים פרימיטיביים מול Object — עכשיו רואים אותו בפעולה.

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

• Value Type — טיפוס שמועבר כעותק של הערך (int, double, bool, char, struct)

• Reference Type — טיפוס שמועבר כהפניה (reference) לאותו אובייקט בזיכרון (string, מחלקות, List<T>)

• out — מילת מפתח שגורמת לפרמטר להיות “יציאה” — הפונקציה חייבת לתת לו ערך, וההשפעה נראית בחוץ

// Value Type — copy, changing it inside does not affect the outside
static void TryDouble(int number)
{
    number = number * 2;   // affects only the local copy
}

int x = 5;
TryDouble(x);
Console.WriteLine(x);   // still 5 — the function only changed a copy
// Reference Type — changing inside the object does affect the outside
static void AddItem(List<string> list)
{
    list.Add("new");   // changes the shared object itself
}

List<string> items = new List<string> { "a", "b" };
AddItem(items);
Console.WriteLine(items.Count);   // 3 — the shared object changed
// out — when you want a function to "return" more than one value
static bool TryDivide(int a, int b, out int result)
{
    if (b == 0)
    {
        result = 0;
        return false;
    }
    result = a / b;
    return true;
}

if (TryDivide(10, 2, out int answer))
{
    Console.WriteLine($"The result: {answer}");   // 5
}

הסבר עיקרי

למה int לא מושפע אבל List<string> כן — כשמעבירים int x לפונקציה, C# מעתיקה את הערך (5) לפרמטר המקומי — זה משתנה נפרד לגמרי מ-x המקורי. כשמעבירים List<string> items, C# מעתיקה רק את ההפניה (הכתובת) לאותו אובייקט ברשימה — הפרמטר המקומי מצביע לאותו אובייקט ממש, כך ש-list.Add(...) משנה את האובייקט שגם items בחוץ מצביע אליו.

ההבדל בין “לשנות את האובייקט” ל"להחליף את ההפניה" — חשוב להבחין: list.Add("חדש") משנה את תוכן האובייקט הקיים — זה נראה בחוץ. אבל list = new List<string>() (בתוך הפונקציה) רק מחליף למה הפרמטר המקומי מצביע — לא משפיע על items בחוץ, כי זו רק החלפת ההפניה המקומית, לא שינוי האובייקט המשותף.

מתי out נחוץ — פונקציה רגילה יכולה להחזיר רק ערך אחד עם return. out מאפשר “להחזיר” גם דרך פרמטר — שימושי כש-TryParse/TryDivide צריכים גם תוצאת בוליאן (הצליח/לא) וגם את הערך המחושב עצמו, בלי לבנות אובייקט מיוחד רק בשביל זה.

יתרונות

Value Types מגנים מפני שינוי בלתי-צפוי — פונקציה לא יכולה “לזיין” משתנה int שנשלח אליה; Reference Types חוסכים העתקה יקרה של מבני נתונים גדולים בכל קריאת פונקציה; out מאפשר “החזרת” כמה ערכים בלי ליצור מחלקה מיוחדת רק לשם כך.

חסרונות

ההבחנה Value/Reference יכולה להפתיע מפתחים מתחילים — שינוי בתוך פונקציה עובד “אחרת” לגבי אובייקטים לעומת פרימיטיבים; out שובר קריאות אם משתמשים בו יותר מדי — פונקציה עם כמה פרמטרי out קשה לקרוא בנקודת הקריאה.

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

• Value Type מועבר כעותק; Reference Type מועבר כהפניה לאותו אובייקט

• שינוי תוכן אובייקט (Reference) בתוך פונקציה נראה בחוץ; החלפת ההפניה עצמה — לא

• out מאפשר לפונקציה “להחזיר” ערך נוסף דרך פרמטר, בנוסף ל-return

• TryParse/TryDivide הם השימוש הכי נפוץ ב-out — הצלחה בוליאנית + תוצאה

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

• לצפות ששינוי int/double בתוך פונקציה ישפיע על המשתנה המקורי בחוץ

• לבלבל בין “לשנות אובייקט” (list.Add, נראה בחוץ) ל"להחליף הפניה" (list = new List<>(), לא נראה בחוץ)

• לשכוח לתת ערך לפרמטר out בכל נתיב קוד אפשרי — שגיאת קומפילציה

סיכום

Value Types (int, double, bool וכו’) מועברים לפונקציות כעותק — שינוי בפנים לא משפיע בחוץ. Reference Types (string, מחלקות, List<T>) מועברים כהפניה — שינוי בתוך האובייקט המשותף כן נראה בחוץ, אך החלפת ההפניה עצמה לא. out מאפשר לפונקציה “להחזיר” ערך נוסף דרך פרמטר, מעבר ל-return הרגיל.

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

Microsoft Learn — Value types and reference types

Microsoft Learn — out parameter modifier


תרגילים

תרגיל 1 — Value Type לא משתנה

המשימה: כתבו פונקציה void AddTen(int number) שמנסה להוסיף 10 לפרמטר, וקראו לה על משתנה int. הדפיסו את המשתנה לפני ואחרי הקריאה.

בדיקה: הערך המודפס לפני ואחרי הקריאה זהה — הפונקציה לא שינתה את המשתנה המקורי.

תרגיל 2 — Reference Type כן משתנה

המשימה: כתבו פונקציה void AddItem(List<int> numbers) שמוסיפה את המספר 99 לרשימה שהתקבלה, וקראו לה על List<int> קיימת. הדפיסו את מספר האיברים לפני ואחרי.

בדיקה: מספר האיברים גדל ב-1 אחרי הקריאה — הרשימה המקורית השתנתה.


פרויקט מסכם

המשימה: כתבו פונקציה bool TryGetDiscount(double price, string couponCode, out double finalPrice) שמחשבת מחיר אחרי הנחה.

דרישות:

  1. אם couponCode שווה ל-"SAVE10" — finalPrice = price * 0.9, הפונקציה מחזירה true
  2. עבור כל קוד קופון אחר — finalPrice = price (ללא שינוי), הפונקציה מחזירה false
  3. ב-Main, קראו לפונקציה עם קוד קופון תקין ולא תקין, והדפיסו הודעה מתאימה לכל מקרה בעזרת ה-bool וה-out שהתקבלו

בדיקה: קופון "SAVE10" על מחיר 100 מחזיר true ו-finalPrice = 90; קופון לא קיים מחזיר false ו-finalPrice שווה למחיר המקורי.

מה בפרק הבא

בפרק הבא — Nullable Types: איך מייצגים “אין ערך” גם עבור טיפוס פרימיטיבי כמו int, שבברירת מחדל תמיד חייב להכיל מספר כלשהו.