C# is a modern, statically typed, object-oriented language from Microsoft that runs on .NET. This cheat sheet covers the full C# language — from variables to async patterns — with copy-ready code for everyday development.
Quick reference
The 25 patterns that cover 95% of everyday C# development.
| Pattern | Example |
|---|---|
| Variable | int x = 42; |
| Var inference | var name = "Alice"; |
| String interpolation | $"Hello, {name}!" |
| Null-coalescing | val ?? "default" |
| Null-conditional | obj?.Property |
| Null-coalescing assign | x ??= new List<int>(); |
| Array | int[] nums = {1, 2, 3}; |
| List | var list = new List<string>(); |
| Dictionary | var map = new Dictionary<string, int>(); |
| LINQ where | list.Where(x => x > 0) |
| LINQ select | list.Select(x => x * 2) |
| LINQ first | list.FirstOrDefault(x => x > 0) |
| String format | string.Format("{0}", val) |
| Async method | async Task<int> GetAsync() |
| Await | var r = await GetAsync(); |
| Try-catch | try { } catch (Exception e) { } |
| Pattern is | if (obj is string s) |
| Switch expression | val switch { 1 => "one", _ => "?" } |
| Record | record Point(int X, int Y); |
| Interface | interface IRepo { T Get(int id); } |
| Generic constraint | where T : class, new() |
| Extension method | static int Double(this int x) => x * 2; |
| Tuple | var p = (X: 1, Y: 2); |
| Deconstruct | var (x, y) = point; |
| Using declaration | using var stream = File.OpenRead(path); |
Variables & types
// Value types
int x = 42;
double pi = 3.14;
bool flag = true;
char ch = 'A';
decimal price = 9.99m; // m suffix for decimal
// Reference types
string name = "Alice";
object obj = new object();
// Type inference
var count = 0; // int
var items = new List<string>(); // List<string>
// Constants
const int MaxSize = 100;
const string Prefix = "ID-";
// Nullable value types
int? maybeNull = null;
int value = maybeNull ?? 0; // 0
int guaranteed = maybeNull!.Value; // throws if null
// Nullable reference types (C# 8+, enable in .csproj)
string? nullableName = null;
string nonNullable = "required";
// Type casting
double d = 3.7;
int truncated = (int)d; // 3
string str = d.ToString(); // "3.7"
int parsed = int.Parse("42"); // throws on bad input
bool ok = int.TryParse("42", out int result); // safe
// Data type sizes
// byte 0-255, short -32k..32k, int -2B..2B, long -9.2×10^18..9.2×10^18
// float 7 digits, double 15-16 digits, decimal 28-29 digits (financial)
Strings
string s = "Hello, World!";
// Length and access
int len = s.Length; // 13
char first = s[0]; // 'H'
// Case
s.ToUpper() // "HELLO, WORLD!"
s.ToLower() // "hello, world!"
// Search
s.Contains("World") // true
s.StartsWith("Hello") // true
s.EndsWith("!") // true
s.IndexOf("o") // 4 (first occurrence)
s.LastIndexOf("o") // 8
// Slice
s.Substring(7, 5) // "World"
s[7..12] // "World" (C# 8+ range syntax)
s[^1] // '!' (index from end)
// Modify (strings are immutable — these return new strings)
s.Replace("World", "C#") // "Hello, C#!"
s.Trim() // strip whitespace from both ends
s.TrimStart()
s.TrimEnd()
s.PadLeft(20) // right-align in 20 chars
s.PadLeft(20, '0') // zero-pad
// Split and join
string[] parts = "a,b,c".Split(','); // ["a","b","c"]
string joined = string.Join("-", parts); // "a-b-c"
string joined2 = string.Join(", ", parts); // "a, b, c"
// Check
string.IsNullOrEmpty(s) // false if s has content
string.IsNullOrWhiteSpace(s) // false if s is not just spaces
// Format
string.Format("Hello, {0}! You are {1}.", name, age)
$"Hello, {name}! You are {age}." // preferred
// Multi-line (C# 11 raw string literals)
string json = """
{
"name": "Alice"
}
""";
// StringBuilder for many concatenations
var sb = new StringBuilder();
sb.Append("Hello");
sb.Append(", ");
sb.Append("World");
string result = sb.ToString(); // "Hello, World"
Control flow
// If-else
if (x > 0)
Console.WriteLine("positive");
else if (x < 0)
Console.WriteLine("negative");
else
Console.WriteLine("zero");
// Switch statement
switch (day)
{
case "Monday":
case "Tuesday":
Console.WriteLine("weekday");
break;
case "Saturday":
case "Sunday":
Console.WriteLine("weekend");
break;
default:
Console.WriteLine("other");
break;
}
// Switch expression (C# 8+)
string label = day switch
{
"Monday" or "Tuesday" or "Wednesday" or "Thursday" or "Friday" => "weekday",
"Saturday" or "Sunday" => "weekend",
_ => "unknown"
};
// For loops
for (int i = 0; i < 10; i++) { }
foreach (var item in collection) { }
while (condition) { }
do { } while (condition);
// Loop control
break; // exit loop
continue; // skip to next iteration
// Ternary
string sign = x > 0 ? "positive" : "non-positive";
Methods
// Basic
int Add(int a, int b) => a + b; // expression body
int Multiply(int a, int b) { return a * b; } // block body
// Optional parameters
void Print(string msg, bool newLine = true)
{
Console.Write(msg);
if (newLine) Console.WriteLine();
}
Print("Hi"); // newLine defaults to true
Print("Hi", false);
// Named arguments
Print(msg: "Hi", newLine: false);
// Params (variable-length)
int Sum(params int[] nums) => nums.Sum();
Sum(1, 2, 3, 4); // 10
// Out parameters
bool TryDivide(int a, int b, out int result)
{
if (b == 0) { result = 0; return false; }
result = a / b;
return true;
}
// Ref parameters (pass by reference)
void Double(ref int x) => x *= 2;
// Extension methods (must be in a static class)
public static class StringExtensions
{
public static bool IsEmail(this string s) =>
s.Contains('@') && s.Contains('.');
}
"user@example.com".IsEmail(); // true
// Local functions
int Factorial(int n)
{
return n <= 1 ? 1 : n * Inner(n - 1);
int Inner(int x) => x <= 1 ? 1 : x * Inner(x - 1);
}
OOP
// Class
public class Animal
{
// Auto-implemented properties
public string Name { get; set; }
public int Age { get; private set; } // read-only outside class
// Constructor
public Animal(string name, int age)
{
Name = name;
Age = age;
}
// Method
public virtual string Speak() => $"{Name} says something.";
// Static member
public static int Count { get; private set; }
// Object initializer support (no special code needed)
}
// Usage
var a = new Animal("Dog", 3);
var b = new Animal { Name = "Cat", Age = 2 }; // requires parameterless ctor
// Inheritance
public class Dog : Animal
{
public string Breed { get; }
public Dog(string name, int age, string breed)
: base(name, age) // call parent constructor
{
Breed = breed;
}
public override string Speak() => $"{Name} barks!";
}
// Abstract class
public abstract class Shape
{
public abstract double Area(); // subclasses must implement
public void Print() => Console.WriteLine($"Area: {Area():F2}");
}
// Interface
public interface IDrawable
{
void Draw();
string Color { get; set; } // properties allowed
void Resize(double factor) => throw new NotImplementedException(); // default impl (C# 8+)
}
// Implementing interface
public class Circle : Shape, IDrawable
{
public double Radius { get; }
public string Color { get; set; } = "black";
public Circle(double radius) => Radius = radius;
public override double Area() => Math.PI * Radius * Radius;
public void Draw() => Console.WriteLine($"Drawing circle r={Radius}");
}
// Sealed (can't inherit from)
public sealed class Singleton
{
private static Singleton? _instance;
public static Singleton Instance => _instance ??= new Singleton();
private Singleton() { }
}
// Record (C# 9+) — immutable value-based equality
public record Point(int X, int Y); // positional record
var p1 = new Point(1, 2);
var p2 = p1 with { Y = 5 }; // non-destructive mutation
Console.WriteLine(p1 == new Point(1, 2)); // true (value equality)
// Struct — value type (stack allocated, no heap)
public struct Color
{
public byte R, G, B;
public Color(byte r, byte g, byte b) { R = r; G = g; B = b; }
}
// Enum
public enum Status { Active, Inactive, Pending }
Status s = Status.Active;
int code = (int)s; // 0
Status back = (Status)0; // Active
string name = s.ToString(); // "Active"
Generics
// Generic method
T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
Max(3, 5) // 5
Max("apple", "fig") // "fig"
// Generic class
public class Stack<T>
{
private readonly List<T> _items = new();
public void Push(T item) => _items.Add(item);
public T Pop()
{
if (_items.Count == 0) throw new InvalidOperationException("Empty stack");
var last = _items[^1];
_items.RemoveAt(_items.Count - 1);
return last;
}
public int Count => _items.Count;
}
// Generic constraints
// where T : class — reference type
// where T : struct — value type
// where T : new() — has parameterless constructor
// where T : BaseClass — inherits from BaseClass
// where T : IInterface — implements IInterface
// where T : unmanaged — unmanaged type (int, float, etc.)
// where T : notnull — non-nullable
public class Repository<T> where T : class, new()
{
public T Create() => new T();
}
// Multiple type parameters
public class KeyValuePair<TKey, TValue>
{
public TKey Key { get; }
public TValue Value { get; }
public KeyValuePair(TKey key, TValue value) { Key = key; Value = value; }
}
// Covariance (out) and contravariance (in)
// IEnumerable<out T> — can return Dog where Animal expected
// IComparer<in T> — can accept Animal comparer where Dog expected
Collections & LINQ
// List<T>
var list = new List<int> { 3, 1, 4, 1, 5 };
list.Add(9);
list.AddRange(new[] { 2, 6 });
list.Remove(1); // removes first 1
list.RemoveAt(0); // removes by index
list.Contains(4); // true
list.Sort(); // in-place
list.Count; // length
// Dictionary<TKey, TValue>
var dict = new Dictionary<string, int>
{
["alice"] = 30,
["bob"] = 25
};
dict["charlie"] = 28;
dict.ContainsKey("alice"); // true
dict.TryGetValue("dave", out int age); // safe get
foreach (var (key, val) in dict) { } // deconstruct
// HashSet<T>
var set = new HashSet<string> { "a", "b", "c" };
set.Add("d");
set.Contains("a"); // true
set.IntersectWith(other); // modifies in place
set.UnionWith(other);
set.ExceptWith(other);
// Queue and Stack
var queue = new Queue<int>();
queue.Enqueue(1);
int front = queue.Dequeue(); // FIFO
var stack = new Stack<int>();
stack.Push(1);
int top = stack.Pop(); // LIFO
// LINQ — query syntax
var result =
from x in list
where x > 2
orderby x descending
select x * 10;
// LINQ — method syntax (preferred)
list.Where(x => x > 2)
.OrderByDescending(x => x)
.Select(x => x * 10)
.ToList();
// Common LINQ methods
list.First() // throws if empty
list.FirstOrDefault() // 0 (default) if empty
list.FirstOrDefault(x => x > 4) // first match or default
list.Single() // throws if not exactly one
list.Last()
list.LastOrDefault()
list.Count() // total items
list.Count(x => x > 2) // conditional count
list.Any(x => x > 10) // true/false
list.All(x => x > 0) // true/false
list.Sum() // sum all
list.Sum(x => x * 2) // sum with transform
list.Average()
list.Min()
list.Max()
list.OrderBy(x => x) // ascending
list.OrderByDescending(x => x)
list.ThenBy(x => x.Name) // secondary sort
list.Select(x => x.ToString()) // transform
list.SelectMany(x => x.Children) // flatten nested
list.Where(x => x > 0).Skip(2).Take(5) // pagination
list.GroupBy(x => x % 2 == 0) // group even/odd
.ToDictionary(g => g.Key, g => g.ToList())
list.Distinct() // remove duplicates
list.Zip(other, (a, b) => a + b) // combine two sequences
// Aggregate
list.Aggregate((acc, x) => acc + x) // sum manually
list.Aggregate(seed: "", (acc, x) => acc + x) // with seed
// ToList / ToArray / ToDictionary
list.Where(x => x > 0).ToList()
list.ToDictionary(x => x, x => x * x) // {1:1, 2:4, ...}
Async / await
using System.Net.Http;
// Async method — always returns Task or Task<T>
public async Task<string> FetchAsync(string url)
{
using var client = new HttpClient();
string body = await client.GetStringAsync(url);
return body;
}
// Calling async code
// From async context:
string html = await FetchAsync("https://example.com");
// From sync entry point (avoid .Result / .Wait() — can deadlock)
// Use Task.Run or async Main:
static async Task Main(string[] args)
{
string html = await FetchAsync("https://example.com");
Console.WriteLine(html.Length);
}
// CancellationToken — cooperative cancellation
public async Task<string> FetchWithCancelAsync(string url, CancellationToken ct)
{
using var client = new HttpClient();
var response = await client.GetAsync(url, ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(ct);
}
// Usage with timeout
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
try
{
string html = await FetchWithCancelAsync(url, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Request timed out");
}
// Parallel async (run multiple tasks concurrently)
var task1 = FetchAsync("https://api.example.com/a");
var task2 = FetchAsync("https://api.example.com/b");
var task3 = FetchAsync("https://api.example.com/c");
string[] results = await Task.WhenAll(task1, task2, task3);
// First to finish
string first = await Task.WhenAny(task1, task2, task3)
.ContinueWith(t => t.Result.Result);
// ConfigureAwait(false) — avoid deadlocks in library code
await SomeAsync().ConfigureAwait(false);
// ValueTask — reduce allocation for hot paths
public async ValueTask<int> GetCachedAsync(int key)
{
if (_cache.TryGetValue(key, out var val)) return val;
return await LoadFromDbAsync(key);
}
// IAsyncEnumerable — async streams
public async IAsyncEnumerable<int> GenerateAsync()
{
for (int i = 0; i < 10; i++)
{
await Task.Delay(100);
yield return i;
}
}
await foreach (int num in GenerateAsync()) { }
Exception handling
// Basic try-catch-finally
try
{
int result = Divide(10, 0);
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Division error: {ex.Message}");
}
catch (ArgumentException ex) when (ex.ParamName == "b") // filter
{
Console.WriteLine("Argument b is invalid");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
throw; // rethrow (preserves stack trace)
}
finally
{
Console.WriteLine("Always runs");
}
// Custom exceptions
public class NotFoundException : Exception
{
public string ResourceType { get; }
public int Id { get; }
public NotFoundException(string resourceType, int id)
: base($"{resourceType} with id {id} not found")
{
ResourceType = resourceType;
Id = id;
}
// Chain inner exception
public NotFoundException(string resourceType, int id, Exception inner)
: base($"{resourceType} with id {id} not found", inner)
{
ResourceType = resourceType;
Id = id;
}
}
// Throw expression (C# 7+)
string name = input ?? throw new ArgumentNullException(nameof(input));
// Using declaration for IDisposable (auto-disposes at end of scope)
using var file = File.OpenRead("data.txt");
// file.Dispose() called automatically
// Traditional using block
using (var file = File.OpenRead("data.txt"))
{
// file.Dispose() called at end of block
}
Delegates, lambdas & events
// Delegates (typed function pointers)
delegate int MathOp(int a, int b);
MathOp add = (a, b) => a + b;
int sum = add(3, 4); // 7
// Built-in delegates (prefer these over custom delegates)
Func<int, int, int> multiply = (a, b) => a * b; // returns int
Action<string> print = msg => Console.WriteLine(msg); // returns void
Predicate<int> isEven = x => x % 2 == 0; // returns bool
// Lambda styles
Func<int, int> square = x => x * x; // single param, no braces
Func<int, int, int> addFn = (x, y) => x + y; // multiple params
Action doWork = () => Console.WriteLine("done"); // no params
Func<int, int> heavy = x => // block body
{
int result = x * x;
return result + 1;
};
// Events
public class Button
{
public event EventHandler? Clicked; // standard .NET event pattern
public event EventHandler<DataEventArgs>? DataReceived;
protected virtual void OnClicked() =>
Clicked?.Invoke(this, EventArgs.Empty); // null-safe invoke
public void Click() => OnClicked();
}
public class DataEventArgs : EventArgs
{
public string Data { get; }
public DataEventArgs(string data) => Data = data;
}
// Subscribe and unsubscribe
var btn = new Button();
btn.Clicked += (sender, e) => Console.WriteLine("Clicked!");
btn.Clicked += HandleClick;
void HandleClick(object? sender, EventArgs e) => Console.WriteLine("Handled");
btn.Clicked -= HandleClick; // unsubscribe to prevent memory leaks
Pattern matching
// Type patterns
object obj = "Hello";
if (obj is string s)
Console.WriteLine(s.ToUpper());
if (obj is string { Length: > 3 } longStr) // property pattern
Console.WriteLine(longStr);
// Switch expression patterns (C# 8+)
double ComputeArea(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle { Width: var w, Height: var h } => w * h,
Square s => s.Side * s.Side,
null => throw new ArgumentNullException(nameof(shape)),
_ => throw new ArgumentException("Unknown shape")
};
// Relational and logical patterns (C# 9+)
string Classify(int n) => n switch
{
< 0 => "negative",
0 => "zero",
> 0 and < 10 => "small positive",
>= 10 and < 100 => "medium",
_ => "large"
};
// Tuple patterns
string Describe(int x, int y) => (x, y) switch
{
(0, 0) => "origin",
(0, _) => "y-axis",
(_, 0) => "x-axis",
(> 0, > 0) => "first quadrant",
_ => "other"
};
// List patterns (C# 11+)
bool IsFirstTwo(int[] nums) => nums is [1, 2, ..];
// Positional (deconstruct) patterns
record Point(int X, int Y);
bool IsOrigin(Point p) => p is (0, 0);
File I/O
using System.IO;
// Read entire file
string text = File.ReadAllText("file.txt");
string[] lines = File.ReadAllLines("file.txt");
byte[] bytes = File.ReadAllBytes("image.png");
// Write entire file
File.WriteAllText("file.txt", "Hello, World!");
File.WriteAllLines("file.txt", new[] { "line 1", "line 2" });
// Append
File.AppendAllText("log.txt", $"{DateTime.Now}: entry\n");
// Async I/O (preferred in async code)
string text = await File.ReadAllTextAsync("file.txt");
await File.WriteAllTextAsync("file.txt", content);
// Stream-based (large files)
using var reader = new StreamReader("data.txt");
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
Console.WriteLine(line);
}
// Path operations
string dir = Path.GetDirectoryName("folder/file.txt"); // "folder"
string name = Path.GetFileName("folder/file.txt"); // "file.txt"
string ext = Path.GetExtension("folder/file.txt"); // ".txt"
string noExt = Path.GetFileNameWithoutExtension("f.txt"); // "f"
string full = Path.GetFullPath("../file.txt"); // absolute path
string combined = Path.Combine("folder", "sub", "f.txt"); // OS-safe join
// Directory operations
Directory.CreateDirectory("new/folder");
Directory.Exists("folder");
Directory.GetFiles("folder", "*.txt", SearchOption.AllDirectories);
Directory.Delete("folder", recursive: true);
// File checks
File.Exists("file.txt");
FileInfo info = new FileInfo("file.txt");
long bytes = info.Length;
DateTime modified = info.LastWriteTime;
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
string a == string b with == |
Value vs reference | Use string.Equals(a, b) or just == (strings override it) — actually == works for strings |
dict[key] when key missing |
KeyNotFoundException |
Use dict.TryGetValue(key, out var val) or dict.GetValueOrDefault(key) |
.Result or .Wait() in async |
Deadlock in UI/ASP.NET | Await all the way up; use async Main |
Mutating collection in foreach |
InvalidOperationException |
Copy first: foreach (var x in list.ToList()) |
Not disposing HttpClient |
Socket exhaustion | Use IHttpClientFactory or one static instance |
catch (Exception e) then ignore |
Swallows errors silently | Log the exception or rethrow |
new List<string> in default param |
Method signature error | Use null default and initialize in method body |
is check then cast (Type)obj |
Double type-check | Use if (obj is Type t) and use t |
Version quick reference
| Version | Key features |
|---|---|
| C# 6 | String interpolation, nameof, null-conditional ?. |
| C# 7 | Tuples, out vars, is patterns, local functions |
| C# 8 | Switch expressions, nullable reference types, using declaration |
| C# 9 | Records, init properties, top-level programs, target-typed new |
| C# 10 | Global usings, file-scoped namespaces, record structs |
| C# 11 | Raw string literals, list patterns, required members |
| C# 12 | Primary constructors for classes, collection expressions [1, 2, 3] |
| C# 13 | params collections, lock statement improvements |
FAQ
What is the difference between == and .Equals() in C#?
For strings, == compares values (it's overloaded), so "a" == "a" is true. For other reference types, == compares references (identity) by default, while .Equals() can be overridden for value equality. For custom classes, override both Equals and GetHashCode together. Records automatically get value-based ==.
When should I use struct vs class?
Use struct for small, immutable value objects (coordinates, colours, money amounts) that are frequently allocated — they live on the stack and have no GC overhead. Use class for anything else: objects with identity, mutable state, or reference semantics. As a rule of thumb: if it's under ~16 bytes and logically a value, consider struct.
What is the difference between IEnumerable<T>, ICollection<T>, and IList<T>?
IEnumerable<T> is the most general — supports iteration only (no count, no add). ICollection<T> adds Count, Add, Remove, Contains. IList<T> adds indexed access ([i]). Accept the most general interface your method needs; return the most concrete type you have.
Why does LINQ throw on First() but not FirstOrDefault()?
First() throws InvalidOperationException if the sequence is empty. FirstOrDefault() returns the default value (null for reference types, 0 for int, etc.). Use First() when an empty sequence is a bug; use FirstOrDefault() when empty is a valid state.
What is ConfigureAwait(false) and when should I use it?
By default, await captures the current synchronization context and resumes on it (e.g., the UI thread). ConfigureAwait(false) skips this — the continuation runs on any thread pool thread. Use it in library code to avoid deadlocks and improve performance. In ASP.NET Core there is no synchronization context, so ConfigureAwait(false) has no effect — but it's still good practice in libraries.
What is the difference between record, record class, and record struct?
record and record class are the same — a reference type with value-based equality, ToString(), and with expressions. record struct is a value type with the same features but stack-allocated. Use record for immutable DTOs, API responses, and domain value objects where equality by content matters.