סי שארפ

היסטוריה

לאחר הפסדה של "מיקרוסופט" במשפט לחברת "סאן", בעקבות סכסוך שפרץ ביניהן סביב המימוש של "מיקרוסופט" לשפת ג'אווה, שנקרא J++‎, חויבה "מיקרוסופט" על פי פסק הדין להפסיק לשווק את J++‎ וכן את המכונה הווירטואלית שפיתחה עבורה, MS-JVM. עקב כך הוחלט ב"מיקרוסופט" על פיתוח שפה חדשה במטרה להוות תחליף ראוי לשפות התכנות מהדור הקודם כמו ויז'ואל בייסיק או C++‎ וכן להוות יריב שקול לג'אווה. צוות פיתוח בראשות מהנדס התוכנה הדני אנדרס הלסברג (מי שהמציא את טורבו פסקל והיה מאדריכלי דלפי) הופקד על פיתוחה בתחילת 1999, והגרסה הראשונה הושקה עם ההכרזה על דוט נט ביולי 2000. גרסה זו סיפקה את התשתית הראשונית לכתיבת יישומים שולחניים ואתרי אינטרנט, תוך שהיא שואבת הרבה מה-API והתחביר של ג'אווה, גם בטכנולוגיית פיתוח האתרים, ASP.NET.
גרסה 1.1, אשר יצאה ב-3 באפריל 2003, הכילה שיפורים בטכנולוגיית ASP.NET אשר מאפשרים פיתוח אתרים המותאמים למכשירים ניידים, שיפורי אבטחה בפיתוח יישומים שולחניים, תמיכה מובנית בגישה לבסיס נתונים אורקל ובשימוש במתאם בסיסי הנתונים האוניברסלי של "מיקרוסופט", ODBC. גרסה זו גם תומכת בהפעלה של מספר גרסאות של אותו יישום במקביל על אותו מחשב, וכן תומכת לראשונה בפרוטוקול IPv6.
גרסה 2.0, אשר יצאה ב-22 בינואר 2006, הוסיפה לראשונה תמיכה בתכנות גנרי, שהיה קיים כבר ב-++C. גרסה זו תמכה לראשונה ביישומי 64 ביט. בנוסף, נוספה תמיכה מובנית לגישה למערכת ההרשאות של Windows, ונוספו שיפורים בטכנולוגיית הגישה לבסיסי נתונים ADO.NET. בתחום פיתוח האתרים, נוספו מנגנונים אשר מאפשרים לשלוט בצורה טובה יותר בתבנית הכללית של כל הדפים באתר. נוספה תמיכה מובנית בתקשורת SSL, ותמיכה בפרוטוקול FTP‎, Ping, SMTP ו-SOAP 1.2. שופרה התמיכה בטכנולוגיית COM וביישומי שורת הפקודה. נוספה תמיכה בקבלת חיבורי HTTP ו-FTP מיישומים אחרים. שופרה התמיכה בסמפור, ונוספה תמיכה בטרנזאקציות דרך טכנולוגיית DTC. פקדים רבים נוספו לפיתוח תוכנות שולחניות, ונוספה טכנולוגיית ClickOnce, אשר מאפשרת ליישומים שולחניים להתעדכן באופן אוטומטי.
גרסה 3 אשר יצאה בנובמבר 2007 הוסיפה הרחבת שיטות, (שמאפשר בזמן ריצה להוסיף שיטות לאובייקטים קיימים), טיפוס מרומז של משתנים מקומיים, ביטויי למדא, טיפוסים אנונימיים, טיפוס ממשק (Type Inference), ואתחול של אובייקטים ואוספים (בגרסאות הדוט נט המקבילות הוספו WCF, WPF וLINQ).
גרסה 4 אשר יצאה באפריל 2010 הוסיפה פרמטרים לפי שם ופרמטרים אופציונליים, טיפוסים דינמיים, ותמיכה מתקדמת ב-COM (בגרסת הדוט נט המקבילה הוספו יכולות של תכנות מקבילי, אתחול עצל, ומהדר DLR).
גרסה 5 אשר יצאה באוגוסט 2012 הוסיפה פונקציות אסינכרוניות, ומידע על המתקשר לפונקציה.
ב-2 באפריל 2014 "מיקרוסופט" הוציאה לאור מהדר סטטי בשם DotNET Native ליישומי Windows Store, המאפשר להדר קוד בשפות הדוט נט לקוד מכונה באופן ישיר (Native Code), ללא שימוש ב-J. 
בגרסה 6 אשר יצאה ביולי 2015 הוספו התכונות הבאות: הידור כשירות (service), יבוא של טיפוסים סטטיים למרחב שם, מסנן לחריגות, 
אתחול אוטומטי של מאפיין לקריאה בלבד, אתחול של מילון, 
Await בבלוק של catch/finally, אינטרפולציה על מחרוזת
והאופרטור nameof.

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

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

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

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

