סי שארפ

שפת תכנות מונחית עצמים שפותחה על ידי מיקרוסופט
(הופנה מהדף C Sharp)

C#‎ (נהוג לבטא C Sharp או "סִי שַׁארְפּ") היא שפת תכנות עילית מרובת-פרדיגמות, מונחית עצמים בעיקרה, המשלבת רעיונות כמו טיפוסיות חזקה, אימפרטיביות, הצהרתיות, פונקציונליות, פרוצדורליות וגנריות. השפה פותחה על ידי חברת מיקרוסופט בשנת 2000 כחלק מפרויקט דוט נט ותוקננה בשנים 2005 ו-2006 על ידי ארגון התקינה Ecma כתקן ECMA-334 ועל ידי ארגון התקינה הבין-לאומי כתקן ISO/IEC 23270:2006. נכון למאי 2024, הגרסה היציבה העדכנית של השפה היא C# 12, ששוחררה עם .NET 8[1].

C#‎
C sharp
פרדיגמות מרובת פרדיגמות: מונחת-עצמים, אימפרטיבית, רפלקטיבית, פונקציונלית, מבנית, מונחת אירועים וגנרית.
תאריך השקה 2001 עריכת הנתון בוויקינתונים
מתכנן מיקרוסופט, אנדרס הלסברג, Mads Torgersen עריכת הנתון בוויקינתונים
מפתח מיקרוסופט עריכת הנתון בוויקינתונים
טיפוסיות חזקה, בטוחה, (סטטית עם תמיכה בטיפוסיות דינמית)
מימושים Visual C#, Mono, Xamarin, Cordova
ניבים Cω, Spec#, Polyphonic C#
הושפעה על ידי ++C,‏ Smalltalk,‏ Java,‏ Visual Basic, אייפל, Modula-3
השפיעה על סוויפט (שפת תכנות), D‏, F#‏, Nemerle‏, Vala
סיומת cs.
https://docs.microsoft.com/en-us/dotnet/csharp/
לעריכה בוויקינתונים שמשמש מקור לחלק מהמידע בתבנית

היסטוריה

עריכה
 
אנדרס הלסברג - האדריכל הראשי של C#. איש בורלנד לשעבר שהישגו הראשון במיקרוסופט היה ניהול הפיתוח של J++‎

לאחר הפסדה של "מיקרוסופט" במשפט לחברת "סאן", בעקבות סכסוך שפרץ ביניהן סביב המימוש של "מיקרוסופט" לשפת ג'אווה, שנקרא J++‎, חויבה "מיקרוסופט" על פי פסק הדין להפסיק לשווק את J++‎ וכן את המכונה הווירטואלית שפיתחה עבורה, MS-JVM[2]. עקב כך הוחלט ב"מיקרוסופט" על פיתוח שפה חדשה שתהווה תחליף ראוי לשפות התכנות מהדור הקודם כמו ויז'ואל בייסיק או C++‎ וכן תהווה יריב שקול לג'אווה. צוות פיתוח בראשות מהנדס התוכנה הדני אנדרס הלסברג (מי שהמציא את טורבו פסקל והיה מאדריכלי דלפי) הופקד על פיתוחה בתחילת 1999, והגרסה הראשונה הושקה עם ההכרזה על דוט נט ביולי 2000[3]. גרסה זו סיפקה את התשתית הראשונית לכתיבת יישומים שולחניים ואתרי אינטרנט, תוך שהיא שואבת הרבה מממשק תכנות יישומים והתחביר של ג'אווה, גם בטכנולוגיית פיתוח האתרים, ASP.NET.

גרסה 1.1, אשר יצאה ב-3 באפריל 2003, הכילה שיפורים בטכנולוגיית ASP.NET אשר מאפשרים פיתוח אתרים המותאמים למכשירים ניידים, שיפורי אבטחה בפיתוח יישומים שולחניים, תמיכה מובנית בגישה לבסיס נתונים אורקל ובשימוש במתאם בסיסי הנתונים האוניברסלי של "מיקרוסופט", ODBC. גרסה זו גם תומכת בהפעלה של מספר גרסאות של אותו יישום במקביל על אותו מחשב, וכן תומכת לראשונה בפרוטוקול IPv6[4].

גרסה 2.0, אשר יצאה ב-22 בינואר 2006, הוסיפה לראשונה תמיכה בתכנות גנרי, שהיה קיים כבר ב-++C. גרסה זו תמכה לראשונה ביישומי 64 ביט. נוספה תמיכה מובנית לגישה למערכת ההרשאות של Windows, ונוספו שיפורים בטכנולוגיית הגישה לבסיסי נתונים ADO.NET. בתחום פיתוח האתרים, נוספו מנגנונים אשר מאפשרים לשלוט בצורה טובה יותר בתבנית הכללית של כל הדפים באתר. נוספה תמיכה מובנית בתקשורת SSL, ותמיכה בפרוטוקולים FTP‎, Ping, SMTP ו-SOAP 1.2. שופרה התמיכה בטכנולוגיית COM וביישומי שורת הפקודה. נוספה תמיכה בקבלת חיבורי HTTP ו-FTP מיישומים אחרים. שופרה התמיכה בסמפור, ונוספה תמיכה בטרנזאקציות דרך טכנולוגיית DTC. פקדים רבים נוספו לפיתוח תוכנות שולחניות, ונוספה טכנולוגיית ClickOnce, אשר מאפשרת ליישומים שולחניים להתעדכן באופן אוטומטי[4].

גרסה 3 אשר יצאה בנובמבר 2007 הוסיפה הרחבת שיטות (שמאפשר בזמן ריצה להוסיף שיטות לאובייקטים קיימים), טיפוס מרומז של משתנים מקומיים, ביטויי למדא, טיפוסים אנונימיים, טיפוס מוסק ואתחול של אובייקטים ואוספים (בגרסאות הדוט נט המקבילות הוספו WCF, WPF ו-LINQ)[4].

גרסה 4 אשר יצאה באפריל 2010 הוסיפה פרמטרים לפי שם ופרמטרים אופציונליים, טיפוסים דינמיים, ותמיכה מתקדמת ב-COM (בגרסת הדוט נט המקבילה הוספו יכולות של תכנות מקבילי, אתחול עצל, ומהדר DLR)[4].

גרסה 5 אשר יצאה באוגוסט 2012 הוסיפה פונקציות אסינכרוניות, ומידע על המתקשר לפונקציה[4].

ב-2 באפריל 2014 "מיקרוסופט" הוציאה לאור מהדר סטטי בשם DotNET Native ליישומי החנות של Windows, המאפשר להדר קוד בשפות הדוט נט לקוד מכונה באופן ישיר (Native Code), ללא שימוש בהידור דינמי (JIT).

בגרסה 6 אשר יצאה ביולי 2015 הוספו התכונות הבאות: הידור כשירות (service), יבוא של טיפוסים סטטיים למרחב שם, מסנן לחריגות, אתחול אוטומטי של מאפיין לקריאה בלבד, אתחול של מילון, Await בבלוק של catch/finally, אינטרפולציה על מחרוזת והאופרטור nameof[4].

בגרסה 9, אשר יצאה בנובמבר 2020, נוספה תמיכה ברשומות (records), האפשרות להגדרת ערך של מאפיין בעת היצירה בלבד (init), והרצת קוד פשוט ללא כל צורך במחלקה ובשיטת Main, תבניות לוגיות (שמאפשרות שימוש במילים is, not, and, or על מנת לפשט תנאים) וקריאה מקוצרת לבנאי ללא צורך בשם המחלקה (אך לא ניתן להשתמש ב-var)[5].

הדמיון לג'אווה

עריכה

קיימים קווי דמיון רבים מאוד בין שפת #C לג'אווה, כגון; אובייקטים כהפניות (References), קוד ביניים ומכונה וירטואלית, הידור דינמי (JIT), ירושה יחידה עם ממשקים, זיכרון מנוהל ופינוי אשפה, שיקוף, Boxing, תהליכונים מובנים, יוניקוד מובנה, צורת השימוש בספריות ועוד. ובכל זאת, קיימים הבדלים גדולים בין השתיים. בעיקר בהגדרת אופן מימוש מנגנוני השפה והשוני במימוש תכנות גנרי. מלבד זאת ב-#C נוספו תכונות שבחלקן אומצו מוויז'ואל בייסיק כגון תכנות חזותי, תכנות מונחה אירועים, אתחול אוטומטי של משתנים, משתנה רב-תכליתי, מאפיינים, לולאת טווח ועוד. במיוחד גדל הפער בין השפות בגרסאות החדשות של #C בהן נוספו הרחבות כמו תכנות פונקציונלי, מתודות-הרחבה (Extension methods) והרחבת LINQ.

