stanford-oval/wikipedia huggingface dataset:
סי שארפ
היסטוריה > הדמיון לג'אווה

קיימים קווי דמיון רבים מאוד בין שפת #C לג'אווה, כגון; אובייקטים כהפניות (References), קוד ביניים ומכונה וירטואלית, הידור דינמי (JIT), ירושה יחידה עם ממשקים, זיכרון מנוהל ופינוי אשפה, שיקוף, Boxing, תהליכונים מובנים, יוניקוד מובנה, צורת השימוש בספריות ועוד.
ובכל זאת, קיימים הבדלים גדולים בין השתיים. בעיקר בהגדרת אופן מימוש מנגנוני השפה והשוני במימוש תכנות גנרי. מלבד זאת ב-#C נוספו תכונות שבחלקן אומצו מוויז'ואל בייסיק כגון תכנות חזותי, תכנות מונחה אירועים, אתחול אוטומטי של משתנים, משתנה רב-תכליתי, מאפיינים, לולאת טווח ועוד. במיוחד גדל הפער בין השפות בגרסאות החדשות של #C בהן נוספו הרחבות כמו תכנות פונקציונלי, מתודות-הרחבה (Extension methods) והרחבת LINQ.
פיתוח השפה היה ראשוני, ונטול עכבות היסטוריות כמו תאימות לאחור, שהקשתה על האבולוציה של ג'אווה. ברור גם כי העובדה ש-#C פותחה אחרי ג'אווה איפשרה ל"מיקרוסופט" ללמוד מהחסרונות והיתרונות שלה וליישם את הלקחים בשפה החדשה.
אנשי "סאן" טענו כי "מיקרוסופט" גנבה לא מעט רעיונות משפת ג'אווה. לטענתם, #C היא בעצם חיקוי לא מוצלח של ג'אווה שממנו הוסרו אמינות, יצרנות ואבטחה. ב"מיקרוסופט" מתעקשים לעומת זאת כי השפה דומה יותר ל-++C מאשר לג'אווה.
ההבדל המהותי ביותר בין #C לג'אווה הוא מתודולוגי – #C היא שפה מונחית עצמים טהורה (Pure Object Oriented), ב-#C כל דבר הוא אובייקט, אפילו הטיפוסים הפשוטים (int, double, float, char ועוד) הם אובייקטים. ג'אווה לעומת זאת, היא אומנם מונחית עצמים אולם היא לא שפה מונחית עצמים טהורה, הטיפוסים הפשוטים הם פרימיטיביים (כמו ב-C), והיא לא מבחינה בין מתודות וירטואליות למתודות לא וירטואליות ועוד.
היסטוריה > יעדי השפה

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

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

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

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

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

ניתן להגדיר קבוע (const) המאותחל בזמן הקישור, אשר בקוד ה-MSIL יוחלף לערכו. ניתן להגדיר משתנה קריאה-בלבד (readonly) היכול להיות מאותחל בזמן ריצה באמצעות הבנאי, וערכו אינו נשמר בקוד ה-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)

ניתן לממש בשפה מערך משונן שהוא מערך שהאיברים שלו הם מערכים, הוא נקרא לפעמים מערך של מערכים. המערכים יכולים להיות בגודל שונה.
להלן הצהרה על מערך חד-ממדי הכולל שלושה איברים, שכל אחד מהם הוא מערך חד-ממדי של מספרים שלמים.
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

ניתן לקבץ ביחד למבנה אחד ערכים של טיפוסי משתנים שונים בשימוש באובייקט 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 היא לולאה שמיועדת לעבור על אוסף או מבנה נתונים באופן סדרתי ולייצג את האיבר הנוכחי בחזרתיות. לולאה זו נלקחה משפת וויז'ואל בייסיק והיא ידידותית לשימוש באוספים. בלולאות ההתניה אין מספר קבוע של חזרות והן יתקיימו כל זמן שערך ההתניה הוא אמת.
מערכת הטיפוסים > אופרטורים

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

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

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

