Quick reference

C# cheat sheet

The syntax you reach for every day, current to C# 14 and .NET 10. Every block links to the lesson that explains it. Bookmark this; it is the page to have open while you code.

Sections
  1. Program structure
  2. Types and variables
  3. Strings
  4. Nullability
  5. Control flow
  6. Pattern matching
  7. Collections
  8. LINQ
  9. Methods
  10. Classes, records and structs
  11. Interfaces and generics
  12. Exceptions
  13. Async
  14. Delegates, lambdas and events
  15. Files and JSON
  16. New in C# 12, 13 and 14

Program structure

Top-level statements: a file with no class or Main is the entry point. Lesson →

using System.Text.Json;          // imports go at the top (many are implicit)

Console.WriteLine("Hello, .NET 10");

var total = Add(2, 3);           // local functions and types can follow
Console.WriteLine($"Total: {total}");

int Add(int a, int b) => a + b;

record Point(double X, double Y);

Create, run and test from the terminal: dotnet new console -n MyApp, dotnet run, dotnet test. A single .cs file can also be run directly with dotnet run app.cs. File-based apps →

Types and variables

Built-in types → · Variables and constants → · Conversion →

int count = 42;            long big = 9_000_000_000L;
double ratio = 0.75;       decimal price = 19.99m;     // decimal for money
bool ok = true;            char initial = 'A';
string name = "Ada";       object anything = 3.14;

var inferred = new List<string>();   // type inferred, still static
const double Pi = 3.14159;           // compile-time constant
readonly int _id;                    // set once, in ctor or declaration

int parsed = int.Parse("42");
bool valid = int.TryParse("x", out int result);   // false, result = 0
double d = count;            // implicit widening
int truncated = (int)ratio;  // explicit narrowing
string s = count.ToString(); object o = count;   // boxing

Strings

Immutable; every operation returns a new string. Lesson →

string greeting = $"Hello, {name}! You are {age:N0} years old.";  // interpolation
string path = @"C:\temp\file.txt";                                 // verbatim
string raw = """
    {"json": "no escaping needed"}
    """;                                                            // raw literal

name.Length; name.ToUpper(); name.Contains("da"); name.StartsWith("A");
name.Substring(1, 2); name.Replace("a", "o"); name.Trim(); name.Split(',');
string.Join(", ", parts); string.IsNullOrWhiteSpace(input);

var sb = new StringBuilder();        // for building in loops
sb.Append("a").AppendLine("b");
string built = sb.ToString();

ReadOnlySpan<char> slice = name.AsSpan(0, 2);   // no allocation

Nullability

Nullable reference types → · Null-safe programming →

string  mustExist = "x";     // never null (compiler-enforced with NRT on)
string? mayBeNull = null;    // may be null
int?    maybeInt  = null;    // Nullable<int>

int len = mayBeNull?.Length ?? 0;       // null-conditional + null-coalescing
mayBeNull ??= "default";                // assign if null
customer?.Address?.City = "Dubai";      // C# 14: null-conditional assignment

if (mayBeNull is not null) Use(mayBeNull);
if (maybeInt is { } value) Use(value);  // pattern unwraps
string forced = mayBeNull!;             // "trust me" - no runtime check

Control flow

Lesson →

if (x > 0) { } else if (x < 0) { } else { }
var label = x > 0 ? "positive" : "non-positive";

switch (day)
{
    case DayOfWeek.Saturday or DayOfWeek.Sunday: rate = 1.5; break;
    default: rate = 1.0; break;
}

for (int i = 0; i < 10; i++) { }
foreach (var item in items) { }
while (condition) { }
do { } while (condition);
break; continue; return;

Pattern matching

Pattern matching → · Switch expressions →

if (shape is Circle { Radius: > 10 } big) Console.WriteLine(big.Radius);

string Describe(object o) => o switch
{
    null                      => "nothing",
    int n when n < 0          => "negative int",
    int                       => "int",
    string { Length: 0 }      => "empty string",
    Point(0, 0)               => "origin",               // positional
    Point { X: var x, Y: 0 }  => $"on the x-axis at {x}", // property
    [1, .., 9]                => "list starting 1, ending 9", // list pattern
    _                         => "something else"
};

var status = code is >= 200 and < 300 ? "ok" : "error";   // relational + logical

Collections

Lists → · Dictionaries → · Collection expressions →

int[] nums = [1, 2, 3];                       // collection expression
List<string> names = ["Ada", "Linus"];
int[] merged = [..nums, 4, 5];                // spread
Span<int> span = [1, 2, 3];