פיתוח השפה היה ראשוני, ונטול עכבות היסטוריות כמו תאימות לאחור, שהקשתה על האבולוציה של ג'אווה. ברור גם כי העובדה ש-C#‎ פותחה אחרי ג'אווה איפשרה ל"מיקרוסופט" ללמוד מהחסרונות והיתרונות שלה וליישם את הלקחים בשפה החדשה[6].

אנשי "סאן" טענו כי "מיקרוסופט" גנבה לא מעט רעיונות משפת ג'אווה. לטענתם, C#‎ היא בעצם חיקוי לא מוצלח של ג'אווה שממנו הוסרו אמינות, יצרנות ואבטחה[7]. ב"מיקרוסופט" מתעקשים לעומת זאת כי השפה דומה יותר ל-C++‎ מאשר לג'אווה.

ההבדל המהותי ביותר בין #C לג'אווה הוא מתודולוגי – #C היא שפה מונחית עצמים טהורה (Pure Object Oriented), ב-#C כל דבר הוא אובייקט, אפילו הטיפוסים הפשוטים (int, double, float, char ועוד) הם אובייקטים. ג'אווה לעומת זאת, היא אומנם מונחית עצמים אולם היא לא שפה מונחית עצמים טהורה, הטיפוסים הפשוטים הם פרימיטיביים (כמו ב-C), והיא לא מבחינה בין מתודות וירטואליות למתודות לא וירטואליות ועוד.

יעדי השפה

עריכה

תקן ECMA מפרט את יעדי הפיתוח של השפה:

  • הפשטה: השפה צריכה להיות פשוטה להבנה וכתיבה, דינאמית, גנרית, מודרנית ומונחית עצמים טהורה במידת האפשר.
  • הנדסה: השפה ויישומה צריכים לתמוך בעקרונות הנדסת תוכנה מתקדמים כמו בדיקת טיפוסיות, בדיקת חריגות גבול, מנגנון לאיתור שימוש במשתנים לא מאותחלים ואיסוף זבל.
  • פריסה: השפה צריכה לתמוך בפיתוח יישומים המתאימים להתקנה בסביבה מבוזרת – קרי, הפעלה בכל מערכת שבה מותקנים רכיבי זמן ריצה מתאימים.
  • ניידות: שימת דגש על ניידות הקוד כמו גם ניידות המתכנת, במיוחד הקלת המעבר לאלו שהתנסו בכתיבה בשפות C ו-C++‎.
  • בין-לאומיות: תמיכה בריבוי שפות כמו גם שפות שמיות (ימין לשמאל ושמאל לימין).
  • מודולריות: השפה צריכה להתאים למערכות שונות, ממערכות גדולות בעלות פונקציונליות מרובה דרך מחשב ביתי ועד למערכות משובצות מחשב ייעודיות קטנות.
  • ביצועיות: למרות היותה חסכונית במונחים של צריכת זיכרון וזמן עיבוד, היא אינה מתחרה ישירות בשפות C / C++‎ או שפת סף.

התחביר של C#‎ מבוסס בעיקרו על שפת התכנות C++‎ אך העיקרון התכנותי הושפע רבות מוויז'ואל בייסיק ומג'אווה[8], שמהן אומצו מספר מוטיבים כמו תכנות מונחה-אירועים, תכנות הצהרתי, תכנות פונקציונלי ותכנות גנרי.

אפיוני השפה

עריכה

מקור שמה הוא פרפרזה על מקור שמה של ++C שהוא אופרטור הקידום שמשמעו "קידום ערך באחד" או בהקשר זה קידום שפת C רמה אחת מעל. במוזיקה משמעות C#‎ היא הוספה של חצי טון לתו C (או "דו דיאז"). רעיון השימוש בסולמית אומץ על ידי "מיקרוסופט" כסיומת נפוצה לטכנולוגיות אחרות מבית החברה שעברו שיפוץ.

שפת #C תוכננה במטרה להשתלב בתשתית דוט נט הכוללת ספרייה תקנית גדולה וסביבת זמן ריצה. עם זאת, השפה איננה דורשת את דוט נט וקיימת מערכת מקבילה לסביבת לינוקס בשם Mono, ומערכת בשם Xamarin לסביבת אנדרואיד.

 
קוד של C#‎ מחשב שורשים של פונקציה בעזרת שיטת ניוטון-רפסון ושיטת החצייה.

בשפה שולבו מספר מאפיינים בולטים שהם חלק מובנה מהעיקרון הטכנולוגי של דוט נט:

  • תמיכה בסריאליזציה[9] שבה נשמר רצף בין הזיכרון לדיסק, כך ניתן לשמור אובייקט לקובץ ולשחזר אותו.
  • מקביליות (Concurrency) שבה ניתן לבצע פעילות אסינכרונית של עצמים שונים.
  • שיקוף (Reflection), כלומר חקירת מאפיינים שונים הקשורים לשפה עצמה בזמן ריצה, למשל לבדוק מהו הטיפוס המדויק של אובייקט נתון. באמצעות תכונה זו ניתן למשל להחליף שם של משתנה בכל המרחב, לעטוף שדה באמצעות תכונה, או להוציא חלק מקוד לפונקציה חדשה.
  • תכונות (Attributes) שמאפשרות להגדיר מגוון רחב של עזרים בקוד התכנותי, כמו פירוט תכונות של מחלקות, שיטות ומשתנים, הגדרת תהליכים תכנותיים ועד הסבר מובנה. מה שמייתר צורך בקובצי הגדרה חיצוניים כמו DEF ו-IDL.
  • אירועים (Events) שמאפשרים לרשום פונקציות לאירועים, ולהזניק הודעות מאובייקט אחד לשני.
  • מבנה השפה. ההיררכיה של קוד ב-#C מתחילה בפתרון (Solution) שיכול לאגד בתוכו פרויקטים (Project). כל פרויקט יכול לאגד בתוכו מחלקות (class). כל מחלקה יכולה להכיל בתוכה פונקציות (Function). פונקציות יכולות להיות משויכות לתחום מסוים (namespace).
  • הנחיות קדם-עיבוד. למרות שלמהדר השפה אין קדם מעבד (preprocessor) נפרד, ניתן לתת לו הנחיות בדומה לשפת ++C כאילו יש קדם מעבד. כל הנחיה תוקדם בתו #. במסגרת הנחיות אלה ניתן לתת הנחיות למהדר, להשתמש במשתני מערכת, לבצע פקודות התניה, ולחולל שגיאות ואזהרות של המהדר[10].

הטיפוסיות בשפה היא סטטית (עם תמיכה ברב-צורתיות של זמן ריצה), ותמיכה בטיפוס דינמי, המאפשר עבודה ב"טיפוסיות-ברווז" דינמית[11].

מערכת הטיפוסים היא חזקה ובטוחה, למעט באזורים בקוד המסומנים באמצעות המילה השמורה unsafe. באזורים אלה מתאפשרת גישה ישירה לזיכרון בעזרת מצביעים (בדומה לשפת C++‎)[12].

על פי רוב, טיפוסי המשתנים מוגדרים במפורש, אך בהקשרים רבים ניתן להשתמש במילה השמורה var על מנת לבקש מהמהדר להסיק אותם בעצמו[13]. כמו כן, ישנה הסקה מובלעת של טיפוסים במבנים תחביריים שונים כגון ביטויי למדא[14].

משתני ערך לעומת משתני ייחוס

עריכה

בשפת #C ישנה חלוקה של הטיפוסים לשני סוגים:[15]

  • טיפוסי ערך (Value Types): בעלי סמנטיקת ערך, בדומה למצב בשפת ++C. טיפוסי ערך יורשים מהמחלקה ValueType (שבתורה יורשת מ-object). טיפוסי ערך הם הטיפוסים הפרימיטיביים (כגון int‏, double, ‏char), מבנים (struct),‏ ו-enum[16]. עבור טיפוסי ערך קיים מנגנון אריזה (Boxing) ההופך אותם לטיפוסי התייחסות כאשר הדבר נדרש[17].
  • טיפוסי התייחסות (Reference Types): בעלי סמנטיקת התייחסות, בדומה למצב בשפת ג'אווה. מחלקות, מערכים, רשומות (records) נציגים (delegates) ממשקים, טיפוסים דינמיים ומחרוזות הם טיפוסי התייחסות[18].

