C# Cheat Sheet
Microsoft's typed, object-oriented language for apps, games (Unity), web APIs and tools on .NET. This sheet targets C# 14 with .NET 10 and marks features that need a recent version.
On this page
dotnet CLI #
| Command | What it does |
|---|---|
dotnet new console -n MyApp | New console app in the folder MyApp |
dotnet new webapi -n MyApi | New ASP.NET Core Web API project |
dotnet run | Build and run the project in the current folder |
dotnet run app.cs | Run a single file without a project (.NET 10+) |
dotnet watch | Run and restart on every file change |
dotnet build / dotnet publish -c Release | Compile / produce a release build to deploy |
dotnet add package Newtonsoft.Json | Add a NuGet package (.NET 10 also accepts dotnet package add) |
dotnet test | Run the tests (create a test project with dotnet new xunit) |
Program Structure #
| Code | What it does |
|---|---|
Console.WriteLine("Hi"); | A complete Program.cs: top-level statements, no Main needed (C# 9+) |
args | Command-line arguments, available in top-level code |
using System.Text.Json; | Import a namespace; global using imports it for every file (C# 10+) |
<ImplicitUsings>enable</ImplicitUsings> | .csproj setting: System, System.IO, System.Linq, System.Collections.Generic and more are imported for you |
namespace MyApp.Models; | File-scoped namespace for the whole file (C# 10+) |
static void Main(string[] args) { } | Classic entry point inside a class (static async Task Main for async) |
Types & Variables #
| Code | What it does |
|---|---|
int long double decimal bool char string | Built-in types; use decimal for money (19.99m): 0.1 + 0.2 == 0.3 is false for double |
var count = 5; | Type is inferred (still strongly typed) |
const int Max = 10; / readonly | Compile-time constant / field set only in the constructor |
int? age = null; | Nullable value type; check with age.HasValue or age ?? 0 |
string? name = null; | Nullable reference type: the compiler warns about unchecked use (<Nullable>enable</Nullable>, on in new projects) |
name! | Null-forgiving: "trust me, it is not null" |
struct enum int bool | Value types: copied on assignment |
class string int[] List<T> | Reference types: two variables can share one object |
(string Name, int Age) p = ("Ann", 30); | Tuple with named elements (p.Name); unpack with var (name, age) = p; |
int.Parse("42") / int.TryParse(s, out int n) | Text to number / safely, returns false on failure |
(int)3.9 / nameof(age) | Cast, truncates to 3 / variable name as a string |
Strings #
| Code | What it does |
|---|---|
$"Hi {name}, you are {age}" | String interpolation |
$"{price:F2}" / $"{n:N0}" / $"{d:yyyy-MM-dd}" | Format: 2 decimals / thousands separator / date |
@"C:\temp\file.txt" | Verbatim string: backslashes are literal |
"""She said "hi".""" | Raw string literal: no escaping needed (C# 11+) |
$$"""{"name": "{{name}}"}""" | Raw interpolated: {{ }} interpolates, single braces stay literal (C# 11+) |
s.Length / s[0] / s[^1] | Length / first / last character |
s[..3] / s.Substring(1, 3) | First 3 characters / 3 characters from index 1 |
s.ToUpper() / s.Trim() / s.Replace("a", "b") | Change case / strip whitespace / replace |
s.Contains("x") / s.StartsWith("x") / s.IndexOf("x") | Search (IndexOf returns -1 if missing) |
s.Split(',') / string.Join(", ", list) | Split into an array / join into a string |
string.IsNullOrWhiteSpace(s) | Null, empty or only spaces |
a.Equals(b, StringComparison.OrdinalIgnoreCase) | Compare ignoring case |
new StringBuilder().Append("a").ToString() | Build long strings efficiently in loops |
Operators & Patterns #
| Code | What it does |
|---|---|
user?.Address?.City | Null-conditional: null instead of NullReferenceException |
name ?? "unknown" / ok ? "yes" : "no" | Fallback when null / ternary |
name ??= "guest"; | Assign only if null |
customer?.Order = order; | Null-conditional assignment: only assigns if customer is not null (C# 14) |
obj is string s / obj as string | Type test and cast in one step / cast or null |
x is null / x is not null | Null checks that can't be fooled by an overloaded == |
n is > 0 and < 10 | Relational and logical patterns (C# 9+) |
p is { Age: >= 18, Name: "Ann" } | Property pattern |
nums is [1, 2, ..] | List pattern: starts with 1, 2 (C# 11+) |
7 / 2 / 7 % 2 / 7 / 2.0 | Integer division (3) / remainder (1) / 3.5 |
&& / || / ! | And / or / not (short-circuit) |
Control Flow #
string size = n switch // switch expression (C# 8+)
{
< 0 => "negative",
0 => "zero",
>= 1 and <= 9 => "small",
_ => "large", // _ = everything else
};
| Code | What it does |
|---|---|
if (a) { } else if (b) { } else { } | Conditions |
(a, b) switch { (0, 0) => "origin", _ => "other" } | Switch on several values with a tuple |
case > 100 when isVip: | switch statement case with a pattern and a guard |
for (int i = 0; i < 10; i++) { } | Counting loop |
foreach (var item in items) { } | Loop over any collection (dictionary: foreach (var (key, value) in dict)) |
while (x) { } / do { } while (x); | Condition loops (break, continue) |
Methods #
| Code | What it does |
|---|---|
int Add(int a, int b) => a + b; | Expression-bodied method |
void Greet(string name = "World") | Optional parameter; call with a named argument: Greet(name: "Ann") |
bool TryGet(int id, out User user) | out: method must set it; call with out var user |
void Swap(ref int a, ref int b) | ref: pass a variable by reference |
int Sum(params int[] nums) | Any number of arguments: Sum(1, 2, 3) |
params ReadOnlySpan<int> nums | params with spans, lists and other collections (C# 13+) |
Func<int, int> square = x => x * x; | Lambda that returns a value (Action<T> returns nothing) |
var add = (int a, int b = 1) => a + b; | Lambda with inferred type (C# 10+) and a default (C# 12+) |
static int WordCount(this string s) | Extension method in a static class: "a b".WordCount() |
extension(string s) { public int Words => s.Split(' ').Length; } | Extension block in a static class, also allows properties (C# 14) |
Classes & Interfaces #
public class Person(string name, int age) // primary constructor (C# 12+)
{
public string Name { get; } = name;
public int Age { get; private set; } = age;
public void Birthday() => Age++;
public override string ToString() => $"{Name} ({Age})";
}
Person p = new("Ann", 30); // target-typed new (C# 9+)
| Code | What it does |
|---|---|
public / private / protected / internal | Everywhere / this class / + subclasses / same project (members default to private) |
class Admin : Person | Inheritance; call the parent constructor with : base(...) |
virtual / override / abstract / sealed | Can be overridden / overrides / must be overridden / can't be inherited |
interface IShape { double Area(); } | Contract; implement with class Circle : IShape |
new Order { Id = 1, Total = 9.95m } | Object initializer: set properties after construction |
class Box<T> { public T Value { get; set; } } | Generic class: new Box<int>() |
T Max<T>(T a, T b) where T : IComparable<T> | Generic method with a constraint (also class, struct, new()) |
Records, Structs & Properties #
| Code | What it does |
|---|---|
public record Point(int X, int Y); | Immutable data type with value equality and nice ToString() (C# 9+) |
var p2 = p with { X = 5 }; | Copy with some values changed |
public record struct Point(int X, int Y); | Record as a value type (C# 10+) |
public struct Size { public int W; public int H; } | Small value type, copied on assignment |
public enum Color { Red, Green, Blue } | Named constants: Color.Red |
public int Age { get; set; } | Auto-property ({ get; private set; } = read-only from outside) |
public string Id { get; init; } | Settable only during object initialization (C# 9+) |
public required string Name { get; init; } | Caller must set it in the object initializer (C# 11+) |
public string FullName => $"{First} {Last}"; | Computed read-only property |
public string Name { get; set => field = value.Trim(); } | field keyword: access the hidden backing field (C# 14) |
Collections #
| Code | What it does |
|---|---|
int[] nums = [1, 2, 3]; | Array with a collection expression (C# 12+); new int[5] = 5 zeros, size: nums.Length |
List<string> names = []; | Growable list |
names.Add("Ann") / names.Remove("Ann") / names.Count | Add / remove / size |
names.Contains("Ann") / names.Sort() / names[0] | Search / sort in place / by index |
var ages = new Dictionary<string, int> { ["Ann"] = 30 }; | Key-value pairs |
ages["Bob"] = 25; | Add or overwrite (reading a missing key throws) |
ages.TryGetValue("Ann", out int a) / ages.ContainsKey("Ann") | Safe lookup / key check (also .Keys, .Values) |
HashSet<int> seen = [1, 2]; / seen.Add(3) | Unique values; Add returns false for duplicates |
int[] all = [.. first, .. second, 99]; | Spread collections into a new one (C# 12+) |
LINQ #
var names = people
.Where(p => p.Age >= 18)
.OrderBy(p => p.Name)
.Select(p => p.Name)
.ToList();
| Code | What it does |
|---|---|
.Where(x => x > 0) / .Select(x => x * 2) | Filter / transform each item |
.OrderBy(p => p.Age).ThenBy(p => p.Name) | Sort (OrderByDescending for reverse) |
.GroupBy(p => p.City) | Groups with g.Key and the items in g |
.First() / .FirstOrDefault() | First item / or default (null, 0) instead of an exception |
.Any(x => x > 5) / .All(x => x > 0) | Does any / every item match |
.Count() / .Sum() / .Average() / .Max() | Aggregates |
.Distinct() / .Take(5) / .Skip(10) | Unique items / first 5 / skip 10 |
.ToList() / .ToArray() / .ToDictionary(p => p.Id) | Run the query and store the result |
.CountBy(p => p.City) | Count per key (.NET 9+) |
from p in people where p.Age >= 18 select p.Name | Query syntax: same as Where + Select, SQL-like |
Async & Exceptions #
async Task<string> LoadAsync(string url)
{
using var http = new HttpClient();
return await http.GetStringAsync(url);
}
try { Console.WriteLine(await LoadAsync("https://example.com")); }
catch (HttpRequestException ex) { Console.WriteLine($"HTTP error {ex.StatusCode}"); }
catch (Exception ex) { Console.WriteLine(ex.Message); }
finally { Console.WriteLine("Done"); }
| Code | What it does |
|---|---|
Task / Task<T> | Async method with no result / with a result |
await Task.WhenAll(t1, t2) | Wait for several tasks running at the same time |
await Task.Delay(1000) | Wait 1 second without blocking |
await foreach (var x in GetItemsAsync()) | Loop over an IAsyncEnumerable<T> |
throw new ArgumentException("Bad id"); | Throw an exception |
throw; | Rethrow inside catch, keeping the stack trace |
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound) | Exception filter: only catch when the condition is true |
ArgumentNullException.ThrowIfNull(user); | Guard clause (.NET 6+) |
using var file = File.OpenRead(path); | Disposed automatically at the end of the scope |
class NotFoundException(string msg) : Exception(msg) { } | Your own exception type |
Useful .NET APIs #
| Code | What it does |
|---|---|
Console.ReadLine() / Console.Write("x") | Read a line of input / print without a newline |
File.ReadAllText(path) / File.WriteAllText(path, text) | Read / write a whole file (also ReadAllLines, AppendAllText, ...Async) |
File.Exists(path) / Directory.CreateDirectory(dir) | Check / create a folder (with parents) |
Path.Combine("data", "a.txt") / Path.GetExtension(path) | Build a path safely / ".txt" |
DateTime.Now / DateTime.UtcNow / DateTime.Today | Now (local) / now (UTC) / today at midnight |
date.AddDays(7) / date.ToString("dd/MM/yyyy") | Date math / format |
(end - start).TotalDays | Difference is a TimeSpan |
DateOnly / TimeOnly | Just a date / just a time (.NET 6+) |
Math.Round(x, 2) / Math.Max(a, b) / Math.Clamp(x, 0, 10) | Rounding / bigger value / limit to a range |
Math.Sqrt(x) / Math.Pow(2, 10) / Math.Abs(x) | Square root / power / absolute value |
Random.Shared.Next(1, 7) | Random int from 1 to 6 (upper bound is exclusive) |
JsonSerializer.Serialize(obj) / JsonSerializer.Deserialize<T>(json) | Object to JSON / back (System.Text.Json) |
Tips #
- Keep nullable reference types on and fix the warnings: they catch most
NullReferenceExceptions before you run the code. var x = [1, 2];doesn't compile: collection expressions need a target type, so writeint[] x = [1, 2];orList<int> x = [1, 2];.- LINQ runs lazily: the query executes when you loop over it or call
ToList(), and again every time you loop. - Never use
.Resultor.Wait()on a task in app code;awaitit, and useasync voidonly for event handlers. Math.Round(2.5)returns2(rounds to even); useMath.Round(x, MidpointRounding.AwayFromZero)for school rounding.
Nothing on this sheet matches “”.