פונקציות (מתודות)
מה זה?
השיעור הזה בונה את הרעיון של פונקציה (מתודה) מאפס: קודם נראה קוד בלי פונקציות בכלל, נראה בדיוק למה הוא בעייתי, ורק אז נבנה פונקציה שלב-שלב כפתרון ישיר לבעיה הזאת.
מילות מפתח שחשוב לזכור
• Method — בלוק קוד עם שם, שאפשר לקרוא לו (Call) מכמה מקומות בתוכנית
• Parameter — משתנה שהפונקציה מקבלת כקלט, מוגדר בחתימה שלה
• Return Value — הערך שהפונקציה מחזירה החוצה אחרי הריצה, עם return
• void — טיפוס החזרה שמציין שהפונקציה לא מחזירה שום ערך
• Overloading — כמה מתודות עם אותו שם אך פרמטרים שונים (מספר/טיפוס)
בונים פונקציה שלב אחר שלב
שלב 1: המצב בלי פונקציות — קוד מועתק-מודבק
נניח שצריך לחשב ממוצע ציונים בשלושה מקומות שונים בתוכנית — למשל בהדפסת תעודה, בבדיקת מלגה, ובדוח סיכום. בלי פונקציות, כותבים את אותו חישוב שוב ושוב:
// במקום 1 — הדפסת תעודה
int math = 80, english = 90, science = 70;
double avg1 = (math + english + science) / 3.0;
Console.WriteLine($"ציון תעודה: {avg1}");
// במקום 2 — בדיקת מלגה
int math2 = 65, english2 = 72, science2 = 88;
double avg2 = (math2 + english2 + science2) / 3.0; // אותו חישוב, שוב
if (avg2 >= 70)
Console.WriteLine("זכאי למלגה");
// במקום 3 — דוח סיכום
int math3 = 95, english3 = 60, science3 = 75;
double avg3 = (math3 + english3 + science3) / 3.0; // ושוב
Console.WriteLine($"ממוצע כיתתי לדוגמה: {avg3}");זה עובד — כל שלוש התוצאות נכונות. הבעיה מתגלה רק כשמשהו צריך להשתנות: נניח שמגלים שצריך לעגל את הממוצע לשתי ספרות אחרי הנקודה. עכשיו צריך לזכור למצוא את כל שלושת המקומות שבהם חושב ממוצע, ולתקן בכל אחד בנפרד. שוכחים מקום אחד — הוא ימשיך להתנהג אחרת מהשאר, ואף אחד לא יבחין בזה מיד.
שלב 2: אורזים את החישוב לבלוק אחד עם שם — Method
הפתרון: כותבים את החישוב פעם אחת, בתוך בלוק עם שם, וקוראים לו מכל מקום שצריך.
static double CalculateAverage(int a, int b, int c)
{
return (a + b + c) / 3.0;
}בואו נעצור על השורה הראשונה, מילה-מילה:
static— כרגע, בכל התרגילים שלנו, המתודות שייכות ישירות לתוכנית (לא למופע של מחלקה) —staticאומר “אפשר לקרוא למתודה הזו בלי ליצור אובייקט קודם”, בדיוק כמו ש-Mainעצמה מוגדרתstatic. (ביחידת ה-OOP נראה גם מתודות בליstatic, ששייכות לאובייקט ספציפי.)double— טיפוס ההחזרה: הערך שהמתודה מחזירה בסוף יהיה מסוגdouble(מספר עשרוני).CalculateAverage— שם המתודה, שבו נקרא לה מאוחר יותר.(int a, int b, int c)— שלושה Parameters: משתנים שהמתודה מקבלת כקלט. כל אחד מוגדר עם טיפוס (int) ושם (a,b,c) — בדיוק כמו הגדרת משתנה רגילה, רק שהערך שלהם יגיע מבחוץ, מהקריאה למתודה.{ return (a + b + c) / 3.0; }— גוף המתודה.returnהוא המילה שאומרת “זה הערך שחוזר החוצה” — וברגע שהקוד מגיע אליה, המתודה מפסיקה לרוץ מיד, גם אם יש שורות אחריה.
שלב 3: קוראים למתודה — Call
עכשיו, בכל אחד משלושת המקומות משלב 1, במקום לחשב ידנית:
double avg1 = CalculateAverage(80, 90, 70);
Console.WriteLine($"ציון תעודה: {avg1}");CalculateAverage(80, 90, 70) היא הקריאה (Call) למתודה. שימו לב להבחנה חשובה: a, b, c בהגדרת המתודה (שלב 2) נקראים Parameters — הם רק השמות שהמתודה משתמשת בהם פנימית. 80, 90, 70 בקריאה בפועל נקראים Arguments — הערכים האמיתיים שנשלחים. C# מתאימה אותם לפי הסדר: 80 → a, 90 → b, 70 → c.
עכשיו, אם צריך לעגל את הממוצע — משנים שורה אחת בלבד, בתוך CalculateAverage, וכל שלושת מקומות הקריאה מקבלים את התיקון אוטומטית:
static double CalculateAverage(int a, int b, int c)
{
return Math.Round((a + b + c) / 3.0, 2); // שינוי אחד, במקום אחד
}זו בדיוק הנקודה: לוגיקה שנכתבה פעם אחת מתוקנת פעם אחת.
שלב 4: מתודה שלא מחזירה כלום — void
לא כל מתודה צריכה להחזיר ערך. מתודה שרק “עושה” משהו (למשל מדפיסה לקונסולה) ולא מפיקה תוצאה לשימוש חוזר, מוגדרת עם void במקום טיפוס החזרה:
static void PrintGreeting(string name)
{
Console.WriteLine($"Hello, {name}!");
}
PrintGreeting("Dana"); // בלי "=" — אין ערך מוחזר שאפשר לשמור במשתנהvoid (פשוטו כמשמעו: “ריק”) אומר “אין כאן return עם ערך”. אפשר עדיין לכתוב return; בלי ערך בתוך מתודת void, כדי לצאת ממנה מוקדם (למשל if (name == null) return;) — רק אי אפשר להחזיר ערך אחריה.
שלב 5: כמה מתודות תחת אותו שם — Overloading
מה קורה אם רוצים לחשב ממוצע גם למערך שלם, לא רק לשלושה מספרים נפרדים? אפשר לתת שם חדש (CalculateAverageOfArray), אבל זה מכריח את מי שקורא לפונקציה לזכור שני שמות שונים לאותו רעיון. הפתרון: Overloading — כמה מתודות עם אותו שם, שנבדלות במספר או בטיפוס הפרמטרים:
static double CalculateAverage(int a, int b, int c) => (a + b + c) / 3.0;
static double CalculateAverage(int[] numbers) => numbers.Sum() / (double)numbers.Length;(שימו לב לתחביר => — זו צורה מקוצרת לגוף בן-שורה-אחת של מתודה, זהה לגמרי ל-{ return ...; }.)
עכשיו CalculateAverage(80, 90, 70) מפעילה את הגרסה הראשונה, ו-CalculateAverage(myArray) מפעילה את השנייה — C# בוחרת אוטומטית לפי מה שנשלח בקריאה. הקוד שקורא לפונקציה פשוט קורא ל-CalculateAverage, בלי לדעת (ובלי צורך לדעת) שיש שתי גרסאות.
טעות נפוצה: לשכוח return
static double CalculateAverage(int a, int b, int c)
{
double result = (a + b + c) / 3.0;
// שכחו return result; כאן
}הקוד הזה לא מתקמפל בכלל — שגיאה: not all code paths return a value. הסיבה: הגדרנו שהמתודה מחזירה double (זה מה ש-double בתחילת החתימה מבטיח לכל מי שקורא לה), אבל בפועל אין שום return שמקיים את ההבטחה הזו. C# בודקת את זה בזמן קומפילציה, לפני שהתוכנית בכלל רצה — כדי שלא תגלו את הבעיה רק כשמישהו יקרא למתודה ויצפה לערך שלא קיים.
הסבר עיקרי
למה Overloading פותר בעיה אמיתית: בלי Overloading, היינו צריכים שמות שונים לכל גרסה (CalculateAverageOfThree, CalculateAverageOfArray) — מסורבל, ומכריח את כל מי שקורא לפונקציה לזכור איזה שם מתאים לאיזה מקרה. עם Overloading, יש שם אחד (CalculateAverage), ו-C# בוחרת את הגרסה הנכונה לבד לפי מספר/טיפוס הארגומנטים שנשלחו — קוד הקורא (Caller) פשוט חושב “אני רוצה ממוצע”, לא “אני צריך את הגרסה השנייה”.
יתרונות
לוגיקה חוזרת נכתבת פעם אחת ומתוקנת במקום אחד; קוד קריא יותר — שם הפונקציה מתעד מה היא עושה בלי צורך בהערה; Overloading נותן API טבעי וקריא עם שם אחד לכמה צורות שימוש קרובות.
חסרונות
פונקציה עם יותר מדי פרמטרים (5+) הופכת קשה לקרוא בנקודת הקריאה; Overloading לא זהיר (חתימות שקשה להבחין ביניהן) עלול לבלבל יותר משהוא עוזר.
נקודות חשובות
• Parameter = הגדרה בחתימה; Argument = ערך אמיתי שנשלח בקריאה
• void = הפונקציה לא מחזירה ערך; כל טיפוס אחר חייב return בכל נתיב אפשרי בקוד
• return יוצא מהפונקציה מיידית — קוד אחריו באותה ריצה לא מתבצע
• Overloading = כמה מתודות באותו שם, נבדלות במספר/טיפוס הפרמטרים (לא בטיפוס ההחזרה בלבד)
טעויות נפוצות
• לשכוח return בפונקציה שלא void — שגיאת קומפילציה “not all code paths return a value” (ראו הסבר מלא למעלה)
• לצפות שפונקציית void תחזיר ערך שאפשר לשמור במשתנה
• לנסות Overload רק לפי טיפוס ההחזרה בלי לשנות פרמטרים — לא חוקי ב-C#, הקומפיילר לא יודע להבדיל
סיכום
פונקציות (מתודות) אורזות לוגיקה חוזרת לבלוק אחד עם שם, כדי שהקוד ייכתב פעם אחת ויתוקן במקום אחד. Parameters הם הקלט שהפונקציה מגדירה, Return Value הוא הפלט שהיא מחזירה, ו-void מציין שאין ערך מוחזר. Overloading מאפשר כמה גרסאות של אותה פעולה תחת שם אחד, כשה-Compiler בוחר את הגרסה הנכונה לפי הארגומנטים שנשלחו.
דוקומנטציה רשמית
Microsoft Learn — Method overloading
תרגילים
תרגיל 1 — פונקציה עם ערך מוחזר
המשימה: כתבו פונקציה IsEven(int number) שמחזירה bool — האם המספר זוגי. קראו לה על כמה מספרים והדפיסו את התוצאה.
בדיקה: IsEven(4) מחזירה true; IsEven(7) מחזירה false.
תרגיל 2 — Overloading
המשימה: כתבו שתי גרסאות של פונקציה Print: אחת שמקבלת string ומדפיסה אותו כמו שהוא, ואחת שמקבלת int ומדפיסה אותו עם הקידומת "מספר: ".
בדיקה: Print("שלום") מדפיס שלום; Print(5) מדפיס מספר: 5 — בלי שקוראים לפונקציות בשמות שונים.
פרויקט מסכם
המשימה: כתבו “מחשבון פשוט” עם 4 פונקציות נפרדות: Add, Subtract, Multiply, Divide, כל אחת מקבלת שני double ומחזירה double.
דרישות:
- כל פעולה היא פונקציה נפרדת עם ערך מוחזר (לא
void) Divideבודקת אם המכנה הוא0— אם כן, מדפיסה הודעת שגיאה ומחזירה0במקום לחשב חלוקה לא חוקית- בפונקציית
Main, קראו לכל ארבע הפונקציות עם ערכים לדוגמה והדפיסו את התוצאות
בדיקה: Add(5, 3) מחזיר 8; Divide(10, 0) מדפיס הודעת שגיאה ומחזיר 0 בלי לקרוס.
מה בפרק הבא
בפרק הבא — Enums: איך מייצגים קבוצת אפשרויות קבועה (כמו ימות השבוע, סטטוס הזמנה) בלי להסתמך על “מספרי קסם” או מחרוזות חופשיות שקל לטעות בהן.