ההבדל העיקרי בין שני סוגים אלו הוא באופן שבו הנתונים מוחזקים בזיכרון. בעוד שמשתני ערך מחזיקים את התוכן עצמו, משתני ייחוס מחזיקים כתובת אל התוכן, כתוצאה מכך ישנו הבדל באופן שבו האובייקט מועתק (בפעולת השמה או בהעברה כפרמטר). בהעתקת "משתנה ערך" מועתק האובייקט עצמו, וכל פעולה המתבצעת בפונקציה מבוצעת על העותק, ולא על האובייקט המקורי. בהעתקת "אובייקט מטיפוס התייחסות", מועתק ייחוס (מזהה המגדיר כיצד למצוא את האובייקט בזיכרון), וכל פעולה שאיננה העתקה מבוצעת על האובייקט המקורי (באופן דומה מעט לעבודה עם מצביעים בשפת C).

בדומה למחלקה, גם מבנים יכולים להכיל קבועים, שדות, תכונות, מתודות, אופרטורים, אירועים ואף לממש ממשקים, אך לא ניתן להגדיר עבורם ירושה.

בעוד שמחלקה היא מבנה התכנות העיקרי, מבנים מיועדים לעטוף קבוצות קטנות של משתנים קשורים, בעיקר כאלה שאינם אמורים להשתנות לאחר היצירה של האובייקט[19].

