לפרמטר אופציונלי יש ערך ברירת מחדל בהצהרת המתודה, כך שהקוראים יכולים להשמיט אותו. ארגומנט עם שם מעביר ערך לפי שם הפרמטר במקום לפי המיקום. יחד הם מאפשרים למתודה אחת לשרת צורות קריאה רבות בלי ערימה של העמסות.
פלט:
to ana@mail.com: (no subject), retries 3
to ben@mail.com: Invoice #1042, retries 3
to cy@mail.com: (no subject) [URGENT], retries 3
to dev@mail.com: Build failed, retries 0
הקריאה השלישית מדלגת על subject ומגדירה את urgent לפי שם; בלי ארגומנטים עם שם היא הייתה צריכה להעביר שוב את "(no subject)" רק כדי להגיע למיקום השלישי. הקריאה האחרונה מעבירה כל ארגומנט לפי שם, בסדר שונה מזה של ההצהרה, וזה חוקי.
כללים לערכי ברירת מחדל
ערך ברירת מחדל חייב להיות משהו שהקומפיילר יכול לחשב:
- קבוע (
3,"INFO",true,1.5m, שדהconst, איבר של enum) nullלטיפוס הפניה או לטיפוס nullabledefault(T), אוnew T()לטיפוס ערךT
כל דבר שמחושב בזמן ריצה נדחה. המקרה הקלאסי הוא תאריך:
static void Schedule(string task, DateTime at = DateTime.Now) { }
// error CS1736: Default parameter value for 'at' must be a compile-time constant
הפתרון המקובל הוא פרמטר nullable עם null כברירת מחדל, שמקבל ערך בגוף המתודה:
פלט:
backup at 2026-01-01 09:00, tags: 0
report at 2026-03-15 18:30, tags: 0
deploy at 2026-01-01 09:00, tags: 2
אותו טריק מתאים גם לאוספים: ברירת מחדל של new List<string>() אינה קבוע, אז קבעו ברירת מחדל null וצרו את הרשימה בפנים. זה גם מונע את הבאג של ברירת מחדל משותפת וניתנת לשינוי בסגנון Python, ש-C# שוללת מראש כשהיא מתירה רק קבועים.
כללי סדר
פרמטרים נדרשים באים קודם, אופציונליים אחריהם, ומערך params (אם יש) בסוף:
static void Log(string message, string level = "INFO", params string[] tags) { } // OK
static void Log(string level = "INFO", string message) { }
// error CS1737: Optional parameters must appear after all required parameters
פרמטרי ref ו-out לא יכולים להיות אופציונליים.
בצד הקורא, ארגומנטים לפי מיקום ממלאים פרמטרים מההתחלה. ארגומנטים עם שם יכולים לבוא אחריהם בכל סדר. מאז C# 7.2 ארגומנט עם שם יכול להופיע גם לפני ארגומנט לפי מיקום, אבל רק כשהוא נמצא במיקום שלו (SendEmail("a@b.c", subject: "Hi", true)); בגרסאות ישנות יותר כל הארגומנטים עם שם חייבים לבוא בסוף. השמטה של פרמטר נדרש, גם כשנותנים שמות לאחרים, היא שגיאת קומפילציה.
ארגומנטים עם שם לשיפור הקריאות
ארגומנטים עם שם שימושיים גם כששום דבר אינו אופציונלי. true, false, null ומספרים חשופים לא אומרים כלום במקום הקריאה:
ResizeImage(photo, 800, 600, true, false); // which is which?
ResizeImage(photo, width: 800, height: 600, keepAspect: true, upscale: false);
שינוי שם של פרמטר הופך לשינוי שובר עבור קוראים שמשתמשים בשם, וכדאי לזכור את זה בספרייה ציבורית.
פרמטרים אופציונליים מול העמסות
לפני C# 4, אותה גמישות דרשה העמסה לכל שילוב. פרמטרים אופציונליים מאחדים אותן למתודה אחת:
// overloads
static void Connect(string host) => Connect(host, 443);
static void Connect(string host, int port) => Connect(host, port, 30);
static void Connect(string host, int port, int timeoutSeconds) { /* ... */ }
// one method with optional parameters
static void Connect(string host, int port = 443, int timeoutSeconds = 30) { /* ... */ }
כששתיהן קיימות, פתרון ההעמסה מעדיף מועמד שלא צריך למלא אף ברירת מחדל:
פלט:
Greet()
Greet(string) with Lena
Greet() מתאימה לשתי המתודות, והקומפיילר בוחר את זו שאין בה פרמטר אופציונלי שהושמט. ערבוב של שתי הטכניקות על אותו שם יוצר בעיקר קריאות שקשה לנחש לאן הן הולכות, אז בחרו אחת לכל מתודה.
בחרו בהעמסות כשהגרסאות צריכות קוד שונה או טיפוסי פרמטרים שונים, ובפרמטרים אופציונליים כשהן נבדלות רק בערכי ברירת המחדל.
ברירות המחדל נצרבות בקורא
ערך ברירת מחדל לא נשלף בזמן ריצה. הקומפיילר מעתיק אותו לתוך כל מקום קריאה כשהקוד הקורא מתקמפל. Connect("api.shop.com") מתקמפל ל-Connect("api.shop.com", 443, 30).
יש לזה השלכה על ספריות. נניח שגרסה 1 של חבילה מספקת את Connect(string host, int timeoutSeconds = 30), וגרסה 2 משנה את ברירת המחדל ל-10. אפליקציה שקומפלה מול גרסה 1 ממשיכה להעביר 30 גם אחרי שמחליפים ל-DLL של גרסה 2, עד שהאפליקציה עצמה מקומפלת מחדש. גם הוספה של פרמטר אופציונלי חדש למתודה ציבורית קיימת שוברת קוראים שכבר קומפלו, כי החתימה של המתודה השתנתה והם עדיין מחפשים את הישנה (MissingMethodException בזמן ריצה).
באפליקציה אחת שמקומפלת כולה יחד זה אף פעם לא משנה. ב-API ציבורי בחבילות NuGet, העמסות (ששומרות את ברירות המחדל בתוך הספרייה) או ברירת מחדל null שמקבלת ערך בגוף המתודה מונעות את הבעיה.
ברירות מחדל והטיפוס המוצהר
אותו כלל של זמן קומפילציה אומר שכשגם ממשק וגם מחלקה מצהירים על ברירות מחדל, ברירת המחדל נלקחת מהטיפוס של המשתנה שדרכו קוראים, ולא מהאובייקט:
פלט:
printing "report" x5
printing "report" x1
אותו אובייקט, שתי ברירות מחדל שונות. שמרו על ערכי ברירת מחדל זהים בין ממשק למימושים שלו, או הצהירו עליהם רק במקום אחד.
תכונות caller information
פרמטרים אופציונליים מפעילים גם את תכונות ה-caller info ב-System.Runtime.CompilerServices. הקומפיילר ממלא אותם בפרטים על מקום הקריאה:
פלט:
[Main:18] starting
[SaveOrder:13] order saved
כך ספריות לוגים מתעדות מאיפה הגיעה הודעה בלי שהקורא יקליד את זה, וכך מימושים של INotifyPropertyChanged מקבלים את שם המאפיין. [CallerFilePath] מוסיף באותה דרך את הנתיב של קובץ המקור.
שאלות נפוצות
איך הופכים פרמטר לאופציונלי ב-C#?
תנו לו ערך ברירת מחדל בהצהרה: static void Log(string message, string level = "INFO"). אז הקוראים יכולים לכתוב Log("started") או Log("failed", "ERROR"). פרמטרים אופציונליים חייבים להופיע אחרי כל הפרמטרים הנדרשים, וברירת המחדל חייבת להיות קבוע בזמן קומפילציה.
מה הם ארגומנטים עם שם ב-C#?
ארגומנטים שמועברים עם שם הפרמטר: SendEmail(to: "ana@mail.com", urgent: true). הם מאפשרים לדלג על פרמטרים אופציונליים באמצע, להעביר ארגומנטים בכל סדר, ולהפוך קריאות עם true/false מילוליים או עם מספרים לקריאות במבט אחד.
למה אי אפשר להשתמש ב-DateTime.Now כערך ברירת מחדל של פרמטר?
ברירות מחדל חייבות להיות קבועים בזמן קומפילציה, ו-DateTime.Now מחושב בזמן ריצה, ולכן הקומפיילר מדווח CS1736, Default parameter value for 'at' must be a compile-time constant. השתמשו במקום זאת בפרמטר nullable: DateTime? at = null, ואז DateTime time = at ?? DateTime.Now; בגוף המתודה.
להשתמש בפרמטרים אופציונליים או בהעמסות מתודות ב-C#?
פרמטרים אופציונליים פשוטים יותר כשהגרסאות נבדלות רק בערכי ברירת המחדל. העמסות עדיפות כשהגרסאות צריכות לוגיקה או טיפוסים שונים, וגם בספריות ציבוריות, כי ערך ברירת מחדל מתקמפל לתוך כל קורא ושינוי שלו בהמשך לא מגיע לקוד שכבר קומפל.
מה המשמעות של "Optional parameters must appear after all required parameters"?
שגיאה CS1737: אחרי פרמטר עם ערך ברירת מחדל מופיע פרמטר בלי ערך כזה. העבירו את הפרמטרים הנדרשים להתחלה: (string to, bool urgent = false), לא (bool urgent = false, string to). אחרי פרמטר אופציונלי יכולים להופיע רק עוד פרמטרים אופציונליים או מערך params.