מתודה היא בלוק קוד עם שם שמקבל קלטים (פרמטרים), עושה את העבודה שלו, ויכול להחזיר תוצאה (ערך ההחזרה). ב-C# כל מתודה נמצאת בתוך טיפוס; אין פונקציות עצמאיות.
פלט:
Headphones 48.00
Cable 11.40
המבנה של מתודה
public static decimal PriceWithTax(decimal net, decimal taxRate)
{
return net * (1 + taxRate);
}
- מגדיר גישה (
public,private, ...): מי רשאי לקרוא לה. אם משמיטים אותו, המתודה היאprivateלטיפוס שלה. static: המתודה שייכת לטיפוס, לא לאובייקט. עוד על זה בהמשך.- טיפוס החזרה: הטיפוס של הערך שהיא מחזירה, או
voidאם היא לא מחזירה כלום. - שם: לפי המוסכמה ב-PascalCase, ובדרך כלל פועל (
Calculate,SendEmail,TryParse). - רשימת פרמטרים: אפס או יותר זוגות של
type name. כל פרמטר הוא משתנה מקומי שמאותחל מהארגומנט של הקורא. - גוף: ההוראות שבין הסוגריים המסולסלים.
השם יחד עם טיפוסי הפרמטרים (וכל מגדיר ref, out או in) יוצרים את החתימה של המתודה. טיפוס ההחזרה אינו חלק ממנה, וזה חשוב בהעמסה.
ערכי החזרה ו-void
return מסיים את המתודה מיד ומחזיר ערך. מתודה עם טיפוס החזרה חייבת להחזיר ערך מהטיפוס הזה בכל מסלול שעובר בה:
static string Grade(int score)
{
if (score >= 50)
return "pass";
} // error CS0161: 'Program.Grade(int)': not all code paths return a value
הקומפיילר בודק כל ענף. הוסיפו את ה-return "fail"; החסר, או throw אם ההגעה לנקודה הזו היא באג.
מתודת void לא מחזירה כלום. היא עדיין יכולה להשתמש ב-return; ריק כדי לצאת מוקדם:
פלט:
ana@mail.com: newsletter sent
ben@mail.com: not subscribed, skipped
False
True
מתודות עם גוף ביטוי
כשהגוף של מתודה הוא ביטוי אחד, => מחליף את הסוגריים המסולסלים ואת ה-return:
static decimal Discount(decimal price) => price * 0.1m;
static bool IsAdult(int age) => age >= 18;
static void Log(string message) => Console.WriteLine($"[log] {message}");
זו אותה מתודה, רק כתובה בקיצור. זה עובד גם למתודות void, כל עוד הגוף הוא ביטוי אחד (קריאה, השמה).
פרמטרים הם עותקים
כברירת מחדל ארגומנטים מועברים by value: הפרמטר מקבל עותק של הארגומנט. השמה לפרמטר בתוך המתודה לא משפיעה על המשתנה של הקורא:
פלט:
inside: 42
after: 21
scores: 70, 85, 100
ה-List השתנה כי בטיפוס הפניה הערך שמועתק הוא ההפניה: המתודה והקורא מצביעים על אותו אובייקט רשימה. השמה מחדש של scores = new List<int>() בתוך המתודה לא הייתה משפיעה על הקורא. כדי לאפשר למתודה לבצע השמה למשתנה של הקורא, השתמשו ב-ref או ב-out; כדי לתת לפרמטרים ערכי ברירת מחדל ולהעביר אותם לפי שם, השתמשו בפרמטרים אופציונליים ובארגומנטים עם שם.
החזרת כמה ערכים עם tuple
מאז C# 7.0 מתודה יכולה להחזיר tuple עם איברים בעלי שם, והקורא יכול לפרק אותו למשתנים נפרדים:
פלט:
min 15, max 27, avg 21.0
range 12
_ משליך את האיבר שלא צריך. לתוצאות שמשתמשים בהן בהרבה מקומות, מחלקה קטנה או record עם מאפיינים בעלי שם מתעדים את המבנה טוב יותר מ-tuple.
העמסה (overloading)
כמה מתודות יכולות לחלוק שם אם רשימות הפרמטרים שלהן שונות במספר, בטיפוסים או בסדר הטיפוסים. הקומפיילר בוחר את ההתאמה הטובה ביותר לכל קריאה:
פלט:
12.57
13.50
25
Area(5) בוחרת את הגרסה של int כי זו התאמה מדויקת; Area(2.0) בוחרת את זו של double. מתודות לא יכולות להיבדל רק בטיפוס ההחזרה: שתי מתודות Area(int) שמחזירות int ו-double הן שגיאה CS0111, Type 'Program' already defines a member called 'Area' with the same parameter types, כי קריאה כמו Area(5) לא הייתה יכולה להבחין ביניהן.
גם קריאה שאף העמסה לא מקבלת נכשלת בזמן קומפילציה: Area(1, 2, 3) היא CS1501, No overload for method 'Area' takes 3 arguments, ו-Area("big") היא CS1503, Argument 1: cannot convert from 'string' to 'double'.
params: מספר משתנה של ארגומנטים
פרמטר מערך params מקבל כל מספר של ארגומנטים, כולל אפס. הקומפיילר בונה את המערך בשבילכם:
פלט:
empty cart: 0 item(s), 0
one item: 1 item(s), 9.99
three items: 3 item(s), 19.75
from array: 2 item(s), 50
הכללים: params חייב להיות הפרמטר האחרון (אחרת CS0231), למתודה יש לכל היותר אחד כזה, והעברה של מערך קיים מוסרת את המערך הזה כמו שהוא. Console.WriteLine("{0} and {1}", a, b) ו-string.Format הן מתודות params object[]. C# 13 הרחיבה את params לטיפוסי אוספים אחרים כמו List<T> ו-ReadOnlySpan<T>; לפני כן, רק מערכים הותרו.
מתודות סטטיות ומתודות מופע
מתודה static שייכת לטיפוס וקוראים לה על שם הטיפוס: Math.Max(3, 7), Program.Total(...). מתודת מופע שייכת לאובייקט ויכולה להשתמש בשדות שלו:
פלט:
12
קריאה למתודת מופע ממתודה סטטית בלי אובייקט היא טעות המתחילים הנפוצה ביותר ב-C#:
class Program
{
void Greet() => Console.WriteLine("hi");
static void Main()
{
Greet();
// error CS0120: An object reference is required for the non-static field, method, or property 'Program.Greet()'
}
}
סמנו את Greet כ-static, או צרו אובייקט וקראו לה עליו: new Program().Greet();. הדף על static מרחיב על איברים סטטיים ומחלקות סטטיות.
המתודה Main
Main היא המקום שבו תוכנית מתחילה. היא חייבת להיות static, והחתימות האלה תקינות:
static void Main() { }
static void Main(string[] args) { } // command-line arguments
static int Main(string[] args) { return 0; } // exit code for the shell
static async Task Main() { } // C# 7.1 and later
הצורה async Task Main מאפשרת לנקודת הכניסה לבצע await ישירות (ראו async ו-await). מאז C# 9 קובץ יכול להכיל במקום זאת top-level statements: קוד בלי class ובלי Main בכלל, כמו בפרויקט חדש של dotnet new console. הקומפיילר יוצר סביבו את המחלקה ואת המתודה Main, כך שזה אותו דבר בכתיבה קצרה יותר; args עדיין זמין.
רקורסיה
מתודה יכולה לקרוא לעצמה. כל קריאה מקבלת פרמטרים ומשתנים מקומיים משלה, ומקרה בסיס חייב לעצור את השרשרת:
פלט:
120
2432902008176640000
35
בלי מקרה בסיס, או עם קלט עמוק מדי, כל קריאה מוסיפה מסגרת למחסנית עד שהתוכנית קורסת עם StackOverflowException. אי אפשר לתפוס את החריגה הזו, ולכן לקלט בעומק לא ידוע, כתבו את הרקורסיה מחדש כלולאה.
פונקציות מקומיות
C# 7.0 הציגה פונקציות מקומיות: מתודות שמוצהרות בתוך מתודה אחרת וגלויות רק שם. הן יכולות להשתמש במשתנים של המתודה העוטפת:
// C# 7.0 and later
static int CountValid(string[] codes)
{
int count = 0;
foreach (string code in codes)
{
if (IsValid(code)) count++;
}
return count;
bool IsValid(string c) => c.Length == 6 && char.IsLetter(c[0]);
}
הן שומרות פונקציית עזר ליד הקורא היחיד שלה בלי לחשוף אותה לשאר המחלקה. מאז C# 8 אפשר לסמן אותן כ-static כדי לאסור לכידה של משתנים חיצוניים. במקום שבו פונקציה מקומית לא זמינה, מתודה פרטית סטטית באותה מחלקה עושה את אותה עבודה, כמו בשאר הדוגמאות בדף הזה.
שאלות נפוצות
מה ההבדל בין מתודה לפונקציה ב-C#?
ב-C# כל פונקציה מוצהרת בתוך class, struct או record, והיא נקראת מתודה. אין פונקציות עצמאיות. גם top-level statements (C# 9) ופונקציות מקומיות (C# 7) מתקמפלות למתודות של טיפוס שנוצר אוטומטית או של הטיפוס העוטף. "פונקציה" משמשת באופן לא רשמי, וגם עבור למבדות ו-delegates.
מה עושה params ב-C#?
params מאפשר למתודה לקבל מספר משתנה של ארגומנטים מטיפוס אחד: אפשר לקרוא ל-static int Sum(params int[] numbers) כ-Sum(), Sum(4), Sum(4, 5, 6) או Sum(existingArray). הקומפיילר אורז את הארגומנטים במערך. הפרמטר params חייב להיות האחרון, ולמתודה יכול להיות רק אחד כזה.
מהי המתודה Main ב-C#?
Main היא נקודת הכניסה של התוכנית: המתודה שסביבת הריצה קוראת לה ראשונה. היא חייבת להיות static, יכולה להחזיר void או int (קוד היציאה), ויכולה לקבל string[] args לארגומנטים של שורת הפקודה. מאז C# 7.1 היא יכולה להיות גם async Task Main, ומאז C# 9 top-level statements מאפשרים לקומפיילר ליצור אותה בשבילכם.
איך מחזירים כמה ערכים ממתודה ב-C#?
החזירו tuple: static (int Min, int Max) Range(int[] values) ו-return (min, max);. הקורא יכול לקרוא את result.Min או לפרק עם var (lo, hi) = Range(values);. לערכים שעוברים יחד בכל הקוד, מחלקה קטנה או record ברורים יותר; פרמטרי out הם החלופה הישנה.
למה מופיעה השגיאה "An object reference is required for the non-static field, method, or property"?
מתודה static כמו Main קראה ישירות למתודת מופע. מתודות סטטיות שייכות לטיפוס, ואין להן אובייקט לקרוא עליו. סמנו את המתודה הנקראת כ-static, או צרו קודם אובייקט: var app = new Program(); app.Greet();.
מה המשמעות של "not all code paths return a value"?
שגיאה CS0161: במתודה עם טיפוס החזרה יש לפחות מסלול אחד שמגיע לסוגר המסולסל הסוגר בלי return. בדרך כלל if מחזיר ערך אבל אין אחריו else או return סופי. הוסיפו return (או throw) בסוף המתודה.