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.
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
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
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
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 →
| Feature | Version | Example | Lesson |
|---|---|---|---|
| Primary constructors on classes | C# 12 | class Svc(ILogger log) | → |
| Collection expressions | C# 12 | int[] a = [1, ..b]; | → |
| Alias any type | C# 12 | using Point = (int X, int Y); | → |
params collections | C# 13 | void M(params ReadOnlySpan<int> xs) | → |
System.Threading.Lock | C# 13 | lock (myLock) { } with a Lock object | → |
field keyword | C# 14 | get => field ?? "n/a"; | → |
| Extension members | C# 14 | extension(string s) { public bool IsBlank => ... } | → |
| Null-conditional assignment | C# 14 | obj?.Prop = value; | → |
| Lambda parameter modifiers | C# 14 | (ref int x) => x++ without types | → |
| Partial constructors and events | C# 14 | public partial Widget(); | → |
| User-defined compound assignment | C# 14 | public void operator +=(Vec v) | → |
| File-based apps | .NET 10 | dotnet 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.