Toolmingo
Guides16 min read

C# Cheat Sheet: Syntax, OOP, LINQ & Async

A complete C# cheat sheet — variables, strings, OOP, generics, LINQ, async/await, pattern matching, and common patterns with copy-ready examples.

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.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools