Rich Ecosystem — NuGet packages, ASP.NET Core, Entity Framework, Unity.
Modern Features — Records, pattern matching, nullable reference types, source generators.
Strong Tooling — Visual Studio, VS Code, Rider — best-in-class IDE support.
Interop — P/Invoke for native C/C++ libraries, COM interop.
Disadvantages
Performance — Slower than C/C++ due to managed runtime (though .NET 8 is very fast).
Memory Overhead — GC pauses can affect real-time applications.
Windows Bias — Some APIs still Windows-only (WPF, WinForms).
Verbosity — More boilerplate than Python or Kotlin for simple tasks.
Basics
Hello World & Entry Point
// Traditional (all versions)using System;class Program { static void Main(string[] args) { Console.WriteLine("Hello, World!"); }}// Top-level statements (C# 9+) — no class/Main neededConsole.WriteLine("Hello, World!");
Comments
// Single-line comment/* Multi-line comment *//// <summary>/// XML doc comment — used by IntelliSense and doc generators/// </summary>/// <param name="x">Parameter description</param>/// <returns>Return value description</returns>
Variables & Data Types
// Value typesint age = 25; // 32-bit integerlong big = 9_999_999L; // 64-bit integer (underscores for readability)float price = 9.99f; // 32-bit floatdouble pi = 3.14159265; // 64-bit floatdecimal money = 99.99m; // 128-bit precise decimal (use for money)char grade = 'A'; // 16-bit Unicode characterbool active = true; // true / false// Reference typesstring name = "Alice"; // immutable Unicode stringobject obj = 42; // base type of everything// Type inferencevar x = 42; // intvar s = "hello"; // stringvar list = new List<int>(); // List<int>// Constantsconst double TAX = 0.18;// Nullable value types (C# 2+)int? nullableInt = null;double? nullableDouble = 3.14;
Primitive Data Types Table
Type Alias Size Range
bool Boolean 1 bit true / false
byte Byte 1 byte 0 to 255
sbyte SByte 1 byte -128 to 127
char Char 2 bytes U+0000 to U+FFFF
short Int16 2 bytes -32,768 to 32,767
ushort UInt16 2 bytes 0 to 65,535
int Int32 4 bytes -2.1B to 2.1B
uint UInt32 4 bytes 0 to 4.3B
long Int64 8 bytes -9.2E18 to 9.2E18
ulong UInt64 8 bytes 0 to 18.4E18
float Single 4 bytes ±1.5E-45 to ±3.4E38
double Double 8 bytes ±5.0E-324 to ±1.7E308
decimal Decimal 16 bytes ±1.0E-28 to ±7.9E28
User Input / Output
// OutputConsole.WriteLine("Hello"); // with newlineConsole.Write("No newline"); // without newlineConsole.WriteLine($"Name: {name}"); // string interpolationConsole.WriteLine("Pi: {0:F2}", 3.14159); // format string → Pi: 3.14// InputConsole.Write("Enter your name: ");string input = Console.ReadLine()!; // ! = non-null assertion// Parse inputConsole.Write("Enter a number: ");if (int.TryParse(Console.ReadLine(), out int num)) { Console.WriteLine($"You entered: {num}");} else { Console.WriteLine("Invalid number");}
Operators
// Arithmeticint a = 10, b = 3;Console.WriteLine(a + b); // 13Console.WriteLine(a - b); // 7Console.WriteLine(a * b); // 30Console.WriteLine(a / b); // 3 (integer division)Console.WriteLine(a % b); // 1 (modulo)Console.WriteLine(a++); // 10 (post-increment)Console.WriteLine(++a); // 12 (pre-increment)// Relationala == b; a != b; a < b; a > b; a <= b; a >= b;// Logicaltrue && false; // falsetrue || false; // true!true; // false// Bitwisea & b; a | b; a ^ b; ~a; a << 1; a >> 1;// Assignmenta += b; a -= b; a *= b; a /= b; a %= b;a &= b; a |= b; a ^= b; a <<= 1; a >>= 1;// Null-coalescing (C# 2+)string? s = null;string result = s ?? "default"; // "default"s ??= "assigned"; // assign if null// Null-conditional (C# 6+)string? name = null;int? len = name?.Length; // null (no NullReferenceException)int len2 = name?.Length ?? 0; // 0// Ternarystring label = (age >= 18) ? "Adult" : "Minor";
Type Casting
// Implicit (safe, no data loss)int i = 42;double d = i; // int → double// Explicit cast (may lose data)double pi = 3.14;int truncated = (int)pi; // 3// Convert class (handles strings too)string s = "42";int n = Convert.ToInt32(s);double x = Convert.ToDouble("3.14");// Parse / TryParseint parsed = int.Parse("99");bool ok = int.TryParse("abc", out int val); // ok = false, val = 0// as / is operatorsobject obj = "hello";string? str = obj as string; // null if cast fails (no exception)if (obj is string s2) { Console.WriteLine(s2.ToUpper()); // pattern matching cast}
using System.Text;var sb = new StringBuilder();sb.Append("Hello");sb.Append(", ");sb.AppendLine("World!");sb.Insert(0, ">> ");sb.Replace("World", "C#");Console.WriteLine(sb.ToString()); // >> Hello, C#!Console.WriteLine(sb.Length); // lengthsb.Clear(); // reset// Use StringBuilder when building strings in loops// string + string in a loop = O(n²), StringBuilder = O(n)
Control Flow
if / else if / else
int score = 85;if (score >= 90) { Console.WriteLine("A");} else if (score >= 80) { Console.WriteLine("B");} else if (score >= 70) { Console.WriteLine("C");} else { Console.WriteLine("F");}// Output: B
Switch Statement & Switch Expression
// Classic switchint day = 3;switch (day) { case 1: Console.WriteLine("Monday"); break; case 2: Console.WriteLine("Tuesday"); break; case 3: Console.WriteLine("Wednesday"); break; default: Console.WriteLine("Other"); break;}// Switch expression (C# 8+) — concise, returns a valuestring dayName = day switch { 1 => "Monday", 2 => "Tuesday", 3 => "Wednesday", 4 => "Thursday", 5 => "Friday", _ => "Weekend" // default};Console.WriteLine(dayName); // Wednesday
Loops
// forfor (int i = 0; i < 5; i++) { Console.Write(i + " "); // 0 1 2 3 4}// whileint n = 0;while (n < 5) { Console.Write(n++ + " ");}// do-while (executes at least once)int x = 0;do { Console.Write(x++ + " ");} while (x < 3);// foreach (works on any IEnumerable)int[] nums = {1, 2, 3, 4, 5};foreach (int num in nums) { Console.Write(num + " ");}// break / continuefor (int i = 0; i < 10; i++) { if (i == 3) continue; // skip 3 if (i == 7) break; // stop at 7 Console.Write(i + " "); // 0 1 2 4 5 6}
Pattern Matching (C# 7+)
object obj = 42;// is patternif (obj is int n) { Console.WriteLine($"Integer: {n}");}// switch with patterns (C# 8+)static string Describe(object o) => o switch { int i when i < 0 => "negative int", int i => $"positive int: {i}", string s => $"string: {s}", null => "null", _ => "unknown"};Console.WriteLine(Describe(42)); // positive int: 42Console.WriteLine(Describe("hello")); // string: helloConsole.WriteLine(Describe(null)); // null// Property pattern (C# 8+)static string GetDiscount(Person p) => p switch { { Age: < 18 } => "Student discount", { Age: >= 65 } => "Senior discount", { Name: "VIP" } => "VIP discount", _ => "No discount"};
Functions & Methods
Method Declaration
// Basic methodstatic int Add(int a, int b) { return a + b;}// Expression-bodied method (C# 6+)static int Multiply(int a, int b) => a * b;// void methodstatic void Greet(string name) => Console.WriteLine($"Hello, {name}!");// CallingConsole.WriteLine(Add(3, 4)); // 7Console.WriteLine(Multiply(3, 4)); // 12Greet("Alice"); // Hello, Alice!
Parameters
// Default parametersstatic void Greet(string name, string msg = "Hello") { Console.WriteLine($"{msg}, {name}!");}Greet("Alice"); // Hello, Alice!Greet("Bob", "Hi"); // Hi, Bob!// Named argumentsGreet(msg: "Hey", name: "Charlie"); // Hey, Charlie!// ref — pass by reference (caller must initialize)static void Double(ref int x) { x *= 2; }int n = 5;Double(ref n);Console.WriteLine(n); // 10// out — output parameter (caller doesn't need to initialize)static bool TryDivide(int a, int b, out double result) { if (b == 0) { result = 0; return false; } result = (double)a / b; return true;}if (TryDivide(10, 3, out double r)) Console.WriteLine(r); // 3.333...// in — read-only reference (C# 7.2+)static void Print(in int x) { Console.WriteLine(x); }// params — variable number of argumentsstatic int Sum(params int[] nums) => nums.Sum();Console.WriteLine(Sum(1, 2, 3, 4, 5)); // 15
Local Functions (C# 7+)
static int Factorial(int n) { // Local function — only visible inside Factorial int Calc(int x) => x <= 1 ? 1 : x * Calc(x - 1); return Calc(n);}Console.WriteLine(Factorial(5)); // 120
Lambda Expressions
// Func<TInput, TOutput>Func<int, int> square = x => x * x;Console.WriteLine(square(5)); // 25// Action<T> — no return valueAction<string> print = s => Console.WriteLine(s);print("Hello"); // Hello// Predicate<T> — returns boolPredicate<int> isEven = n => n % 2 == 0;Console.WriteLine(isEven(4)); // True// Multi-line lambdaFunc<int, int, int> add = (a, b) => { int result = a + b; return result;};// Used with LINQvar nums = new[] {1, 2, 3, 4, 5};var evens = nums.Where(n => n % 2 == 0).ToList(); // {2, 4}
Extension Methods
// Must be in a static class, first param has 'this'public static class StringExtensions { public static bool IsPalindrome(this string s) { string rev = new string(s.Reverse().ToArray()); return s == rev; } public static string Repeat(this string s, int times) => string.Concat(Enumerable.Repeat(s, times));}// Usage — called as if it's a method on stringConsole.WriteLine("racecar".IsPalindrome()); // TrueConsole.WriteLine("ha".Repeat(3)); // hahaha
Collections & Data Structures
Arrays
// 1D arrayint[] nums = {1, 2, 3, 4, 5};int[] zeros = new int[5]; // all zerosConsole.WriteLine(nums[0]); // 1Console.WriteLine(nums.Length); // 5Array.Sort(nums);Array.Reverse(nums);// 2D arrayint[,] grid = new int[3, 3];grid[0, 0] = 1;Console.WriteLine(grid.GetLength(0)); // rows = 3// Jagged array (array of arrays)int[][] jagged = new int[3][];jagged[0] = new int[] {1, 2};jagged[1] = new int[] {3, 4, 5};
List<T>
using System.Collections.Generic;var list = new List<int> {1, 2, 3};list.Add(4);list.AddRange(new[] {5, 6});list.Insert(0, 0); // insert at index 0list.Remove(3); // remove first occurrence of 3list.RemoveAt(0); // remove at indexlist.RemoveAll(x => x < 3); // remove all matchingConsole.WriteLine(list.Count); // countConsole.WriteLine(list.Contains(4)); // TrueConsole.WriteLine(list.IndexOf(4)); // indexlist.Sort();list.Reverse();list.Clear();// Convertint[] arr = list.ToArray();List<string> strs = new List<string>(new[] {"a", "b"});
Dictionary<TKey, TValue>
var dict = new Dictionary<string, int> { ["Alice"] = 95, ["Bob"] = 87};dict["Charlie"] = 92;dict.Add("Dave", 78);Console.WriteLine(dict["Alice"]); // 95Console.WriteLine(dict.ContainsKey("Bob")); // TrueConsole.WriteLine(dict.Count); // 4dict.Remove("Dave");// Safe accessif (dict.TryGetValue("Alice", out int score)) { Console.WriteLine(score); // 95}// Iterateforeach (var (key, val) in dict) { Console.WriteLine($"{key}: {val}");}// Keys and Valuesforeach (string key in dict.Keys) { }foreach (int val in dict.Values) { }
HashSet<T>
var set = new HashSet<int> {1, 2, 3, 4, 5};set.Add(3); // ignored — already existsset.Remove(1);Console.WriteLine(set.Contains(3)); // TrueConsole.WriteLine(set.Count); // 4// Set operationsvar a = new HashSet<int> {1, 2, 3, 4};var b = new HashSet<int> {3, 4, 5, 6};a.UnionWith(b); // a = {1,2,3,4,5,6}a.IntersectWith(b); // a = {3,4}a.ExceptWith(b); // a = {1,2}
public class Car { // Fields private string _brand; private int _year; // Properties (C# way of getters/setters) public string Brand { get => _brand; set => _brand = value ?? throw new ArgumentNullException(); } // Auto-property (compiler generates backing field) public int Year { get; set; } public string Model { get; init; } // init-only (C# 9+) // Constructor public Car(string brand, int year, string model) { _brand = brand; Year = year; Model = model; } // Method public string Describe() => $"{Year} {_brand} {Model}"; // Static method public static Car Create(string brand) => new Car(brand, 2024, "Base"); // Override ToString public override string ToString() => Describe();}// Usagevar car = new Car("Toyota", 2022, "Camry");Console.WriteLine(car.Describe()); // 2022 Toyota CamryConsole.WriteLine(car); // calls ToString()// Object initializer syntaxvar car2 = new Car("Honda", 2023, "Civic") { Year = 2024 };
Constructors
public class Person { public string Name { get; } public int Age { get; } // Primary constructor (C# 12+) // public Person(string name, int age) { Name = name; Age = age; } public Person(string name, int age) { Name = name; Age = age; } // Constructor chaining with : this() public Person(string name) : this(name, 0) { } // Static constructor — runs once before first use static Person() { Console.WriteLine("Person class initialized"); }}var p1 = new Person("Alice", 30);var p2 = new Person("Bob"); // Age = 0
Records (C# 9+) — Immutable Data Classes
// Record — immutable by default, value equality, auto ToStringpublic record Point(double X, double Y);var p1 = new Point(1.0, 2.0);var p2 = new Point(1.0, 2.0);Console.WriteLine(p1 == p2); // True (value equality!)Console.WriteLine(p1); // Point { X = 1, Y = 2 }// Non-destructive mutation with 'with'var p3 = p1 with { X = 5.0 };Console.WriteLine(p3); // Point { X = 5, Y = 2 }// Record struct (C# 10+) — value type recordpublic record struct Color(byte R, byte G, byte B);// Mutable recordpublic record class MutablePoint { public double X { get; set; } public double Y { get; set; }}
Static Classes & Members
public static class MathHelper { public static double PI = 3.14159265; public static double CircleArea(double r) => PI * r * r; public static double Clamp(double val, double min, double max) => Math.Max(min, Math.Min(max, val));}Console.WriteLine(MathHelper.CircleArea(5)); // 78.539...Console.WriteLine(MathHelper.Clamp(15, 0, 10)); // 10
public class Animal { public string Name { get; } public Animal(string name) { Name = name; } public virtual void Speak() => Console.WriteLine($"{Name} makes a sound"); public void Eat() => Console.WriteLine($"{Name} is eating");}public class Dog : Animal { public string Breed { get; } public Dog(string name, string breed) : base(name) { Breed = breed; } public override void Speak() => Console.WriteLine($"{Name} says Woof!"); public void Fetch() => Console.WriteLine($"{Name} fetches the ball");}Dog d = new Dog("Rex", "Labrador");d.Speak(); // Rex says Woof!d.Eat(); // Rex is eatingd.Fetch(); // Rex fetches the ball// Polymorphism via base referenceAnimal a = new Dog("Buddy", "Poodle");a.Speak(); // Buddy says Woof! (runtime dispatch)
Abstract Classes & Interfaces
// Abstract class — cannot be instantiated, can have implementationpublic abstract class Shape { public string Color { get; set; } = "Black"; public abstract double Area(); // must override public abstract double Perimeter(); // must override public void Describe() => Console.WriteLine($"{Color} shape, area={Area():F2}");}// Interface — pure contract, no implementation (until C# 8 default methods)public interface IDrawable { void Draw(); void Resize(double factor); // all abstract by default}public interface ISerializable { string Serialize();}// Class implementing abstract + multiple interfacespublic class Circle : Shape, IDrawable, ISerializable { public double Radius { get; } public Circle(double r) { Radius = r; } public override double Area() => Math.PI * Radius * Radius; public override double Perimeter() => 2 * Math.PI * Radius; public void Draw() => Console.WriteLine($"Drawing circle r={Radius}"); public void Resize(double f) { /* ... */ } public string Serialize() => $"{{\"radius\":{Radius}}}";}Circle c = new Circle(5);c.Describe(); // Black shape, area=78.54c.Draw(); // Drawing circle r=5Console.WriteLine(c.Serialize()); // {"radius":5}
sealed, abstract, override, new
public class Base { public virtual void Foo() => Console.WriteLine("Base.Foo"); public virtual void Bar() => Console.WriteLine("Base.Bar");}public class Derived : Base { public override void Foo() => Console.WriteLine("Derived.Foo"); // override public sealed override void Bar() => Console.WriteLine("Derived.Bar"); // no further override public new void Baz() { } // hides base method (not polymorphic)}// sealed class — cannot be inheritedpublic sealed class FinalClass { }
Generics
Generic Methods & Classes
// Generic methodstatic T Max<T>(T a, T b) where T : IComparable<T> => a.CompareTo(b) >= 0 ? a : b;Console.WriteLine(Max(3, 7)); // 7Console.WriteLine(Max("apple", "banana")); // banana// Generic classpublic class Stack<T> { private List<T> _items = new(); public void Push(T item) => _items.Add(item); public T Pop() { var item = _items[^1]; // index from end (C# 8+) _items.RemoveAt(_items.Count - 1); return item; } public T Peek() => _items[^1]; public int Count => _items.Count; public bool IsEmpty => _items.Count == 0;}var stack = new Stack<string>();stack.Push("a"); stack.Push("b"); stack.Push("c");Console.WriteLine(stack.Pop()); // c
Generic Constraints
// where T : class — T must be a reference type// where T : struct — T must be a value type// where T : new() — T must have parameterless constructor// where T : BaseClass — T must inherit from BaseClass// where T : IInterface — T must implement IInterface// where T : notnull — T must be non-nullable (C# 8+)static T CreateAndInit<T>() where T : new() => new T();static void PrintAll<T>(IEnumerable<T> items) where T : notnull { foreach (var item in items) Console.WriteLine(item);}
Delegates, Events & Func/Action
Delegates
// Delegate — type-safe function pointerdelegate int MathOp(int a, int b);static int Add(int a, int b) => a + b;static int Mul(int a, int b) => a * b;MathOp op = Add;Console.WriteLine(op(3, 4)); // 7op = Mul;Console.WriteLine(op(3, 4)); // 12// Multicast delegate — chain multiple methodsAction<string> log = Console.WriteLine;log += s => Console.Error.WriteLine($"[ERR] {s}");log("test"); // calls both
Func, Action, Predicate
// Func<T1, T2, ..., TResult> — has return valueFunc<int, int, int> add = (a, b) => a + b;Func<string, int> len = s => s.Length;Func<int> getRandom = () => new Random().Next(100);// Action<T1, T2, ...> — no return value (void)Action greet = () => Console.WriteLine("Hello!");Action<string, int> info = (name, age) => Console.WriteLine($"{name} is {age}");// Predicate<T> — returns boolPredicate<int> isPositive = n => n > 0;Console.WriteLine(add(3, 4)); // 7Console.WriteLine(isPositive(-1)); // False
Events
public class Button { // Event based on EventHandler delegate public event EventHandler? Clicked; public event EventHandler<string>? TextChanged; public void Click() { Clicked?.Invoke(this, EventArgs.Empty); // safe invoke } public void ChangeText(string text) { TextChanged?.Invoke(this, text); }}var btn = new Button();// Subscribebtn.Clicked += (sender, e) => Console.WriteLine("Button clicked!");btn.TextChanged += (sender, text) => Console.WriteLine($"Text: {text}");btn.Click(); // Button clicked!btn.ChangeText("OK"); // Text: OK
LINQ — Language Integrated Query
Query Syntax vs Method Syntax
using System.Linq;int[] nums = {5, 3, 8, 1, 9, 2, 7, 4, 6};// Method syntax (most common)var result = nums .Where(n => n > 4) .OrderBy(n => n) .Select(n => n * 2) .ToList();// result = {10, 12, 14, 16, 18}// Query syntax (SQL-like)var result2 = (from n in nums where n > 4 orderby n select n * 2).ToList();
Common LINQ Methods
var nums = new[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// Filteringnums.Where(n => n % 2 == 0); // {2,4,6,8,10}// Projectionnums.Select(n => n * n); // {1,4,9,16,...}nums.Select((n, i) => $"{i}:{n}"); // with index// Orderingnums.OrderBy(n => n);nums.OrderByDescending(n => n);nums.ThenBy(n => n); // secondary sort// Aggregationnums.Count(); // 10nums.Count(n => n > 5); // 5nums.Sum(); // 55nums.Sum(n => n * 2); // 110nums.Min(); // 1nums.Max(); // 10nums.Average(); // 5.5nums.Aggregate((acc, n) => acc + n); // 55 (custom reduce)// Element accessnums.First(); // 1nums.First(n => n > 5); // 6nums.FirstOrDefault(n => n > 100); // 0 (default)nums.Last(); // 10nums.Single(n => n == 5); // 5 (throws if 0 or >1)nums.ElementAt(3); // 4// Existence checksnums.Any(n => n > 9); // Truenums.All(n => n > 0); // Truenums.Contains(5); // True// Set operationsnums.Distinct();nums.Union(new[] {11, 12});nums.Intersect(new[] {1, 2, 3});nums.Except(new[] {1, 2, 3});// Partitioningnums.Take(3); // {1,2,3}nums.Skip(7); // {8,9,10}nums.TakeLast(3); // {8,9,10}nums.SkipLast(7); // {1,2,3}nums.TakeWhile(n => n < 5); // {1,2,3,4}// Groupingnums.GroupBy(n => n % 2 == 0 ? "even" : "odd");// Flatteningvar nested = new[] { new[]{1,2}, new[]{3,4} };nested.SelectMany(x => x); // {1,2,3,4}// Conversionnums.ToList();nums.ToArray();nums.ToDictionary(n => n, n => n * n);nums.ToHashSet();
// required property — must be set in object initializerpublic class Config { public required string Host { get; init; } public required int Port { get; init; } public string Protocol { get; init; } = "https";}var cfg = new Config { Host = "localhost", Port = 8080 };// Primary constructor (C# 12) — parameters available throughout classpublic class Logger(string name, bool verbose = false) { public void Log(string msg) { if (verbose) Console.WriteLine($"[{name}] {msg}"); }}