names.Add("Grace"); names.Remove("Ada"); names.Count; names[0];
names.Contains("Linus"); names.IndexOf("Linus"); names.Sort();

var ages = new Dictionary<string, int> { ["Ada"] = 36 };
ages["Linus"] = 54;
if (ages.TryGetValue("Ada", out var age)) { }
foreach (var (person, years) in ages) { }     // deconstruct KeyValuePair

var unique = new HashSet<int>([1, 1, 2]);     // {1, 2}
var stack = new Stack<int>(); stack.Push(1); stack.Pop();
var queue = new Queue<int>(); queue.Enqueue(1); queue.Dequeue();

IEnumerable<int> lazy = Evens();              // iterator
IEnumerable<int> Evens() { for (int i = 0; ; i += 2) yield return i; }

LINQ

Queries are lazy until enumerated; call ToList() when you need the result more than once. LINQ → · Deferred execution →

var adults = people.Where(p => p.Age >= 18)
                   .OrderBy(p => p.LastName).ThenBy(p => p.FirstName)
                   .Select(p => new { p.FirstName, p.Age })
                   .ToList();

people.First(); people.FirstOrDefault(p => p.Age > 100);   // null if none
people.Single(p => p.Id == 7); people.Any(); people.All(p => p.Age > 0);
people.Count(); people.Sum(p => p.Age); people.Average(p => p.Age);
people.Max(p => p.Age); people.MaxBy(p => p.Age);           // element, not value
people.GroupBy(p => p.City).Select(g => new { City = g.Key, N = g.Count() });
orders.SelectMany(o => o.Lines);                            // flatten
people.Skip(20).Take(10); people.Distinct(); people.DistinctBy(p => p.Email);
people.Chunk(100);                                          // batches of 100
a.Zip(b); a.Concat(b); a.Union(b); a.Intersect(b); a.Except(b);

// query syntax - same thing
var q = from p in people where p.Age >= 18 orderby p.LastName select p.FirstName;

Methods

Methods → · ref, in, out →

int Add(int a, int b = 0) => a + b;             // optional parameter, expression body
Add(b: 2, a: 1);                                 // named arguments
int Sum(params int[] values) => values.Sum();    // variadic
int Sum(params ReadOnlySpan<int> values) { }    // C# 13: params spans, no allocation

void Swap(ref int a, ref int b) { (a, b) = (b, a); }
bool TryGet(out int value) { value = 1; return true; }
double Length(in Vector3 v) => v.Magnitude;     // read-only reference

(int Min, int Max) Range(int[] xs) => (xs.Min(), xs.Max());  // tuple return
var (lo, hi) = Range(nums);                                  // deconstruct

int Outer() { int Inner(int x) => x * 2; return Inner(21); } // local function

Classes, records and structs

Classes → · Records → · Structs → · Primary constructors →

public class Account(string owner)               // primary constructor
{
    private decimal _balance;
    public string Owner { get; } = owner;
    public decimal Balance
    {
        get => _balance;
        private set => _balance = value >= 0 ? value : throw new ArgumentOutOfRangeException();
    }
    public required string Currency { get; init; }   // must be set in initializer
    public string Label { get => field ?? Owner; set; }  // C# 14 field keyword

    public void Deposit(decimal amount) => Balance += amount;
    public override string ToString() => $"{Owner}: {Balance} {Currency}";
}

var acct = new Account("Ada") { Currency = "AED" };

public record Money(decimal Amount, string Currency);   // value equality, ToString, with
var fee = new Money(5, "AED");
var doubled = fee with { Amount = 10 };
var same = fee == new Money(5, "AED");                  // true

public readonly record struct Point(double X, double Y); // value type record

public abstract class Shape { public abstract double Area { get; } }
public sealed class Circle(double r) : Shape { public override double Area => Math.PI * r * r; }

public static class MathX { public static int Square(int x) => x * x; }  // static class
public enum Status { Draft, Active, Archived }

Interfaces and generics

Interfaces → · Generics → · Constraints →

public interface IRepository<T> where T : class
{
    Task<T?> GetAsync(int id, CancellationToken ct = default);
    Task AddAsync(T entity, CancellationToken ct = default);
    int Count => 0;                       // default implementation (C# 8+)
}

public class Cache<TKey, TValue> where TKey : notnull
{
    private readonly Dictionary<TKey, TValue> _items = [];
    public TValue GetOrAdd(TKey key, Func<TKey, TValue> factory)
        => _items.TryGetValue(key, out var v) ? v : _items[key] = factory(key);
}