סוגי משתנים

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

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

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

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

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

מאפיינים (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);
והמימוש במחלקה אחרת יראה באופן הבא: ההשמה תשתמש במתודה 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; }}

תכנות מונחה אירועים

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

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

החל מגרסה 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);

תכנות גנרי

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

תכנות חזותי

השפה תומכת בתכנות חזותי בעיקר בממשקי משתמש, באמצעות ספריות תקניות של הדוט נט וכלי פיתוח של הסטודיו, כאשר אלמנטים בשפה כ-Events ו-Properties משתלבים בפיתוח החזותי ומקלים עליו. ניתן ליצור טפסים (Form) ליישומים שולחניים באמצעות ה-Winform, דפים (Page) ליישומי אינטרנט באמצעות ה-WebApplication, חלונות (Window) ליישומי גרפיקה מתקדמים באמצעות מודל התכנות WPF, ומסכים (Screen) ביישומי משחקים.
העקרון שנלקח מוויז'ואל בייסיק והורחב והועצם, הוא יצירת אובייקט ראשי המסמל טופס, דף או חלון בהתאם לסוג היישום, שיכול להכיל בתוכו פקדים שהם מודלים תכנותיים קטנים, המיוצגים באמצעות צלמיות, שיכולים לתקשר זה עם זה. על גבי הטופס ניתן ל"צייר" ולמקם פקדים באמצעות גרירתם מחלונית "ארגז כלים" (ToolBox), וניתן לראות את צורת הסידור שלהם באמצעות חלונית "מתאר מסמך" (Document outline/Designer). ישנה חלוקה פנימית בין פקדים (Controls) (שהם רכיבי תוכנה חזותיים כמו פקד תיבת טקסט ופקד תמונה) לבין רכיבים (Components) (שהם רכיבי תוכנה לוגיים, למשל רכיב FileSystemWatcher שעוקב אחר שינויים בקבצים, או רכיב BackgroundWorker שמאפשר ליצור תהליכון נפרד עם אירועים). הקשר בין פעולות הממשק לקוד הוא דו כיווני: לכל פעולה חזותית מחולל קוד מתאים, למשל גרירה של פקד אל הטופס יוצרת הכרזה שלו בטופס ואתחול של מאפיינים מסוימים שנקבעו מראש, כמיקום הפקד בטופס, וגודלו, וכל שינוי בקוד מתורגם לייצוגים החזותיים בחזרה.
ב-WebApplication קיימים כ-90 פקדים ורכיבים שונים, וב-Winform קיימים כ-70 פקדים ורכיבים שונים. ניתן ליצור פקדי ורכיבי משתמש מותאמים אישית על בסיס פקדים ורכיבים קיימים, או פקדים ורכיבים חדשים. הפקדים מסווגים בדרך כלל לכמה קטגוריות עיקריות. ב-Winform למשל הם מסווגים לשיתופיים, מכולות (פקדים שמכילים פקדים אחרים), תפריטים, נתונים (לעבודה עם בסיסי נתונים), תיבות שיח ועוד. דרך העבודה האופיינית היא גישה אל הקוד מתוך הממשק והליכה מהכלל אל הפרט. תחילה יש ליצור את הטופס, לשרטט עליו את הפקדים, ולאחר מכן להגדיר את מאפייניהם השונים, והאירועים הדרושים ליישום באמצעות חלונית "מאפיינים" (Properties) שמציגה את המאפיינים והאירועים הקיימים בפקד, ואז ליצור אירוע באזור הקוד, ולכתוב בו את הקוד הנדרש כדי לממש את התוכנית.

זיכרון מנוהל

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

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

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

דוגמה לתוכנית Hello world

להלן דוגמה לתוכנית Hello world בשפה זו:
using System;public class ExampleClass{
 public static void Main(string[] args)
 {
    Console.WriteLine("Hello world!");
 }}