התחביר של C# מבוסס בעיקרו על שפת התכנות C++ אך העיקרון התכנותי הושפע רבות מוויז'ואל בייסיק וג'אווה מהם אומצו מספר מוטיבים כמו תכנות מונחה-אירועים, תכנות הצהרתי, תכנות פונקציונלי ותכנות גנרי.
אפיוני השפה.
מקור שמה הוא פרפרזה על מקור שמה של ++C שהוא אופרטור הקידום שמשמעו "קידום ערך באחד" או בהקשר זה קידום שפת C רמה אחת מעל. במוזיקה משמעות C# היא הוספה של חצי טון לתו C (או "דו דיאז"). רעיון השימוש בסולמית אומץ על ידי "מיקרוסופט" כסיומת נפוצה לטכנולוגיות אחרות מבית החברה שעברו שיפוץ.
שפת #C תוכננה במטרה להשתלב בתשתית דוט נט הכוללת ספרייה תקנית גדולה וסביבת זמן ריצה. עם זאת, השפה איננה דורשת את דוט נט וקיימת מערכת מקבילה לסביבת לינוקס בשם Mono. ומערכת בשם Xamarin לסביבת אנדרואיד.
בשפה שולבו מספר מאפיינים בולטים שהם חלק מובנה מהעיקרון הטכנולוגי של דוט נט:
מערכת הטיפוסים.
הטיפוסיות בשפה היא סטטית (עם תמיכה ברב צורתיות של זמן ריצה), ותמיכה בטיפוס dynamic, המאפשר עבודה ב"טיפוסיות-ברווז" דינמית.
מערכת הטיפוסים היא חזקה ובטוחה, למעט באזורים בקוד המסומנים באמצעות המילה השמורה unsafe. באזורים אלה מתאפשרת גישה ישירה לזיכרון בעזרת מצביעים (בדומה לשפת C++).
על פי רוב, טיפוסי המשתנים מוגדרים במפורש, אך בהקשרים רבים ניתן להשתמש במילה השמורה var על מנת לבקש מהמהדר להסיק אותם בעצמו. כמו כן, ישנה הסקה מובלעת של טיפוסים במבנים תחביריים שונים כגון ביטויי למדא.
משתני ערך לעומת משתני ייחוס.
בשפת #C ישנה חלוקה של הטיפוסים לשני סוגים:
ההבדל העיקרי בין שני סוגים אלו הוא באופן שבו אובייקט מועתק (בפעולת השמה או בהעברה כפרמטר). בהעתקת משתנה ערך מועתק האובייקט עצמו, וכל פעולה המתבצעת בפונקציה מבוצעת על העותק, ולא על האובייקט המקורי. בהעתקת אובייקט מטיפוס התייחסות, מועתק יחוס (מזהה המגדיר כיצד למצוא את האובייקט בזיכרון), וכל פעולה שאיננה העתקה מבוצעת על האובייקט המקורי (באופן דומה מעט לעבודה עם מצביעים בשפת C).
בדומה למחלקה, גם מבנים יכולים להכיל קבועים, שדות, תכונות, מתודות, אופרטורים, אירועים ואף לממש ממשקים, אך לא ניתן להגדיר עבורם ירושה.
בעוד שמחלקה היא מבנה התכנות העיקרי, מבנים מיועדים לעטוף קבוצות קטנות של משתנים קשורים, בעיקר כאלה שאינם אמורים להשתנות לאחר היצירה של האובייקט.
אין לבלבל בין טיפוסי ערך והתייחסות ובין העברת פרמטרים על פי ערך ועל פי התייחסות (ר' בהמשך).
העברת פרמטרים.
ברירת המחדל של העברת פרמטרים למתודה היא על ידי העתקה: אובייקט מטיפוס ערך מועתק בעצמו, ועבור אובייקט מטיפוס התייחסות מועתקת ההתייחסות אליו. ניתן לשלוט על כך באמצעות הגדרת הפרמטרים כ-ref או כ-out (הן במתודה והן בקריאה אליה). פרמטר המוגדר כאחד מהם דורש משתנה ממש (או ביטוי שניתן לבצע אליו השמה - lvalue. ולא אובייקט); משתנה זה מועבר בעצמו - ללא העתקה - ופעולות השמה בתוך המתודה מתבצעות ישירות עליו.
ניתן להגדיר מתודה המקבלת מספר משתנה של פרמטרים בעזרת המילה השמורה params.
סוגי משתנים.
ניתן להגדיר משתנה קבוע (const) המאותחל בזמן הקישור, אשר בקוד ה-MSIL יוחלף לערכו. ניתן להגדיר משתנה קריאה-בלבד (readonly) היכול להיות מאותחל בזמן ריצה באמצעות הבנאי, וערכו אינו נשמר בקוד ה-MSIL, בניגוד לקבוע.
תכנות מונחה-עצמים.
שפת C# היא שפה מונחית-עצמים, התומכת בכימוס (Encapsulation), בירושה ובפולימורפיזם של זמן ריצה.
ירושה.
בשפת #C כל מחלקה יכולה לרשת ישירות ממחלקה אחת בלבד, וישנה אפשרות לממש מספר ממשקים (Interfaces): מחלקות הכוללות מתודות מופשטות בלבד, ומשמשות כחוזים המכריחים את המחלקות היורשות לממש את המתודות המוגדרות בממשק. ב-#C קיימת ירושה ציבורית בלבד; לא ניתן להחיל הגבלות גישה על מחלקות האם.
סוגים שונים של מחלקות:
מתודות (שיטות).
בשפת C# לא ניתן לכתוב פונקציות גלובליות או להגדיר משתנים גלובליים כמו בשפת C++ ווויז'ואל בייסיק. כל המתודות חייבות להיות מאוגדות במחלקות, ובפרט הפונקציה Main, שהיא נקודת הכניסה (ההתחלה) של התוכנית.
התחליף לפונקציה גלובלית הוא מתודה סטטית (static), המופעלת דרך שם המחלקה שלה בלי הצורך ליצור מופע. כתחליף למשתנה גלובלי ניתן להגדיר משתנה סטטי במחלקה. מתודה סטטית איננה יכולה לגשת למשתנים רגילים ללא התייחסות לאובייקט ספציפי, אך יכולה לגשת למשתנים סטטיים, אותם ניתן לאתחל באמצעות בנאי סטטי. ניתן להגדיר מחלקה כסטטית, ובמחלקה כזאת ניתן להגדיר רק מתודות סטטיות. לדוגמה, המחלקה Console היא סטטית, וכך גם המתודות הכלולות בה כגון WriteLine. סוג מיוחד של מתודות סטטיות הן מתודות-הרחבה (extension methods).
במחלקות מופשטות ובממשקים ניתן להגדיר "מתודה מופשטת": חתימה של מתודה ללא מימוש, שתמומש על ידי מחלקה יורשת.
ניתן להגדיר מתודה כמדומה (virtual) ולאפשר שינוי של המתודה במחלקה הנגזרת באמצעות המילה override. מתודה מדומה היא מתודה עבורה קיים מימוש, אך מימוש זה עשוי להיות מוחלף (להידרס) במחלקה יורשת.
ניתן למנוע דריסה של מתודה מדומה במחלקה נגזרת על ידי הגדרתה כמתודה חתומה (sealed) במחלקת הבסיס.
C# מאפשרת העמסת מתודות, שהיא האפשרות לכתוב מתודות בעלות שם זהה, וחתימה שונה (רשימת הפרמטרים צריכה להיות שונה בטיפוסי הפרמטרים או במספרם). כמו כן ניתן ליצור מתודה עם מספר לא ידוע של פרמטרים (באמצעות מילת המפתח params והגדרת מערך ברשימת הפרמטרים. (לדוגמה: (public void FunctionName(params String[] str1). גם ניתן ליצור ערכי ברירת מחדל בפרמטרים של מתודה באופן דומה לכתיבה ב-C#.
למחלקה בשפה ניתן לכתוב בנאי (Constructor) המופעל כאשר נוצר מופע, והורס (Destructor) המופעל לפני מחיקת המופע. ההורס קורא בעקיפין למתודה מסיימת (Finalize). כברירת מחדל נוצרים בנאי והורס ריקים. ניתן ליצור מספר רב של בנאים באמצעות העמסה. ייעודה של מתודת ה-Finalize הוא שחרור משאבים שהם Unmanaged - כלומר משאבים שאינם מנוהלים על ידי ה-CLR. למתודת ה-Finalize עלולה להיות השפעה גדולה על ביצועי התוכנה מאחר שהיא נקראת בתהליך איסוף הזבל אשר משהה עד לסיומו את התהליכונים המנוהלים על ידי ה-CLR.
הסתרה וחלוקה למודולים.
יחידת ההסתרה ב-#C היא המחלקה (ולא האובייקט). ישנן שש רמות הרשאה לגישה לשדות המחלקה:
ניתן לפצל קוד במחלקות חלקיות (partial), שהן מחלקות שמוגדרות במספר מקומות (ואף במספר קבצים). דוגמה רווחת לכך הוא פיצול הגדרת פקדי טופס WinfForms שנוצרים באמצעות המחולל הגרפי לקובץ 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
בדוגמה מחלקת 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 ומעלה נוספו מספר יכולות חדשות ביניהן היכולת ליצור "מאפיינים אוטומטיים" - אם יש צורך בגישה לחבר פרטי ללא לוגיקה כלשהי (וגם להשאיר מרווח תמרון להוסיף לוגיקה כזו בעתיד), ניתן ליישמו בדרך ישירה וחסכונית כמו בדוגמה הבאה. במקרה זה המהדר מייצר מאחורי הקלעים שדה מגבה שהגישה אליו אפשרית רק דרך המאפיינים, כאילו נכתב הקוד לעיל:
למאפיינים ב-#C תפקיד בתכנות חזותי. כל המאפיינים של כל פקד, מוצגים בזמן הפיתוח באמצעות גיליון המאפיינים ואף ניתן לשנות את תכונותיהם, באמצעות הממשק הגרפי, כך השינוי ישתקף בטופס באופן מידי כבר בזמן הפיתוח, מבלי שהמתכנת צריך לעשות שימוש בקוד.