T Max<T>(T a, T b) where T : IComparable<T> => a.CompareTo(b) >= 0 ? a : b;
T Zero<T>() where T : INumber<T> => T.Zero;     // generic math

IEnumerable<Animal> animals = new List<Dog>();  // covariance (out T)

Exceptions

Handling → · Creating and throwing →

try
{
    var text = await File.ReadAllTextAsync(path, ct);
}
catch (FileNotFoundException ex) when (ex.FileName is not null)
{
    logger.LogWarning(ex, "Missing {File}", ex.FileName);
}
catch (IOException)
{
    throw;                          // rethrow, stack trace preserved
}
finally
{
    Cleanup();                      // always runs
}

ArgumentNullException.ThrowIfNull(input);
ArgumentOutOfRangeException.ThrowIfNegative(count);

public sealed class InsufficientFundsException(decimal needed)
    : Exception($"Need {needed} more") { public decimal Needed { get; } = needed; }

using var file = File.OpenRead(path);    // disposed at end of scope
await using var conn = new SqlConnection(cs);

Async

Async and await → · Task.WhenAll → · Async streams →

public async Task<Order> LoadAsync(int id, CancellationToken ct)
{
    var order = await _db.Orders.FindAsync([id], ct);   // frees the thread while waiting
    return order ?? throw new KeyNotFoundException();
}

var (a, b) = await (FetchAAsync(), FetchBAsync());      // concurrent, both awaited
var all = await Task.WhenAll(ids.Select(LoadAsync));    // parallel fan-out
var first = await Task.WhenAny(t1, t2);

await Task.Delay(TimeSpan.FromSeconds(1), ct);
await Parallel.ForEachAsync(items, ct, async (item, token) => await ProcessAsync(item, token));

await foreach (var line in ReadLinesAsync(path).WithCancellation(ct)) { }  // IAsyncEnumerable

// Never:  task.Result / task.Wait()  (blocks; can deadlock or starve the pool)
// Never:  async void               (except event handlers)
// Always: pass the CancellationToken through

Delegates, lambdas and events

Delegates → · Lambdas → · Events →

Func<int, int, int> add = (a, b) => a + b;      // returns a value
Action<string> log = msg => Console.WriteLine(msg);
Predicate<int> isEven = n => n % 2 == 0;
Func<int> counter = () => ++count;              // closure over count

public delegate void Notify(string message);      // custom delegate type

public class Downloader
{
    public event EventHandler<ProgressEventArgs>? Progress;
    protected void OnProgress(int pct) => Progress?.Invoke(this, new(pct));
}
downloader.Progress += (s, e) => Console.WriteLine(e.Percent);
downloader.Progress -= handler;                   // unsubscribe to avoid leaks

Files and JSON

Files → · JSON →

string text = await File.ReadAllTextAsync(path);
string[] lines = await File.ReadAllLinesAsync(path);
await File.WriteAllTextAsync(path, text);
File.Exists(path); Directory.CreateDirectory(dir); Path.Combine(dir, "a.txt");
Path.GetExtension(path); Path.GetFileNameWithoutExtension(path);

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
string json = JsonSerializer.Serialize(order, options);
Order? back = JsonSerializer.Deserialize<Order>(json, options);
// source-generated, reflection-free:
[JsonSerializable(typeof(Order))] partial class AppJsonContext : JsonSerializerContext { }
JsonSerializer.Serialize(order, AppJsonContext.Default.Order);

New in C# 12, 13 and 14

Features you will meet in modern codebases, with the version they arrived in. What's new in C# 14 →

FeatureVersionExampleLesson
Primary constructors on classesC# 12class Svc(ILogger log)→
Collection expressionsC# 12int[] a = [1, ..b];→
Alias any typeC# 12using Point = (int X, int Y);→
params collectionsC# 13void M(params ReadOnlySpan<int> xs)→
System.Threading.LockC# 13lock (myLock) { } with a Lock object→
field keywordC# 14get => field ?? "n/a";→
Extension membersC# 14extension(string s) { public bool IsBlank => ... }→
Null-conditional assignmentC# 14obj?.Prop = value;→
Lambda parameter modifiersC# 14(ref int x) => x++ without types→
Partial constructors and eventsC# 14public partial Widget();→
User-defined compound assignmentC# 14public void operator +=(Vec v)→
File-based apps.NET 10dotnet run app.cs→

Need the words as well as the syntax? The glossary defines every term used here. Preparing for an interview? The interview guide turns these snippets into the questions you will be asked.