שיטה היא בלוק קוד בעלת חתימה מסוימת המכילה סדרת הוראות, שביצועה נעשה באמצעות קריאה לה באמצעות השימוש בשמה. כל פקודה בשפה מבוצעת בהקשר של שיטה המכילה אותה. כל השיטות חייבות להיות מאוגדות במחלקות (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).
במחלקות מופשטות ובממשקים ניתן להגדיר "מתודה מופשטת": חתימה של מתודה ללא מימוש, שתמומש על ידי מחלקה יורשת.
ניתן להגדיר מתודה כמדומה (virtual) ולאפשר שינוי של המתודה במחלקה היורשת באמצעות המילה override. מתודה מדומה היא מתודה עבורה קיים מימוש, אך מימוש זה עשוי להיות מוחלף (להידרס) במחלקה יורשת.
ניתן למנוע דריסה של מתודה מדומה במחלקה יורשת על ידי הגדרתה כמתודה חתומה (sealed) במחלקת הבסיס. #C מאפשרת העמסת מתודות, שהיא האפשרות לכתוב מתודות בעלות שם זהה, וחתימה שונה (רשימת הפרמטרים צריכה להיות שונה בטיפוסי הפרמטרים או במספרם). כמו כן ניתן ליצור מתודה עם מספר לא ידוע של פרמטרים (באמצעות מילת המפתח params והגדרת מערך ברשימת הפרמטרים. (לדוגמה: (public void FunctionName(params String[] str1). גם ניתן ליצור ערכי ברירת מחדל בפרמטרים של מתודה באופן דומה לכתיבה ב-#C.
למחלקה בשפה ניתן לכתוב בנאי (Constructor) המופעל כאשר נוצר מופע, והורס (Destructor) המופעל לפני מחיקת המופע. ההורס קורא בעקיפין למתודה מסיימת (Finalize). כברירת מחדל נוצרים בנאי והורס ריקים. ניתן ליצור מספר רב של בנאים באמצעות העמסה. ייעודה של מתודת ה-Finalize הוא שחרור משאבים שהם Unmanaged – כלומר משאבים שאינם מנוהלים על ידי ה-CLR. למתודת ה-Finalize עלולה להיות השפעה גדולה על ביצועי התוכנה מאחר שהיא נקראת בתהליך איסוף הזבל אשר משהה עד לסיומו את התהליכונים המנוהלים על ידי ה-CLR.
ניתן ליצור מתודה אסינכרונית עם הקידומת async שההוראות שבה יתבצעו באופן אסינכרוני כמו למשל באמצעות Task.
תכנות מונחה-עצמים > הסתרה וחלוקה למודולים