אין לבלבל בין טיפוסי ערך והתייחסות ובין העברת פרמטרים על פי ערך ועל פי התייחסות (ר' בהמשך).

העברת פרמטרים

עריכה

ברירת המחדל של העברת פרמטרים למתודה היא על ידי העתקה: אובייקט מטיפוס ערך מועתק בעצמו, ועבור אובייקט מטיפוס התייחסות מועתקת ההתייחסות אליו[20]. ניתן לשלוט על כך באמצעות הגדרת הפרמטרים כ-ref או כ-out (הן במתודה והן בקריאה אליה). פרמטר המוגדר כאחד מהם דורש משתנה ממש (או ביטוי שניתן לבצע אליו השמה – lvalue. ולא אובייקט); משתנה זה מועבר בעצמו – ללא העתקה – ופעולות השמה בתוך המתודה מתבצעות ישירות עליו.

  • העברת משתנה כ-ref מחייבת אתחולו לפני הפעלת המתודה, והמתודה רשאית לקרוא ממנו או לכתוב אליו[21].
  • העברת משתנה כ-out מאפשרת לדמות החזרה של מספר ערכים מהמתודה. המתודה איננה רשאית לקרוא מהמשתנה אלא רק לכתוב אליו[22].

ניתן להגדיר מתודה המקבלת מספר משתנה של פרמטרים בעזרת המילה השמורה params[23].

סוגי משתנים

עריכה

ניתן להגדיר קבוע (const) המאותחל בזמן הקישור[24], אשר בקוד ה-MSIL יוחלף לערכו. ניתן להגדיר משתנה קריאה-בלבד (readonly) היכול להיות מאותחל בזמן ריצה באמצעות הבנאי[25], וערכו אינו נשמר בקוד ה-MSIL, בניגוד לקבוע.

טיפוסי משתנים

עריכה

השפה מורכבת מטיפוסי המשתנים המובנים הבאים:

בוליאני (bool), מספר שלם (byte, short, int, long), נקודה צפה (decimal, float, double) ומחרוזת (char, string). בטיפוסי המספרים השלמים ניתן להוסיף קידומת u, בכדי לציין מספר חיובי ובכך להכפיל את גודלו. טיפוס מחרוזת הוא גם מערך של char.

לכל אחד מהטיפוסים יש מבנה תואם, שמכיל קבועים ושיטות סטטיות תואמות לטיפוס, שמקלות על ביצוע חישובים מתמטיים, כך: ל-int למשל יש שיטות כמו חישוב מספר מקסימלי, חישוב לוגריתמי, והאם מספר נתון הוא תוצאה של חזקה שנייה

 int theGreater = int.Max(100, 50);
 int log2 = int.Log2(10);
 bool isPow2 = int.IsPow2(6);

ול-double יש למשל שיטות לחישוב שורשים, שיטות לטריגונומטריה, וקבוע Pi

 double root = double.RootN(9, 2)
 double perimeter = 2 * double.Pi * 50;
 double sin = double.Sin(3);

בנוסף יש לטיפוסים מספריים מחלקה כללית לביצוע חישובים בשם Math, ולטיפוס מחרוזות יש מחלקה בשם string שמכילה שיטות מחרוזתיות שונות.

מערכים

עריכה

כל אחד מסוגי הטיפוסים יכול לשמש כאיבר במערך (array), (המצוין בשפה באמצעות סוגריים מרובעות []). בכדי לבצע פעולות על מערך כמו איפוס, שינוי גודל, העתקה, מיון, חיפוש, מעבר סדרתי ועוד, ניתן להשתמש במחלקת Array. ניתן ליצור בשפה מערך לא רק בממד אחד (וקטור) או שניים (מטריצה) אלא אף בריבוי ממדים. כך למשל ניתן להגדיר מערך תלת-ממדי שמייצג קובייה הונגרית:

 int[,,] array3D = new int[3, 3, 6];

מספר הממדים המקסימלי של מערך הוא 32 ממדים.

מערך משונן (Jagged array)

עריכה

ניתן לממש בשפה מערך משונן שהוא מערך שהאיברים שלו הם מערכים, הוא נקרא לפעמים מערך של מערכים. המערכים יכולים להיות בגודל שונה[26].

להלן הצהרה על מערך חד-ממדי הכולל שלושה איברים, שכל אחד מהם הוא מערך חד-ממדי של מספרים שלמים.

 int[][] jaggedArray = new int[3][];

 jaggedArray[0] = new int[] { 1, 2, 3 };
 jaggedArray[1] = new int[] { 4, 5 };
 jaggedArray[2] = new int[] { 6, 7, 8, 9 };

 // Accessing elements in the jagged array
 int element1 = jaggedArray[0][1]; // Accessing element at index 1 in the first array: 2

ניתן ליצור מערכים משוננים מקוננים, שבו איבר במערך משונן יהיה בעצמו מערך משונן. הגדרה למערך כזה תראה באופן הבא:

int[][][] nestedJaggedArray = new int[2][][];

ניתן לקבץ ביחד למבנה אחד ערכים של טיפוסי משתנים שונים בשימוש באובייקט Tuple. הדבר שימושי במיוחד לפונקציה שמחזירה ערכים מרובים, ומייתר את השימוש בפרמטר מסוג out. הנתונים ב-Tuple הם לקריאה בלבד, ומוגבלים לעד 7 פריטים. דוגמה לשימוש ב-Tuple בכדי להחזיר מספר ערכים מפונקציה GetPerson.

 static Tuple<int, string> GetPerson()
{
 int id = 58694955;
 string name = "John Doe";
 return Tuple.Create(id, name);
}

מבני בקרה

עריכה

בשפה קיימים שני סוגי משפטי התניה: if ו-switch וארבעה משפטי לולאות: שניים סדרתיים: for, foreach, ושניים בעלי התניה while ו-do while. לולאת for היא לולאה סדרתית קלאסית שמבצעת חזרות במספר קבוע, לולאת foreach היא לולאה שמיועדת לעבור על אוסף או מבנה נתונים באופן סדרתי ולייצג את האיבר הנוכחי בחזרתיות. לולאה זו נלקחה משפת VB והיא ידידותית לשימוש באוספים. בלולאות ההתניה אין מספר קבוע של חזרות והן יתקיימו כל זמן שערך ההתניה הוא אמת.

אופרטורים

עריכה

כמו בשפות רבות האופרטורים מתחלקים לקטגוריות שונות:

  • אופרטורים אריתמטיים (+, -, *, /, %, ++, --)
  • אופרטורי השמה (=, +=, -=, *=, /=, %=)
  • אופרטורים השוואתיים (==, !=, <, >, <=, >=)
  • אופרטורים לוגיים (&&, ||, !)
  • אופרטורי סיביות (Bitwise) (&, |, ^, ~, <<, >>)
  • כמו כן קיימים אופרטורים נוספים (., (), [], ?:, is, as, sizeof, new, =>)

תכנות מונחה-עצמים

עריכה

שפת C#‎ היא שפה מונחית-עצמים, התומכת בכימוס (Encapsulation), בירושה ובפולימורפיזם של זמן ריצה.

המחלקה שהיא האובייקט הבסיסי מורכבת ממשתנים שבדרך כלל יהיו פרטיים, מאפיינים שיאפשרו גישה פומבית למשתנים. משיטות שהן הפונקציות המיוחדות לאובייקט, ומאירועים שהאובייקט יכול להזניק לאובייקט שמשתמש בו.

ירושה

עריכה

בשפת #C כל מחלקה יכולה לרשת ישירות ממחלקה אחת בלבד, וישנה אפשרות לממש מספר ממשקים (Interfaces): מחלקות הכוללות מתודות מופשטות בלבד, ומשמשות כחוזים המכריחים את המחלקות היורשות לממש את המתודות המוגדרות בממשק. ב-#C קיימת ירושה ציבורית בלבד; לא ניתן להחיל הגבלות גישה על מחלקות האם.

סוגים שונים של מחלקות:[27]

  • מחלקה מופשטת (abstract) היא מחלקה המשמשת כמחלקת אם בלבד; לא ניתן ליצור עצמים של המחלקה שלא דרך מחלקה יורשת, והיא מגדירה טיפוס משותף, ממשק משותף (אם מוגדרות בה מתודות) ומימוש ברירת מחדל (אם מוגדרות בה מתודות לא-מופשטות או משתנים) עבור מחלקות יורשות.
  • מחלקה חתומה (sealed): מחלקה שלא ניתן לרשת ממנה (מקביל ל-final בג'אווה)[28].
  • מחלקה סטטית: מודול. מחלקה שלא ניתן ליצור מופעים שלה כלל. תפקידה הוא לאגד מתודות סטטיות (שהן למעשה פונקציות) קשורות בינן לבין עצמן[29]. מחלקות סטטיות מאפשרות לכתוב מתודות-הרחבה (ראה בהמשך) עבור מחלקות אחרות.

שיטות (מתודות)

עריכה

שיטה היא בלוק קוד בעלת חתימה מסוימת המכילה סדרת הוראות, שביצועה נעשה באמצעות קריאה לה באמצעות השימוש בשמה. כל פקודה בשפה מבוצעת בהקשר של שיטה המכילה אותה. כל השיטות חייבות להיות מאוגדות במחלקות (class) או מבנים (struct), ובפרט הפונקציה Main, שהיא נקודת הכניסה (ההתחלה) של התוכנית. הצורות האפשריות לכתיבת שיטה מתוארות באופן הבא (כאשר אפשרויות אופציונליות נמצאות בסוגריים מרובעים):

[public]/[private] [async] [static] void/typeName FunctionName([type argName])
{
 return argName;
}

שיטה שמחשבת חזקה ריבועית יכולה להכתב באופן הבא:

int PowerSquared(int i)
{
 return i * i;
}

כמו בשפת ++C‎ שיטה שלא מחזירה ערך מסומנת באמצעות הקידומת void במקום טיפוס משתנה. החזרת הערך והיציאה מהשיטה נעשית באמצעות ההוראה return.

בשפת #C‎ לא ניתן לכתוב פונקציות גלובליות או להגדיר משתנים גלובליים כמו בשפת ++C‎ וויז'ואל בייסיק. התחליף לפונקציה גלובלית הוא מתודה סטטית (static), המופעלת דרך שם המחלקה שלה בלי הצורך ליצור מופע. כתחליף למשתנה גלובלי ניתן להגדיר משתנה סטטי במחלקה. מתודה סטטית איננה יכולה לגשת למשתנים רגילים ללא התייחסות לאובייקט ספציפי, אך יכולה לגשת למשתנים סטטיים, שאותם ניתן לאתחל באמצעות בנאי סטטי. ניתן להגדיר מחלקה כסטטית, ובמחלקה כזאת ניתן להגדיר רק מתודות סטטיות. לדוגמה, המחלקה Console היא סטטית, וכך גם המתודות הכלולות בה כגון WriteLine. סוג מיוחד של מתודות סטטיות הן מתודות-הרחבה (extension methods)[30].

במחלקות מופשטות ובממשקים ניתן להגדיר "מתודה מופשטת": חתימה של מתודה ללא מימוש, שתמומש על ידי מחלקה יורשת.

ניתן להגדיר מתודה כמדומה (virtual) ולאפשר שינוי של המתודה במחלקה היורשת באמצעות המילה override[31]. מתודה מדומה היא מתודה עבורה קיים מימוש, אך מימוש זה עשוי להיות מוחלף (להידרס) במחלקה יורשת.

ניתן למנוע דריסה של מתודה מדומה במחלקה יורשת על ידי הגדרתה כמתודה חתומה (sealed) במחלקת הבסיס[28].

  1. C‎ מאפשרת העמסת מתודות, שהיא האפשרות לכתוב מתודות בעלות שם זהה, וחתימה שונה (רשימת הפרמטרים צריכה להיות שונה בטיפוסי הפרמטרים או במספרם). כמו כן ניתן ליצור מתודה עם מספר לא ידוע של פרמטרים (באמצעות מילת המפתח params והגדרת מערך ברשימת הפרמטרים. (לדוגמה: (public void FunctionName(params String[] str1). גם ניתן ליצור ערכי ברירת מחדל בפרמטרים של מתודה באופן דומה לכתיבה ב-#C‎.

למחלקה בשפה ניתן לכתוב בנאי (Constructor) המופעל כאשר נוצר מופע, והורס (Destructor) המופעל לפני מחיקת המופע. ההורס קורא בעקיפין למתודה מסיימת (Finalize). כברירת מחדל נוצרים בנאי והורס ריקים. ניתן ליצור מספר רב של בנאים באמצעות העמסה. ייעודה של מתודת ה-Finalize הוא שחרור משאבים שהם Unmanaged – כלומר משאבים שאינם מנוהלים על ידי ה-CLR. למתודת ה-Finalize עלולה להיות השפעה גדולה על ביצועי התוכנה מאחר שהיא נקראת בתהליך איסוף הזבל אשר משהה עד לסיומו את התהליכונים המנוהלים על ידי ה-CLR.

ניתן ליצור מתודה אסינכרונית עם הקידומת async שההוראות שבה יתבצעו באופן אסינכרוני כמו למשל באמצעות Task.

הסתרה וחלוקה למודולים

עריכה

יחידת ההסתרה ב-#C היא המחלקה (ולא האובייקט). ישנן שש רמות הרשאה לגישה לשדות המחלקה:[32]

  1. private: (פרטי) מותרת גישה אך ורק מתוך המחלקה הנוכחית.
  2. public: (ציבורי) מותרת גישה מכל מקום.
  3. internal: (פנימי) מותרת גישה מכל מקום בתוך המכלול (assembly) של המחלקה, שהוא הקובץ בשפת-הביניים אותו מריצה סביבת הדוט נט לאחר שהתוכנית עוברת הידור.
  4. protected: (מוגן) מותרת גישה מתוך המחלקה הנוכחית ומתוך מחלקות יורשות, גם אם הן במכלול אחר.
  5. protected internal: (פנימי מוגן) מותרת גישה מתוך המכלול הנוכחי ומתוך מחלקות יורשות, גם אם הן במכלול אחר.
  6. private protected: (מוגן פרטי) מותרת גישה מתוך המחלקה הנוכחית ומתוך מחלקות יורשות במכלול הנוכחי. זמין החל מגרסה 7.2.

ניתן לפצל קוד במחלקה חלקית (partial), שהיא מחלקה אחת שמפוצלת למספר מקומות (ואף במספר קבצים). דוגמה רווחת לכך הוא פיצול הגדרת פקדי טופס WinForm שנוצרים באמצעות המחולל הגרפי לקובץ designer נפרד, כדי לשמור על קובץ cs נקי לכתיבת הקוד עצמו.

ניתן ליצור מחלקות מקוננות. מופעים של מחלקה מקוננת נוצרים באופן בלתי תלוי במחלקת האם (כלומר, אין מחלקות מקוננות לא סטטיות, בניגוד למצב בשפת ג'אווה).

מאפיינים (Properties)

עריכה

מאפיין הוא שיטה מיוחדת הנקראת Accessor (מאפשר גישה) שמתנהגת מבחינה תחבירית כמשתנה חבר ותפקידה לאפשר גישה לשדות פרטיים.

בדומה למוסכמה השכיחה בג'אווה לכנות מתודות שתפקידן השמה ואחזור של ערכים get ו-set בהתאמה ולאחריהן שם התכונה. אומץ רעיון ה"מאפיינים" מ-Visual Basic כדי להקל על השמה ואחזור שדות פרטיים. בתפיסה זו מאפיין מורכב משדה ושיטות הפועלות עליו: האחת קוראת (getter) והאחרת כותבת (setter). כל שנדרש להגדיר הוא הגבלת ההרשאה (Access Modifier) על פי רעיון הכימוס, לקבוע מה יהיה הערך המוחזר ולקבוע את שם המאפיין. מימוש המאפיין מתנהג כפונקציה חברה לכל דבר, כך ניתן לשלוט בערכים המושמים והמאוחזרים בזמן הריצה. בכל פעם שתתבצע גישה למאפיין היא תופנה ל-get וכל פעם שיוצב ערך במאפיין תתבצע הפעלה של set. כמו כן, החל מגרסה 2.0, ניתן לקבוע רמות הרשאה שונות ל-get ול-set (כך, לדוגמה, ניתן לקבוע הרשאת גישה ציבורית לאחזור get ולהגביל את השמת הערך set ל"פנימי מוגן" כך רק מחלקות יורשות יוכלו לשנות את השדה. או להסיר את ההשמה לחלוטין, במקרה כזה יקרא המאפיין "קריאה בלבד"). מעבר לעובדה שהמאפיינים מהווים רמת הפשטה למשתמש, הם הופכים את השדה לשדה חכם (smart field) שמאפשר לבצע פעולות נוספות בעת השמה או אחזור, כמו בדיקות תקינות או חריגה או לבצע אתחול עצל, שהוא אתחול בזמן שימוש בשדה ולא בזמן יצירת המחלקה, וכן פעולות לוגיות אחרות.

class Student
{
 int _age;
 public int Age
 {
 get { return _age; }
 set { _age = value; }
 }
}

בדוגמה מחלקת Student שבה מאפיין ציבורי בשם Age שכומס משתנה פרטי בשם _age.

 Student student = new Student();
 student.Age = 26;
 Console.Write(student.Age);

באופן שקול, ניתן גם להגדיר את ערכו של מאפיין ציבורי עם יצירת האובייקט (מגרסה 3 ומעלה):

 Student student = new Student() {
 Age = 26,
 };
 Console.Write(student.Age);

והמימוש במחלקה אחרת יראה באופן הבא: ההשמה תשתמש במתודה set, והתצוגה תשתמש במתודה get של המאפיין. בגרסה 3 ומעלה נוספו מספר יכולות חדשות ביניהן היכולת ליצור "מאפיינים אוטומטיים"[33] – אם יש צורך בגישה לחבר פרטי ללא לוגיקה כלשהי (וגם להשאיר מרווח תמרון להוסיף לוגיקה כזו בעתיד), ניתן ליישמו בדרך ישירה וחסכונית כמו בדוגמה הבאה. במקרה זה המהדר מייצר מאחורי הקלעים שדה מגבה שהגישה אליו אפשרית רק דרך המאפיינים, כאילו נכתב הקוד לעיל:

public int Age { get; set; }

למאפיינים ב-#C תפקיד בתכנות חזותי. כל המאפיינים של כל פקד, מוצגים בזמן הפיתוח באמצעות גיליון המאפיינים ואף ניתן לשנות את תכונותיהם, באמצעות הממשק הגרפי, כך השינוי ישתקף בטופס באופן מידי כבר בזמן הפיתוח, מבלי שהמתכנת צריך לעשות שימוש בקוד.

אחד החסרונות של מאפיינים הוא ההסתרה (מבחינה תחבירית) של פעולת חישוב. פעולת גישה למשתנה רגיל היא זולה מאוד, מבחינת זמן חישוב, אך פעולת גישה למאפיין עשויה לדרוש חישוב בלתי מוגבל בזיכרון או בזמן; השפה לא מונעת יצירה של לולאה אינסופית בתוך מאפיין. מהסיבה הזאת אין זה מקובל להגדיר מאפיינים המבצעים חישובים מורכבים.

העמסת אופרטורים

עריכה

C#‎ מאפשרת העמסת אופרטורים – שהיא טכניקה המאפשרת לתת משמעות חדשה לרוב האופרטורים הקיימים בשפה[34], על מנת להקל על הקריאות של התוכנית. את האופרטור "[ ]" מעמיסים בצורה מעט שונה מהאחרים, בעזרת מנגנון הנקרא סדרן – Indexer הדומה בכתיבתו למאפיין:

private int arr = new int[100];
public int this[int i]
{
 get { return arr[i]; }
 set { arr[i] = value; }
}

רשומות (Records)

עריכה

החל מגרסה 9 של השפה, נוספה תמיכה ברשומות. רשומה היא טיפוס ייחוס שדומה למחלקה, שבה אין שיטות אלא רק מאפיינים פומביים – ואת אלה ניתן להגדיר אך ורק בזמן היצירה של הרשומה.

את הרשומה מגדירים באופן הבא:

public record Student(string Name, int Age);

כדי ליצור רשומה פשוט משתמשים ב-new, בדומה למחלקה:

Student stu1 = new Student("First Student", 22);
Student stu2 = new Student("Second Student", 24);

השוואה בין שתי רשומות נעשית באמצעות האופרטור == או באמצעות השיטה Equals, והשוואה זו, בניגוד לטיפוסי ייחוס אחרים (כגון מחלקות), משווה את הערכים ברשומה ולא את מיקום המצביע. לכן, אם ניצור שתי רשומות שונות עם פרמטרים זהים, ונשווה ביניהן, תוצאת ההשוואה תהיה true.

האירוע בשפה הוא למעשה מאפיין נוסף של המחלקה, ונחשב לחלק מהעצם כמו השדה והשיטה. event (אירוע) הוא טיפוס Delegate המובנה בשפה בעל הרשאות גישה מיוחדות: ניתן לרשום לאירוע פונקציות גם מחוץ למחלקה בה הוא מוגדר, אך הקריאה אליהן מתבצעת רק מתוך המחלקה. ה-Delegate עצמו הוא מחלקה שמכילה רשימה של הפניות לפונקציות, שניתן באמצעותה להזניק פונקציות ממקומות שונים[35].

ה-event משמש בשפת #C לתמיכה ישירה בתכנות מונחה אירועים. כדי שיהיה אפשר לתקשר ולהעביר הודעות ומידע בין מחלקות שונות, (בעקבות שפת וויז'ואל בייסיק). במחלקה המתארחת ניתן ליצור אירועים בחותמות שונות, ולהזניק אותם במקום הרלוונטי במחלקה באמצעות קריאה בשמם, ובמחלקה המארחת ניתן ללכוד אירועים אלו, באמצעות רישום מוקדם של פונקציה מקומית (בעלת חתימה זהה) לאירוע של האובייקט לאחר ההכרזה עליו. התוצאה היא שבעת שאירוע מוזנק במחלקה המתארחת, הוא גורם למעשה להרצת הפונקציה במחלקה המארחת.

בדוגמה מוגדר event מטיפוס MultiplicationEventHandler במחלקה CalculationEvent, לאחר מכן מתבצע במחלקה המארחת HostCalculationEvent יצירת מופע של המחלקה ורישום האירוע MultiplicationEventHandler לפונקציה testCalculation_MultiplicationNum, כך שבעת הזנקה שלו תופעל פונקציה זו.

במחלקה המתארחת: CalculationEvent

class CalculationEvent
{
 public delegate int MultiplicationEventHandler(int x, int y);
 public event MultiplicationEventHandler MultiplicationNum;

 public int RaiseCalc(int x, int y)
 {
 return MultiplicationNum(x, y);
 }
}

במחלקה המארחת: HostCalculationEvent

class HostCalculationEvent
{
 CalculationEvent testCalculation = new CalculationEvent();

 public HostCalculationEvent()
 {
 testCalculation.MultiplicationNum += new CalculationEvent.MultiplicationEventHandler(testCalculation_MultiplicationNum);
 }

 int testCalculation_MultiplicationNum(int x, int y)
 {
 return x * y;
 }
}

תכנות פונקציונלי

עריכה

נציג (Delegate) הוא טיפוס הכומס פונקציה, בדומה למצביע פונקציה בשפות C ו-++C, אך בשונה מהם הוא מונחה עצמים, בעל סוג בטוח ומאובטח. כל מופע שלו מחזיק רשימת עצמים ברי-קריאה המתאימים לחתימה זאת[36]. קריאה לנציג יוזמת קריאה של כל השיטות המוחזקים בו. ניתן לרשום (Subscribe) פונקציות לנציג בעזרת אופרטור =+, ולקרוא להן בעזרת תחביר רגיל של קריאה לפונקציה.

דוגמה לתוכנית "שלום עולם" בעזרת נציגים:

class HelloWorld
{
 public delegate void Del();
 static void Main(string[] args) {
 Del x = delegate { Console.Write("hello "); };
 x += delegate { Console.WriteLine("world"); };
 x();
 }
}

טיפוס בסיסי זה מאפשר להשתמש בטכניקות של תכנות פונקציונלי. למשל הוא מאפשר לקרוא לפונקציה לא מוכרת במחלקה, ליצור אירועים, ולהפעיל תהליכון (Thread)[37].

ישנם מספר מימושים גנריים ל-Delegates בספריה של #C:

  • Action: פונקציה המקבלת פרמטרים אך לא מחזירה דבר[38].
  • Func: פונקציה המקבלת פרמטרים כלשהם ומחזירה טיפוס כלשהו.
  • Predicate: פונקציה המקבלת פרמטר אחד ומחזירה טיפוס בוליאני.
  • Comparison: פונקציה המקבלת שני פרמטרים בני אותו טיפוס, ומחזירה ערך מספרי. נועדת להשוואה בין אובייקטים[39].

דוגמאות:

Action<> printHello = delegate { Console.Write("hello world!"); };

Predicate<int> range255 = delegate(int x) {return x>=0 && x<=255;};

Func<string, string> convert = delegate(string s) {return s.ToUpper();};

פונקציות אנונימיות וביטויי למדא

עריכה

החל מגרסה 2 שפת #C תומכת במתודות אנונימיות – פונקציה הנכתבת בשורת פקודה, ומוגדרת בזמן ההרצה עצמה[40]. החל מגרסה 3 נוספה תמיכה בביטויי למדא לשפה. זוהי דרך נוספת תמציתית וגמישה לכתוב מתודה אנונימית[14]. שימוש בפונקציות אנונימיות נעשה כאשר רוצים להשתמש בפונקציה באופן מקומי מבלי להגדירה במחלקה, או כאשר פרמטר של פונקציה מוגדר כפונקציה, ואז ניתן לכתוב את הפונקציה האנונימית בקריאה לפונקציה, מבלי להגדירה כלל כפונקציה במחלקה.

בדוגמה מימוש של הפונקציה שלעיל באמצעות ביטוי למדא (האופרטור <= מפעיל הלמדא, מפריד בין הפרמטרים של הקלט בצידו השמאלי לגוף הלמדא מצידו הימני).

Func<string, string> Convert = x => x.ToUpper();

בביטויי למדא נעשה שימוש רב ב-LINQ. בדוגמה איתור גודל המילה הקטנה ביותר במערך מחרוזתי מתקבל במשתנה shortestWordLength.

string[] wordsArray = {"Orange", "Apple", "Pineapple", "Pear", "Peach"};
int shortestWordLength = wordsArray.Min(w => w.Length);

תכנות גנרי

עריכה

השפה תומכת בפולימורפיזם פרמטרי: ניתן להגדיר מחלקה המקבלת טיפוסים כפרמטרים:

public class GenericList<T> {
 void Add(T input) { }
}

ניתן להעביר כפרמטר כל טיפוס – פרימיטיבי, טיפוס-ערך או טיפוס-התייחסות:

class TestGenericList {
 private class ExampleClass { }
 static void Main() {
 // Declare a list of type int.
 GenericList<int> list1 = new GenericList<int>();

 // Declare a list of type string.
 GenericList<string> list2 = new GenericList<string>();

 // Declare a list of type ExampleClass.
 GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
 }
}

ניתן להגדיר אילוצים של טיפוסים באמצעות משפט where, כך למשל ניתן להגדיר שמחלקה מסוימת תהיה רק מטיפוס מחלקה, מבנה או טיפוס קונקרטי. וניתן לתת ערכי ברירת מחדל למשתנים הגנריים באמצעות המילה default:

public class GenericWithWhere <T> where T : IComperable {}

לדוגמה מבטיח כי הטיפוס הפרמטרי יהיה "ניתן להשוואה", בכך שמאלצים אותו לרשת מהממשק המתאים.

מנגנון הגנריות ב-#C איננו מתבצע בעזרת תבניות בזמן הידור (כמו ב-++C) ואיננו נעלם בזמן ריצה (כמו בג'אווה) אלא מתבצע בזמן טעינה, וכל אובייקט שומר בתוכו מידע על הפרמטרים בעזרתם הוא נוצר גם בזמן ריצה, על מנת לאפשר שיקוף ובטיחות טיפוסים.

ניתן להעביר רק טיפוסים (ולא קבועים מספריים, למשל, כמו ב-++C). מטיפוסים אלה ניתן לדרוש דברים מסוימים, כגון ירושה ממחלקה או ממשק מסוים, או קיומו של בנאי נטול פרמטרים.

לא ניתן לבצע התמחות של מחלקה גנרית עבור טיפוס ספציפי. כתוצאה מכך, כוח החישוב של מנגנון הגנריות ב-#C חלש יותר משל התבניות בשפת ++C, והוא איננו מאפשר להגדיר חישובים מורכבים שיתבצעו בזמן הידור או טעינה – הוא איננו שלם-טיורינג; כל תפקידו הוא יצירה של טיפוסים, משתנים, נציגים ופונקציות בעזרת פרמטרים (במילים אחרות: הוא מאפשר תכנות גנרי, אך לא מטא-תכנות). חולשה זו מאפשרת להדר ולטעון קוד גנרי מהר יותר.

תכנות דינמי

עריכה

על אף שהשפה היא במהותה שפה סטטית, החל מגרסה 4 נוספה לה תמיכה בתכנות דינמי, דהיינו הגדרת האובייקטים כמו משתנים, מאפיינים, שיטות ואירועים בזמן ריצה, ולא בזמן ההידור. לצורך השימוש בתכנות דינמי נוסף טיפוס חדש מסוג dynamic.

בדוגמה הבאה ניתן לראות שטיפוס המשתנה d1 משתנה בעקבות ההשמה של התוכן, ממחרוזת למספר שלם.

 dynamic d1 = "STRING";
 Console.WriteLine(d1.GetType());
 // Prints "System.String".
 d1 = 60;
 Console.WriteLine(d1.GetType());
 // Prints "System.Int32".

ונוספו מחלקות חדשות לטיפול דינמי (באמצעות מרחב System.Dynamic). המחלקה DynamicObject שבאמצעותה ניתן להוסיף ולשנות מאפיינים תוך כדי ריצה, ובאמצעות המחלקה ExpandoObject שנותנת טיפול כולל יותר, ניתן להוסיף למחלקה בזמן ריצה כל אובייקט, מאפיין, שיטה ואף אירוע. מחלקה זו תומכת ב-LINQ ובתחשיב למדא. דרך נוספת לתכנות דינמי היא באמצעות יכולת השיקוף של ה-#C (אם כי דרך זו מורכבת יותר למימוש).

בדוגמה הבאה נוצרת באמצעות ExpandoObject מחלקה בזמן ריצה שיש לה שני מאפיינים (בשמות Name מסוג מחרוזת ו-Age מסוג מספרי) ושיטה אחת (בשם Increment) שמוסיפה 5 לערך שקיים במאפיין Age. בסוף הדוגמה יוצג בחלון Output התוכן הבא: Name: John Smith, Age: 20

 using System.Dynamic;
 ...
 dynamic employee = new ExpandoObject() ;
 employee.Name = "John Smith";
 employee.Age = 15;

 employee.Increment = (Action)(() => { employee.Age+=5; });
 employee.Increment();

 Console.WriteLine("Name: " + employee.Name + " " + ", Age: " + employee.Age);

התמיכה מאחורי הקלעים בתכנות הדינמי נעשית באמצעות מהדר אחר ה-DLR ולא ה-CLR. בשל כך באובייקטים דינמיים לא ניתן להשתמש בתכונת השיקוף ולא ב-Intellisense.

תכנות חזותי

עריכה
 
תמונת מסך של כלי פיתוח לתכנות חזותי ב-WinForm ב-C#, טופס, פקדים, רכיבים, חלונית Toolbox, חלונית מאפיינים, חלונית Document outline.

השפה תומכת בתכנות חזותי בעיקר בממשקי משתמש, באמצעות ספריות תקניות של הדוט נט וכלי פיתוח של הסטודיו, כאשר אלמנטים בשפה כאירועים ומאפיינים משתלבים בפיתוח החזותי ומקלים עליו. ניתן ליצור טפסים (Form) ליישומים שולחניים באמצעות ה-WinForms, דפים (Page) ליישומי אינטרנט באמצעות ה-WebApplication, חלונות (Window) ליישומי גרפיקה מתקדמים באמצעות מודל התכנות WPF, ומסכים (Screen) ביישומי משחקים.

העיקרון שנלקח מוויז'ואל בייסיק, הורחב והועצם, הוא יצירת אובייקט ראשי המסמל טופס, דף או חלון בהתאם לסוג היישום, שיכול להכיל בתוכו פקדים שהם מודלים תכנותיים קטנים, המיוצגים באמצעות צלמיות, שיכולים לתקשר זה עם זה. על גבי הטופס ניתן ל"צייר" ולמקם פקדים באמצעות גרירתם מחלונית "ארגז כלים" (ToolBox), וניתן לראות את צורת הסידור שלהם באמצעות חלונית "מתאר מסמך" (Document outline/Designer). ישנה חלוקה פנימית בין פקדים (Controls) (שהם רכיבי תוכנה חזותיים כמו פקד תיבת טקסט ופקד תמונה) לבין רכיבים (Components) (שהם רכיבי תוכנה לוגיים, למשל רכיב FileSystemWatcher שעוקב אחר שינויים בקבצים, או רכיב BackgroundWorker שמאפשר ליצור תהליכון נפרד עם אירועים). הקשר בין פעולות הממשק לקוד הוא דו כיווני: לכל פעולה חזותית מחולל קוד מתאים, למשל גרירה של פקד אל הטופס יוצרת הכרזה שלו בטופס ואתחול של מאפיינים מסוימים שנקבעו מראש, כמיקום הפקד בטופס, וגודלו, וכל שינוי בקוד מתורגם לייצוגים החזותיים בחזרה.

ב-WebApplication קיימים כ-90 פקדים ורכיבים שונים, וב-WinForms קיימים כ-70 פקדים ורכיבים שונים. ניתן ליצור פקדי ורכיבי משתמש מותאמים אישית על בסיס פקדים ורכיבים קיימים, או פקדים ורכיבים חדשים. הפקדים מסווגים בדרך כלל לכמה קטגוריות עיקריות. ב-WinForms למשל הם מסווגים לשיתופיים, מכולות (פקדים שמכילים פקדים אחרים), תפריטים, נתונים (לעבודה עם בסיסי נתונים), תיבות שיח ועוד. דרך העבודה האופיינית היא גישה אל הקוד מתוך הממשק והליכה מהכלל אל הפרט. תחילה יש ליצור את הטופס, לשרטט עליו את הפקדים, ולאחר מכן להגדיר את מאפייניהם השונים, והאירועים הדרושים ליישום באמצעות חלונית "מאפיינים" (Properties) שמציגה את המאפיינים והאירועים הקיימים בפקד, ואז ליצור אירוע באזור הקוד, ולכתוב בו את הקוד הנדרש כדי לממש את התוכנית.

השפה גם תומכת, באמצעות Xamarin.Forms, בתכנות אפליקציות חוצה פלטפורמות עבור טלפונים ניידים ומכשירי Windows (עם פלטפורמת UWP).

זיכרון מנוהל

עריכה

במטרה למנוע בעיות הנובעות מניהול עצמאי של זיכרון כמו זליגת זיכרון ודריסת זיכרון, שפת C#‎, כחלק מטכנולוגיית דוט נט, מוגדרת כ"קוד מנוהל" (Managed Code). המתכנת פטור מן האחריות לשחרור מפורש של זיכרון המוקצה לאובייקט. סביבת ההרצה (CLR – Common Language Runtime) היא המופקדת על שחרור הזיכרון שתופסים עצמים שאינם בשימוש יותר.

לצורך כך, יוזמת סביבת ההרצה אחת לזמן מה תהליך של "איסוף זבל" (Garbage collection), הממפה את כל העצמים במערכת ומסמן למחיקה את אלו שאינם בשימוש. לצורך המיפוי מוגדרים מספר רכיבים ראשיים ("שורשים"). עצם הוא בשימוש אם הוא מוחזק על ידי אחד הרכיבים הראשיים, או שניתן להגיע אליו מאחד הרכיבים הראשיים דרך סדרה של עצמים אחרים. עצמים שאינם בשימוש מסומנים למחיקה. אם הוגדר עליהם קוד הרצה לפני מחיקה (Finalizer) הוא יבוצע. ולאחר מכן העצם ימחק והזיכרון אותו תפס ישוחרר.

זיכרון מנוהל אמור להבטיח ניצול של משאבי הזיכרון, אך הוא אינו מגן על משאבים אחרים כדוגמת גישה לבסיסי מידע, קבצים פתוחים וכדומה. בנוסף, זליגת זיכרון עדיין עלולה לקרות אם כתוצאה משגיאה בתוכנית ניתן לגשת אל עצמים גם כאשר אין בהם עוד צורך. מנגנון איסוף הזבל לא יכול לדעת שמדובר בשגיאה, ולא יפנה את העצמים מן הזיכרון.

טיפול בחריגות

עריכה

C#‎ תומכת בטיפול בחריגות (מנגנון בקרת זרימה המיועד לציין ולטפל במצבים לא תקינים במהלך הריצה של התוכנית) על ידי "זריקת" אובייקטים במעלה שרשרת הקריאות לפונקציה, כדי להעביר מידע על החריגה. אפשר לזרוק רק אובייקטים ממחלקות היורשות מהמחלקה Exception. אין דרך להצהיר על החריגות הנזרקות ממתודה, חריגה שנזרקת ולא נתפסת תפעפע עד סביבת הריצה עצמה.

באופן דומה לשפות אחרות, הטיפול בחריגות נעשה על ידי השימוש ב-try-catch-finally, כאשר ניתן לספק טיפוס מסוים ל-catch ולקבל מידע אודות השגיאה:

try
{
 ... // code
}
catch (ExceptionType e)
{
 // Here exception can be handeled
}
finally
{
 // this will be called anyway
}

תאימות לאחור

עריכה

כדי להקל על מפתחים להגר משפות אחרות[דרוש מקור], ובפרט משפת C++‎, אל שפת C#‎, נכללו בשפה "כינויים" למספר מחלקות ומבנים, הזהים לשמות המקבילים בשפת C++‎:

מחלקה כינוי תיאור גודל בבתים אתחול אוטומטי
System.Byte byte בית 1 0
System.Int16 short שלם קצר 2 0
System.Int32 int שלם 4 0
System.Int64 long שלם ארוך 8 0
System.Decimal decimal שלם דצימלי 12 0
System.Single float ממשי 4 0.0
System.Double double ממשי כפול 8 0.0
System.Boolean bool בוליאני 1 false
System.Char char תו יוניקוד בודד 2 /0
System.String string מחרוזת יוניקוד null

בנוסף, קיימים כינויים למחלקות שאינן קיימות כסוגי משתנים בשפת C++‎: הכינוי object למחלקה Object, וכן הקידומת unsigned משפת C++‎ תהיה בדרך כלל הקידומת u לכינוי המתאים (למעט byte, שהוא כבר unsigned, ולכן קיים הכינוי sbyte, שהוא byte עם סימן). למשל, unsigned int בשפת C++‎ מקביל ל-uint בשפת C#‎, שאינו אלא כינוי למבנה UInt32.

המבנה Char בשפת C#‎ מכיל נתון באורך 16 סיביות או יותר (קידוד UTF-16), כדי שיוכל להכיל תו יוניקוד, בניגוד ל-char של C++‎ שהוא על פי רוב באורך 8 סיביות (מתאים להכיל תו ASCII, או בית בודד).

ניתן להשתמש בכינוי של מחלקה או בשם המפורש שלה, ללא כל משמעות מבחינת ריצת התוכנית.

גרסאות

עריכה

ל-C#‎ פורסמו מספר מהדורות, בדרך כלל בצמוד להפצת מהדורה חדשה של ויז'ואל סטודיו:

גרסה תאריך ‎גרסת NET. ויז'ואל סטודיו
C# 1.0 ינואר 2002 ‎.NET Framework 1.0 Visual Studio .NET 2002
C# 1.2 אפריל 2003 ‎.NET Framework 1.1 Visual Studio .NET 2003
C# 2.0 נובמבר 2005 ‎.NET Framework 2.0 Visual Studio 2005
C# 3.0 נובמבר 2007 ‎.NET Framework 3.0
‎.NET Framework 3.5
Visual Studio 2008
Visual Studio 2010
C# 4.0 אפריל 2010 ‎.NET Framework 4 Visual Studio 2010
C# 5.0 אוגוסט 2012 ‎.NET Framework 4.5 Visual Studio 2012
C# 6.0 יולי 2015 ‎.NET Framework 4.6 Visual Studio 2015
C# 7.0 מרץ 2016 ‎.NET Framework 4.6.2 Visual Studio 2017
C# 7.1 אוגוסט 2017 ‎.NET Core 2.0
C# 7.2 נובמבר 2017
C# 7.3 מאי 2018 ‎.NET Core 2.1

‎.NET Core 2.2

‎.NET Framework 4.8

C# 8.0 ספטמבר 2019 ‎.NET Core 3.0

‎.NET Core 3.1

Visual Studio 2019
C# 9.0 ספטמבר 2020 ‎.NET 5
תקציר הגרסאות
C# 2.0 C# 3.0 C# 4.0 C# 5.0 C# 6.0
תכונות
שהוספו
  • ג'נריקס
  • טיפוסים חלקיים (Partial)
  • מחלקה סטטית
  • מתודות אנונימיות
  • איטרטורים
  • טיפוסים מאפשרים ערכי NULL
  • הגדרת מאפיין (setters) כפרטי
  • Method group conversions (delegates)
  • טיפוס מרומז של משתנים מקומיים (var)
  • מאתחל אובייקטים ואוספים
  • מימוש אוטומטי של מאפיינים
  • טיפוסים אנונימיים
  • מתודות הרחבה
  • ביטויי שאילתה
  • ביטויי למדא
  • עצי ביטויי
  • מתודות חלקיות
  • LINQ
  • כריכה דינמית
  • טיפוס דינמי (Dynamic)
  • ארגומנטים אופציונליים
  • Generic co - and contravariance
  • Embedded interop types ("NoPIA")
  • חוזה - Code Contracts
  • מתודות אסינכרוניות (Async / await)
  • Caller info attributes
  • הידור כשירות (service)
  • יבוא של טיפוסים סטטיים למרחב שם
  • מסנן לחריגות
  • אתחול אוטומטי של מאפיין לקריאה בלבד
  • אתחול של מילון
  • Await בבלוק של catch/finally
  • אינטרפולציה על מחרוזת
  • אופרטור nameof

דוגמאות

עריכה

תוכנית Hello world

עריכה

להלן דוגמה לתוכנית Hello world בשפה זו[41]:

using System;

public class ExampleClass
{
 public static void Main(string[] args)
 {
 Console.WriteLine("Hello world!");
 }
}

פונקציה לחישוב עצרת

עריכה
public static int CalculateFactorial(int number)
{
 int factorial = 1;
 for (int i = 1; i <= number; i++)
 {
 factorial *= i;
 }
 return factorial;
}

ראו גם

עריכה

לקריאה נוספת

עריכה
  • כריסטוף וייל, היכרות עם C#‎, בהוצאת SAMS והוד עמי, ישראל 2001, 189 עמ'
  • טום ארצ'ר, C#‎ למתכנתי Java/C++/Visual C++, הוצאת מיקרוסופט והוד עמי, ישראל 2002, 411 עמ'
  • דאנקן מקניז, וקנט שארקי, C#‎ - סדנת לימוד, SAMS והוצאת הוד עמי, ישראל 2002, 800 עמ'
  • על כוס קפה, מדריך לשפת C#‎ ולמערכת .NET, עיטם מדעי המחשב, 2011

קישורים חיצוניים

עריכה

הערות שוליים

עריכה
  1. ^ BillWagner, What's new in C# 13, learn.microsoft.com, ‏2024-05-21 (באנגלית אמריקאית)
  2. ^ CNN.com - Microsoft dropping Java code from Windows XP - July 20, 2001, edition.cnn.com
  3. ^ Kovacs, James (7 בספטמבר 2007). "C#/.NET History Lesson". נבדק ב-18 ביוני 2009. {{cite web}}: (עזרה)
  4. ^ 1 2 3 4 5 6 erikdietrich, The history of C#, learn.microsoft.com, ‏2024-03-07 (באנגלית אמריקאית)
  5. ^ C# 9.0 on the record, .NET Blog, ‏2020-11-10 (באנגלית אמריקאית)
  6. ^ C# and Java: Comparing Programming Languages, Msdn
  7. ^ News, CNET (באנגלית)
  8. ^ "C# משלבת את היצרנות הגבוהה של Microsoft Visual Basic עם העוצמה הגלומה ב-C++" סקוט ויילטמוף (חבר בצוות הפיתוח של השפה) מצוטט ב-C# למתכנתי Java/C++/Visual C++, הוצאת מיקרוסופט והוד עמי, ישראל 2002, עמ' 14
  9. ^ Serialization (C# and Visual Basic), learn.microsoft.com, ‏2017-03-27 (באנגלית אמריקאית)
  10. ^ BillWagner, C# preprocessor directives, learn.microsoft.com, ‏2023-02-01 (באנגלית אמריקאית)
  11. ^ BillWagner, Built-in reference types - C# reference, learn.microsoft.com, ‏2023-02-25 (באנגלית אמריקאית)
  12. ^ BillWagner, unsafe keyword - C# reference, learn.microsoft.com, ‏2024-03-30 (באנגלית אמריקאית)
  13. ^ BillWagner, Declaration statements - local variables and constants, var, local reference variables (ref locals) - C# reference, learn.microsoft.com, ‏2023-06-21 (באנגלית אמריקאית)
  14. ^ 1 2 http://msdn.microsoft.com/en-us/library/vstudio/bb397687.aspx
  15. ^ BillWagner, Classes, structs, and records - C#, learn.microsoft.com, ‏2022-09-21 (באנגלית אמריקאית)
  16. ^ BillWagner, Value types - C# reference, learn.microsoft.com, ‏2022-09-29 (באנגלית אמריקאית)
  17. ^ Archiveddocs, Boxing and Unboxing (C# Programming Guide), learn.microsoft.com, ‏2013-11-24 (באנגלית אמריקאית)
  18. ^ BillWagner, Reference types - C# reference, learn.microsoft.com, ‏2024-03-30 (באנגלית אמריקאית)
  19. ^ "In general, classes are used to model more complex behavior, or data that is intended to be modified after a class object is created. Structs are best suited for small data structures that contain primarily data that is not intended to be modified after the struct is created.", [מקור http://msdn.microsoft.com/en-us/library/vstudio/ms173109.aspx]
  20. ^ BillWagner, Method Parameters - C# reference, learn.microsoft.com, ‏2024-05-21 (באנגלית אמריקאית)
  21. ^ BillWagner, ref keyword - C# reference, learn.microsoft.com, ‏2024-05-24 (באנגלית אמריקאית)
  22. ^ BillWagner, Method Parameters - C# reference, learn.microsoft.com, ‏2024-05-21 (באנגלית אמריקאית)
  23. ^ BillWagner, Method Parameters - C# reference, learn.microsoft.com, ‏2024-05-21 (באנגלית אמריקאית)
  24. ^ http://msdn.microsoft.com/en-us/library/vstudio/e6w8fe1b.aspx
  25. ^ http://msdn.microsoft.com/en-us/library/vstudio/acdd6hb7.aspx
  26. ^ BillWagner, Jagged Arrays - C# Programming Guide, learn.microsoft.com, ‏2021-09-15 (באנגלית אמריקאית)
  27. ^ BillWagner, Abstract and Sealed Classes and Class Members - C#, learn.microsoft.com, ‏2021-10-27 (באנגלית אמריקאית)
  28. ^ 1 2 http://msdn.microsoft.com/en-us/library/88c54tsw.aspx
  29. ^ BillWagner, Static Classes and Static Class Members - C#, learn.microsoft.com, ‏2024-03-19 (באנגלית אמריקאית)
  30. ^ BillWagner, Extension Methods - C#, learn.microsoft.com, ‏2024-03-15 (באנגלית אמריקאית)
  31. ^ BillWagner, override modifier - C# reference, learn.microsoft.com, ‏2024-03-30 (באנגלית אמריקאית)
  32. ^ BillWagner, Accessibility Levels - C# Reference, docs.microsoft.com (באנגלית אמריקאית)
  33. ^ BillWagner, Auto-Implemented Properties - C#, learn.microsoft.com, ‏2022-09-29 (באנגלית אמריקאית)
  34. ^ Archiveddocs, Overloadable Operators (C# Programming Guide), learn.microsoft.com, ‏2013-11-24 (באנגלית אמריקאית)
  35. ^ BillWagner, event keyword - C# reference, learn.microsoft.com, ‏2022-05-03 (באנגלית אמריקאית)
  36. ^ BillWagner, Using Delegates - C#, learn.microsoft.com, ‏2023-07-31 (באנגלית אמריקאית)
  37. ^ ThreadStart Delegate
  38. ^ dotnet-bot, Action Delegate (System), learn.microsoft.com (באנגלית אמריקאית)
  39. ^ dotnet-bot, Comparison Delegate (System), learn.microsoft.com (באנגלית אמריקאית)
  40. ^ BillWagner, delegate operator - Create an anonymous method that can be converted to a delegate type. - C# reference, learn.microsoft.com, ‏2024-03-30 (באנגלית אמריקאית)
  41. ^ על מנת שהתוכנית תמתין לתגובה מהמשתמש, יש להוסיף את הפקודה ;()Console.ReadKey לאחר השורה השלישית מהסוף.