יחידת ההסתרה ב-#C היא המחלקה (ולא האובייקט). ישנן שש רמות הרשאה לגישה לשדות המחלקה:
private: (פרטי) מותרת גישה אך ורק מתוך המחלקה הנוכחית.
public: (ציבורי) מותרת גישה מכל מקום.
internal: (פנימי) מותרת גישה מכל מקום בתוך המכלול (assembly) של המחלקה, שהוא הקובץ בשפת-הביניים אותו מריצה סביבת הדוט נט לאחר שהתוכנית עוברת הידור.
protected: (מוגן) מותרת גישה מתוך המחלקה הנוכחית ומתוך מחלקות יורשות, גם אם הן במכלול אחר.
protected internal: (פנימי מוגן) מותרת גישה מתוך המכלול הנוכחי ומתוך מחלקות יורשות, גם אם הן במכלול אחר.
private protected: (מוגן פרטי) מותרת גישה מתוך המחלקה הנוכחית ומתוך מחלקות יורשות במכלול הנוכחי. זמין החל מגרסה 7.2.
ניתן לפצל קוד במחלקה חלקית (partial), שהיא מחלקה אחת שמפוצלת למספר מקומות (ואף במספר קבצים). דוגמה רווחת לכך הוא פיצול הגדרת פקדי טופס WinForms שנוצרים באמצעות המחולל הגרפי לקובץ designer נפרד, כדי לשמור על קובץ cs נקי לכתיבת הקוד עצמו.
ניתן ליצור מחלקות מקוננות. מופעים של מחלקה מקוננת נוצרים באופן בלתי תלוי במחלקת האם (כלומר, אין מחלקות מקוננות לא סטטיות, בניגוד למצב בשפת ג'אווה).
תכנות מונחה-עצמים > מאפיינים (Properties)

מאפיין הוא שיטה מיוחדת הנקראת Accessor (מאפשר גישה) שמתנהגת מבחינה תחבירית כמשתנה חבר ותפקידה לאפשר גישה לשדות פרטיים.
בדומה למוסכמה השכיחה בג'אווה לכנות מתודות שתפקידן השמה ואחזור של ערכים get ו-set בהתאמה ולאחריהן שם התכונה. אומץ רעיון ה"מאפיינים" מוויז'ואל בייסיק כדי להקל על השמה ואחזור שדות פרטיים. בתפיסה זו מאפיין מורכב משדה ושיטות הפועלות עליו: האחת קוראת (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 ומעלה נוספו מספר יכולות חדשות ביניהן היכולת ליצור "מאפיינים אוטומטיים" – אם יש צורך בגישה לחבר פרטי ללא לוגיקה כלשהי (וגם להשאיר מרווח תמרון להוסיף לוגיקה כזו בעתיד), ניתן ליישמו בדרך ישירה וחסכונית כמו בדוגמה הבאה. במקרה זה המהדר מייצר מאחורי הקלעים שדה מגבה שהגישה אליו אפשרית רק דרך המאפיינים, כאילו נכתב הקוד לעיל:
public int Age { get; set; }
למאפיינים ב-#C תפקיד בתכנות חזותי. כל המאפיינים של כל פקד, מוצגים בזמן הפיתוח באמצעות גיליון המאפיינים ואף ניתן לשנות את תכונותיהם, באמצעות הממשק הגרפי, כך השינוי ישתקף בטופס באופן מידי כבר בזמן הפיתוח, מבלי שהמתכנת צריך לעשות שימוש בקוד.
אחד החסרונות של מאפיינים הוא ההסתרה (מבחינה תחבירית) של פעולת חישוב. פעולת גישה למשתנה רגיל היא זולה מאוד, מבחינת זמן חישוב, אך פעולת גישה למאפיין עשויה לדרוש חישוב בלתי מוגבל בזיכרון או בזמן; השפה לא מונעת יצירה של לולאה אינסופית בתוך מאפיין. מהסיבה הזאת אין זה מקובל להגדיר מאפיינים המבצעים חישובים מורכבים.
תכנות מונחה-עצמים > העמסת אופרטורים

#C מאפשרת העמסת אופרטורים – שהיא טכניקה המאפשרת לתת משמעות חדשה לרוב האופרטורים הקיימים בשפה, על מנת להקל על הקריאות של התוכנית. את האופרטור "[ ]" מעמיסים בצורה מעט שונה מהאחרים, בעזרת מנגנון הנקרא סדרן – 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 עצמו הוא מחלקה שמכילה רשימה של הפניות לפונקציות, שניתן באמצעותה להזניק פונקציות ממקומות שונים.
ה-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, אך בשונה מהם הוא מונחה עצמים, בעל טיפוס חזק ומאובטח. כל מופע שלו מחזיק רשימת עצמים ברי-קריאה המתאימים לחתימה זאת. קריאה לנציג יוזמת קריאה של כל השיטות המוחזקים בו. ניתן לרשום (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).
ישנם מספר מימושים גנריים ל-Delegates בספריה של #C:
Action: פונקציה המקבלת פרמטרים אך לא מחזירה דבר.
Func: פונקציה המקבלת פרמטרים כלשהם ומחזירה טיפוס כלשהו.
Predicate: פונקציה המקבלת פרמטר אחד ומחזירה טיפוס בוליאני.
Comparison: פונקציה המקבלת שני פרמטרים בני אותו טיפוס, ומחזירה ערך מספרי. נועדת להשוואה בין אובייקטים.
דוגמאות:
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 תומכת במתודות אנונימיות – פונקציה הנכתבת בשורת פקודה, ומוגדרת בזמן ההרצה עצמה. החל מגרסה 3 נוספה תמיכה בביטויי למדא לשפה. זוהי דרך נוספת תמציתית וגמישה לכתוב מתודה אנונימית. שימוש בפונקציות אנונימיות נעשה כאשר רוצים להשתמש בפונקציה באופן מקומי מבלי להגדירה במחלקה, או כאשר פרמטר של פונקציה מוגדר כפונקציה, ואז ניתן לכתוב את הפונקציה האנונימית בקריאה לפונקציה, מבלי להגדירה כלל כפונקציה במחלקה.
בדוגמה מימוש של הפונקציה שלעיל באמצעות ביטוי למדא (האופרטור <= מפעיל הלמדא, מפריד בין הפרמטרים של הקלט בצידו השמאלי לגוף הלמדא מצידו הימני).
Func<string, string> Convert = x => x.ToUpper();
בביטויי למדא נעשה שימוש רב ב-LINQ. בדוגמה איתור גודל המילה הקטנה ביותר במערך מחרוזתי מתקבל במשתנה shortestWordLength.
string[] wordsArray = {"Orange", "Apple", "Pineapple", "Pear", "Peach"};
int shortestWordLength = wordsArray.Min(w => w.Length);