Open-source libraries used

1 libraries are bundled into this tool's code.

C# Cheatsheet — Quick Reference

C# 12 (with .NET 8) syntax, OOP, LINQ, and the most commonly used standard library—a cheatsheet covering about 80% of everyday scenarios.

C#

C# C# 12 (with .NET 8)

.NET (Core / 5+ / 8) · OOP, generics, functional, concurrent · Static, strong typing, nominal typing

Recommended Learning Path

First, get 'Hello World and Build Environment' running (dotnet new console) → familiarize yourself with variables, types, and control flow → process data with collections and LINQ → understand object-oriented programming (class/interface) → master async/await → finally look up files, networking, regex, and build/debugging as needed. The FAQ section is worth revisiting to avoid pitfalls.

1.Hello World and Build Environment

Create, run, and organize a .NET program from scratch: top-level statements, project files, namespaces, and command-line arguments.

Minimal Program

C# 9+ supports top-level statements: write executable code directly in Program.cs with no class and Main boilerplate. Console.WriteLine prints a line.

1
2
3
// Program.cs
Console.WriteLine("Hello, world!");
// Top-level statements (C# 9+): the compiler auto-generates the Main entry

Create and Run

The dotnet CLI is the command-line entry point for .NET: dotnet new creates a project, dotnet run compiles and runs, dotnet build only compiles.

1
2
3
// $ dotnet new console -n app
// $ cd app && dotnet run
// $ dotnet build // compile only, produces bin/Debug

Command-Line Arguments

In top-level statements, args is a string[] of command-line arguments, with user parameters starting at args[0]. Equivalent to the traditional Main(string[] args).

1
2
3
4
5
6
7
// Program.cs
if (args.Length > 0) {
Console.WriteLine($"Hello, {args[0]}!");
} else {
Console.WriteLine("No args");
}
// $ dotnet run -- Nick

Exit Code

Top-level statements can use return to specify an exit code: 0 means success, non-zero indicates an error category. Environment.ExitCode can also be read and written.

1
2
3
// Program.cs
return 0; // Top-level statements can return an int directly
// or Environment.ExitCode = 1;

Namespace

namespace organizes types to avoid collisions. File-scoped namespaces (C# 10) are declared with a semicolon, omitting braces and indentation.

1
2
3
4
5
6
namespace App.Utils; // File-scoped namespace (C# 10)
public class Helper {
public static int Twice(int x) => x * 2;
}
// Access: App.Utils.Helper.Twice(3)

using Directive

The using directive imports a namespace, removing the need for fully qualified names. Global usings in GlobalUsings.cs are shared across the project.

1
2
3
4
5
using System;
using System.Collections.Generic;
// GlobalUsings.cs
// global using System.Linq; // global using (C# 10)
// global using static System.Console;

Top-Level Statement Details

Top-level statements compile into the Main method of a Program class. Only one per project is allowed; use local functions for helpers.

1
2
3
4
5
Console.WriteLine("start");
int total = Sum(1, 2);
Console.WriteLine(total);
static int Sum(int a, int b) => a + b; // local function

csproj Project File

The csproj file is project configuration: TargetFramework, ImplicitUsings, and Nullable enable the nullable context.

1
2
3
4
5
6
7
8
9
<!-- app.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

2.Variables and Constants

Type inference, const/readonly, nullable types, null-coalescing, nameof, and scope.

var Type Inference

var lets the compiler infer the type from the initializer. Local variables can be inferred, but fields/properties cannot. Prefer var when the initializer's type is clear for brevity.

1
2
3
4
5
var count = 42; // int
var name = "Nick"; // string
var list = new List<int>(); // List<int>
var value = 3.14; // double
// `var` can only be used for local variables; fields must have an explicit type

const and readonly

const is a compile-time constant (primitive types); readonly is a runtime read-only field (assignable in a constructor). For static constants, prefer static readonly.

1
2
3
4
public const int MaxRetry = 3; // compile-time constant
public static readonly DateTime Epoch =
new DateTime(1970, 1, 1); // runtime read-only
// readonly fields can be assigned once, in the constructor

Nullable Types

Nullable<T> (e.g., int?) allows value types to be null; nullable reference types (? suffix + Nullable enable) let the compiler statically analyze null references.

1
2
3
4
int? maybe = null; // nullable value type
int real = maybe ?? 0; // null coalescing
string? name = FindName(); // nullable reference type
if (name is null) return; // compiler knows name is non-null afterward

Null-Coalescing and Null-Conditional

?? returns the right side when the left side is null; ?.> short-circuits to null when the left side is null. Chained ?. makes deep access null-safe.

1
2
3
4
5
string? s = null;
var result = s ?? "default"; // "default"
int? len = s?.Length; // null (no exception thrown)
var first = s?[0] ?? '-'; // '-'
// ?. combined with ?? : null-safe chained access + fallback

nameof Expression

nameof returns the symbol name as a string, automatically syncing on renames. Commonly used for parameter validation, property change notifications, and log tags.

1
2
3
4
5
public void SetAge(int age) {
if (age < 0)
throw new ArgumentOutOfRangeException(nameof(age));
}
var prop = nameof(Person.Name); // "Name"

Scope

C# uses braces to define block scope. Local variables can shadow fields with the same name (avoid for clarity); using declarations restrict resource scope to the block's end.

1
2
3
4
5
6
int x = 1;
{
int x = 2; // inner block shadows the outer local (hurts readability)
Console.WriteLine(x);
}
Console.WriteLine(x); // 1

init and Property Initialization

The init accessor allows assignment in object initializers and is read-only afterward. Property initializers set defaults; the set accessor can add validation.

1
2
3
4
5
6
public class Person {
public string Name { get; init; } = ""; // settable only during initialization
public int Age { get; set; }
}
var p = new Person { Name = "Nick", Age = 30 }; // OK
// p.Name = "X"; // compile error: read-only after init

Deconstruction

Tuples and records support deconstruction: use parentheses to split elements into multiple variables. The Deconstruct method customizes deconstruction logic.

1
2
3
4
5
var (sum, count) = (42, 7); // tuple deconstruction
var (name, age) = GetPerson(); // method returns a tuple
record Person(string Name, int Age);
var p = new Person("Nick", 30);
var (n, a) = p; // records support deconstruction out of the box

3.Data Types

Built-in types, nullable, enum, struct, generics, record, tuple, and type conversion.

Built-in Types

Built-in types like int/long/double/bool map to System types. var is just inference; the runtime type is unchanged.

1
2
3
4
5
6
7
8
int i = 42; // System.Int32
long l = 42L;
double d = 3.14;
float f = 3.14f;
decimal m = 19.99m; // high-precision decimal
bool b = true;
char c = 'A';
string s = "hi";

Nullable Types

int? is a nullable value type (Nullable<int>); access via HasValue/Value. With Nullable enable, the compiler statically checks nullable reference types.

1
2
3
4
5
int? x = null;
if (x.HasValue) { int v = x.Value; }
int y = x ?? 0; // null coalescing
string? name = null; // nullable reference type
// With nullable reference types enabled, direct access to `name` warns

enum

enum defines named integer constants, defaulting to int. The Flags attribute enables bit combinations to be tested with HasFlag or bitwise operations.

1
2
3
4
5
6
enum Color { Red, Green, Blue } // default values 0, 1, 2
[Flags]
enum Perm { Read = 1, Write = 2, Exec = 4 }
var p = Perm.Read | Perm.Write;
if ((p & Perm.Write) != 0) { } // bit test
var c = (Color)1; // Green

struct

struct is a value type: assignment copies the whole block, allocated on the stack, and cannot be null (boxed when made nullable). Use struct for small, immutable data.

1
2
3
4
5
6
struct Point {
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
}
Point a = new(1, 2);
Point b = a; // value copy: changes to b do not affect a

Class (Reference Type)

class is a reference type: assignment shares the same object, GC manages memory, can be null. Use class for mutable state.

1
2
3
4
5
6
class Counter {
public int Value { get; set; }
}
Counter a = new() { Value = 1 };
Counter b = a; // reference copy: both share the same object
b.Value = 99; // a.Value is also 99

Generics

Generics parameterize types, generating strongly typed code at compile time without runtime boxing. T is the type parameter; the where clause adds constraints.

1
2
3
4
5
public class Box<T> {
public T Value { get; set; }
}
var b = new Box<int> { Value = 42 };
// Constraint: where T : class, new()

record Type

record (C# 9) is a reference type with value semantics: built-in value equality, ToString, deconstruction, and with expressions. Suited for DTOs and immutable data.

1
2
3
4
5
record Person(string Name, int Age);
var p1 = new Person("Nick", 30);
var p2 = p1 with { Age = 31 }; // non-destructive mutation
bool eq = p1 == p2; // false (compared by value)
var (name, age) = p1; // deconstruction

Tuple

Tuples pack multiple values and support named elements. Most convenient for returning multiple values or quick aggregations; use tuples short-term, record long-term.

1
2
3
4
var t = (sum: 42, count: 7); // named tuple
Console.WriteLine(t.sum);
(int, int) swap((int a, int b) t) => (t.b, t.a);
var (a, b) = (1, 2);

Type Conversion

is safely checks type, as safely casts (returns null on failure), (T) is a hard cast (throws on failure), Convert/Parse explicitly parses.

1
2
3
4
5
6
object o = "hello";
if (o is string s) { } // pattern matching + conversion
var str = o as string; // null or string
// var bad = (int)o; // throws InvalidCastException
int n = int.Parse("42"); // explicit parse
bool ok = int.TryParse("42", out int v); // safe parse

Pattern Matching

is/switch use pattern matching: type patterns, property patterns, positional patterns. Replaces lots of if + cast, expressing intent more clearly.

1
2
3
4
5
6
7
string Describe(object o) => o switch {
int n when n > 0 => "positive",
int n => "integer",
string s when s.Length > 3 => "long string",
null => "null",
_ => "other", // fallback
};

4.References and Arrays

Value types vs reference types, arrays, Span, index/range, ref/in/out, and unsafe pointers.

Value Type vs Reference Type

Value types (struct/enum/primitives) are copied on assignment; reference types (class/interface) share the object on assignment. This is C#'s most important mental model.

1
2
3
4
5
6
7
8
// struct value copy: b is an independent copy
Point a = new(1, 2);
Point b = a;
b.X = 99; // a.X is still 1
// class reference is shared
var c1 = new Counter();
var c2 = c1;
c2.Value = 5; // c1.Value is also 5

Arrays

Arrays are fixed-length reference types, accessed with [] indexing. Multidimensional and jagged arrays differ: [,] is rectangular, [][] is jagged. Arrays use Length, not Count.

1
2
3
4
5
6
int[] arr = { 1, 2, 3 };
int first = arr[0];
arr[2] = 99;
int len = arr.Length; // 3
int[,] rect = new int[2, 3]; // 2D rectangular array
int[][] jag = new int[2][]; // jagged array

Index and Range

^ indexes from the end (^1 is the last), .. denotes a range (1..^1 excludes first and last). Modern syntax for slicing arrays/Lists.

1
2
3
4
5
int[] arr = { 1, 2, 3, 4, 5 };
int last = arr[^1]; // 5
int secondLast = arr[^2]; // 4
int[] mid = arr[1..^1]; // { 2, 3, 4 }
int[] tail = arr[2..]; // { 3, 4, 5 }

Span and Memory

Span<T> is a read-only view over any contiguous memory, allocation-free and sliceable, the core of high-performance data processing. Can point to stack or heap memory.

1
2
3
4
5
int[] arr = { 1, 2, 3, 4 };
Span<int> s = arr;
Span<int> part = s[1..3]; // slice, no copy
part[0] = 99; // mutates the original array
// Also supports stack memory: Span<int> st = stackalloc int[4];

ref / in / out Parameters

ref passes by reference (read-write), in passes by reference (read-only), out is for return values (callers don't need to initialize first). Pass-by-reference avoids copying value types.

1
2
3
4
5
6
7
void Increment(ref int x) => x++;
void Init(out int x) => x = 42;
int ReadOnly(in int x) => x;
int v = 1;
Increment(ref v); // v = 2
Init(out v); // v = 42
ReadOnly(in v); // read-only reference

unsafe Pointers

unsafe context allows real pointers (e.g., int*); requires AllowUnsafeBlocks in csproj. Use only for native interop; avoid in ordinary code.

1
2
3
4
5
6
unsafe {
int x = 42;
int* p = &x; // take address
Console.WriteLine(*p); // dereference
}
// csproj: <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

Memory and Buffers

Memory<T> is the heap-safe version of Span, storable in fields and usable across async. ReadOnlyMemory is the read-only view. Used for async buffered data processing.

1
2
3
4
byte[] data = GetBytes();
Memory<byte> mem = data; // can be stored across async calls
ReadOnlyMemory<byte> rom = mem; // read-only view
// Span can't be stored as a field / across await; Memory can

Copy Semantics

Value types are passed by value by default (large structs incur copy overhead); objects (references) are passed by reference. Use ref only when you need to modify in place.

1
2
3
4
5
struct Big { public long A, B, C, D; }
void PassByValue(Big b) { } // copies the whole struct
void PassByRef(ref Big b) { } // by reference, zero copy
var x = new Big();
PassByRef(ref x); // the original object is mutated

5.Control Flow

if/else, for/foreach/while, switch expressions, break/continue, and throw expressions.

if / else

if/else branches on a condition. C# uses == for comparison, && and || for short-circuit. Braces can be omitted for a single statement but it's recommended to keep them.

1
2
3
4
5
6
7
if (score >= 90) {
grade = "A";
} else if (score >= 60) {
grade = "B";
} else {
grade = "F";
}

for Loop

Classic for: initializer, condition, step. Use when you need an index; foreach is safer in most cases.

1
2
3
4
for (int i = 0; i < 10; i++) {
Console.WriteLine(i); // 0..9
}
for (int i = arr.Length - 1; i >= 0; i--) { } // reverse order

foreach Iteration

foreach iterates collections without an index, with compile-time safety. Combined with var and LINQ, it's the most common way to process data.

1
2
3
4
5
foreach (var item in items) {
Console.WriteLine(item);
}
// When you need the index, use `for` or `Enumerable.Select`
foreach (var (i, item) in items.Select((x, i) => (x, i))) { }

while / do-while

while checks first then executes; do-while executes at least once. Use when reading streams until EOF or for unknown loop counts.

1
2
3
4
5
6
int i = 0;
while (i < 5) { i++; } // test first
string? line;
do {
line = Console.ReadLine();
} while (line != null); // runs at least once

switch Expression

The switch expression (C# 8) uses => to return a value, replacing long if-else chains. Type patterns combined with when guards are powerful.

1
2
3
4
5
6
7
8
9
10
11
string Describe(int n) => n switch {
0 => "zero",
1 => "one",
_ => "other", // fallback
};
// Type pattern: switch dispatches on the runtime type
string Type(object o) => o switch {
int => "int",
string => "string",
_ => "other",
};

break and continue

continue skips to the next iteration; break exits the loop; return exits the method. Use goto to escape from nested loops.

1
2
3
4
5
6
7
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue; // skip even numbers
if (i > 7) break; // exit the loop
}
// Break out of two nested loops
goto Exit;
Exit: ;

Ternary Operator

cond ? a : b expresses an if/else assignment in one line. Branch expression types must be compatible (convertible to a common type).

1
2
var grade = score >= 60 ? "pass" : "fail";
var label = score switch { >= 90 => "A", _ => "other" };

throw Expression

throw can be used as an expression on the right side of ?? and ?:, and as a single-line method body. Throwing ArgumentNullException is concise for parameter validation.

1
2
3
string s = name ?? throw new ArgumentNullException(nameof(name));
int v = dict.TryGetValue(k, out var val) ? val :
throw new KeyNotFoundException(k);

6.Functions and Methods

Method signatures, parameter passing, overloading, local functions, lambda, and expression-bodied members.

Method Definition

A method = return type + name + parameter list + body. Returning void means no return value. Access modifiers control visibility.

1
2
3
4
5
6
7
public int Add(int a, int b) {
return a + b;
}
public void Say(string msg) {
Console.WriteLine(msg); // void returns nothing
}
private int twice = 0; // field

Parameter Passing

Pass by value by default: modifications inside the method don't affect the caller's variable. Objects are passed as a reference copy; modifying members affects the original.

1
2
3
4
5
6
void Set(int x) { x = 99; } // by value: the caller is unaffected
void Mutate(List<int> l) { l.Add(1); } // by reference: caller sees the change
var n = 1;
Set(n); // n is still 1
var list = new List<int>();
Mutate(list); // list is now [1]

Optional and Named Parameters

Optional parameters have default values (must be at the end of the parameter list); named arguments pass by name, skipping intermediate optional parameters.

1
2
3
4
5
6
void Greet(string name, string prefix = "Hello", int times = 1) {
Console.WriteLine($"{prefix}, {name}");
}
Greet("Nick"); // use defaults
Greet("Nick", "Hi");
Greet("Nick", times: 3); // named argument skips `prefix`

ref / out / in

ref passes by reference (read-write); out is for output only (no pre-initialization needed); in passes by reference read-only (avoids large value-type copies).

1
2
3
4
5
6
7
void Ref(ref int x) => x++;
void Out(out int x) => x = 42;
void In(in int x) { /* read-only */ }
int v = 1;
Ref(ref v); // v = 2
Out(out v); // v = 42
In(in v); // zero-copy read-only

Method Overloading

Overloading = same method name with different parameter lists (count/type). The compiler picks the best match based on arguments. Return type doesn't participate in overload resolution.

1
2
3
4
int Parse(string s) => int.Parse(s);
int Parse(int x) => x; // overload: different parameter type
long Parse(string s, int radix) => // overload: different number of parameters
Convert.ToInt64(s, radix);

Expression-Bodied Members

When a method/property/constructor is a single expression, use => as shorthand. Similar to lambda, but expression-bodied members are real members.

1
2
3
4
5
public int Square(int x) => x * x;
public string Name => "Nick"; // read-only property
public void Reset() => Count = 0;
// Constructors can use it too:
class Box { public Box(string s) => Label = s; string Label { get; } }

Local Functions

Functions defined inside a method can access outer variables (closures); commonly used for recursive helpers or iterator internals.

1
2
3
4
5
int Factorial(int n) {
int Helper(int x) => x <= 1 ? 1 : x * Helper(x - 1);
return Helper(n);
}
// `Helper` is only visible inside this method

Lambda Expression

Anonymous function: arguments => expression. Used with delegate/Func/Action, this is LINQ's core syntax.

1
2
3
4
5
6
7
8
Func<int, int> square = x => x * x;
Func<int, int, int> add = (a, b) => a + b;
Action<string> print = msg => Console.WriteLine(msg);
// Multi-line body:
Func<int, int> twice = (x) => {
int r = x * 2;
return r;
};

Delegate

A delegate is a method type: declare a signature, subscribe multiple methods with +=, invoking the delegate triggers them in order. Events are based on delegates.

1
2
3
4
5
delegate void Notify(string msg);
void Log(string m) => Console.WriteLine(m);
Notify n = Log; // delegate points at the method
n += m => Console.WriteLine("!" + m);
n("hi"); // invokes every subscriber in order

7.Strings

String literals, interpolation, common methods, mutable StringBuilder, and formatting.

String Literals

Double-quoted regular strings, @ verbatim strings (escape sequences are not processed), $ interpolated strings, and $$ composite literals (C# 11).

1
2
3
string s = "Line\n\tIndent"; // escape sequences
string raw = @"C:\Program Files\"; // verbatim: \ is not an escape
string path = "C:\\Program Files\\"; // equivalent regular form

String Interpolation

The $ prefix inlines the result of expressions in {} into the string; supports formatting and alignment. More readable than + concatenation.

1
2
3
4
5
int age = 30;
string name = "Nick";
var msg = $"{name} is {age} years old";
var pad = $"{name,10}"; // right-align in a 10-wide field
var money = $"{age:C}"; // currency format

Concatenation and Comparison

+ concatenates, string.Concat batches, string.Join uses separators. String equality uses == (value comparison), not the quote operator.

1
2
3
4
string a = "foo" + "bar"; // "foobar"
string all = string.Join(", ", names); // "a, b, c"
bool eq = a == "foobar"; // true (value comparison)
int cmp = string.Compare("a", "b"); // lexicographic comparison

Common Methods

Length, Substring, Contains, StartsWith, IndexOf, Replace, Trim, Split, ToUpper/ToLower cover the vast majority of string processing.

1
2
3
4
5
6
string s = " Hello, World ";
int len = s.Length; // 14
var sub = s.Substring(7, 5); // "World"
bool has = s.Contains("World"); // true
var rep = s.Replace("World", "C#");
var words = s.Trim().Split(","); // ["Hello", " World"]

StringBuilder

Use StringBuilder for heavy concatenation to avoid repeatedly creating strings (strings are immutable; + creates a new object each time). Especially noticeable in loops.

1
2
3
4
5
var sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.Append(i).Append(",");
}
string result = sb.ToString(); // materialize as a string in one shot

char

char is a single UTF-16 character. char.IsDigit/IsLetter/IsWhiteSpace checks categories. string is immutable; char is iterable.

1
2
3
4
5
6
char c = 'A';
bool digit = char.IsDigit(c); // false
bool upper = char.IsUpper(c); // true
foreach (char ch in "Hi") {
Console.WriteLine(ch); // 'H', 'i'
}

Formatting

string.Format / $ interpolation use format specifiers: D for integer, F for decimal, C for currency, P for percentage, X for hexadecimal.

1
2
3
4
5
double d = 1234.567;
Console.WriteLine($"{d:F2}"); // 1234.57
Console.WriteLine($"{d:C}"); // ¥1,234.57 (locale-dependent)
Console.WriteLine($"{42:D5}"); // 00042
Console.WriteLine($"{255:X}"); // FF

Parsing Strings

Parse converts a string to a number (throws on failure); TryParse is safe (returns bool + out result). Prefer TryParse.

1
2
3
4
int n = int.Parse("42"); // can throw
bool ok = int.TryParse("42", out int v); // safe
if (ok) Console.WriteLine(v);
// Also: int.Parse("ff", NumberStyles.HexNumber)

8.Collections and LINQ

Common collections like List/Dictionary/HashSet, IEnumerable, and chained LINQ queries.

List Dynamic Array

List<T> is a variable-length array: Add/Insert/Remove/IndexOf, O(1) index access. Iterate with foreach.

1
2
3
4
5
6
var list = new List<int> { 1, 2, 3 };
list.Add(4);
list.Insert(0, 0); // [0,1,2,3,4]
list.Remove(2);
int count = list.Count; // Length becomes Count
bool any = list.Contains(4);

Dictionary

Dictionary<K,V> maps keys to values with O(1) lookup. TryGetValue is safe for retrieval; iterate KeyValuePair.

1
2
3
4
5
6
7
8
var dict = new Dictionary<string, int>();
dict["Nick"] = 30;
if (dict.TryGetValue("Nick", out int age)) {
Console.WriteLine(age);
}
foreach (var kv in dict) {
Console.WriteLine($"{kv.Key}: {kv.Value}");
}

HashSet

HashSet<T> has no duplicates and O(1) Contains checks. UnionWith/IntersectWith/ExceptWith perform set operations for dedup and merging.

1
2
3
4
5
6
var set = new HashSet<int> { 1, 2, 3 };
set.Add(3); // already present, no effect
bool has = set.Contains(2);
set.UnionWith(new[] { 3, 4 }); // {1,2,3,4}
var common = new HashSet<int> { 2 };
common.IntersectWith(set); // {2}

Queue and Stack

Queue<T> is FIFO (Enqueue/Dequeue); Stack<T> is LIFO (Push/Pop). Use for task queues or undo stacks.

1
2
3
4
5
6
var q = new Queue<int>();
q.Enqueue(1); q.Enqueue(2);
int next = q.Dequeue(); // 1
var s = new Stack<int>();
s.Push(1); s.Push(2);
int top = s.Pop(); // 2

IEnumerable and Laziness

IEnumerable<T> is a read-only sequence interface; LINQ uses it for lazy evaluation: values are computed when iterated. Arrays/Lists/Dictionaries all implement it.

1
2
3
4
IEnumerable<int> nums = GetNums(); // lazy: not computed yet
int sum = nums.Where(n => n > 0)
.Sum(); // evaluates only when iterated
// ToList()/ToArray() materializes and executes everything immediately

LINQ Filtering and Projection

Where filters, Select projects (maps), OrderBy sorts, Distinct dedupes. Method chains are readable and lazy.

1
2
3
4
5
var adults = people
.Where(p => p.Age >= 18) // filter
.OrderByDescending(p => p.Age) // sort
.Select(p => p.Name) // project
.ToList(); // materialize

LINQ Aggregation

Count/Sum/Average/Min/Max aggregate; Any/All test; First/Single pick an element (throw on no match; FirstOrDefault returns default).

1
2
3
4
5
6
7
int[] nums = { 1, 2, 3, 4 };
int sum = nums.Sum(); // 10
double avg = nums.Average(); // 2.5
bool anyEven = nums.Any(n => n % 2 == 0); // true
bool allPos = nums.All(n => n > 0); // true
int first = nums.First(n => n > 2); // 3
int? maybe = nums.FirstOrDefault(n => n > 9); // null

GroupBy

GroupBy groups by key, producing IGrouping sequences; commonly used for statistics (count, sum by category).

1
2
3
4
5
6
7
8
var stats = orders
.GroupBy(o => o.Region)
.Select(g => new {
Region = g.Key,
Total = g.Sum(o => o.Amount),
Count = g.Count(),
})
.ToList();

LINQ Query Syntax

The from/where/orderby/select query syntax is declarative for method chains and is equivalent after compilation. A matter of readability preference.

1
2
3
4
5
6
7
8
var adults = from p in people
where p.Age >= 18
orderby p.Age descending
select p.Name;
// Equivalent method chain:
var same = people.Where(p => p.Age >= 18)
.OrderByDescending(p => p.Age)
.Select(p => p.Name);

9.Memory and Resource Management

Automatic GC, IDisposable and using to release unmanaged resources, weak references, and object pools.

GC Garbage Collection

C# memory is managed by the GC automatically: heap objects are collected when no longer referenced. Generational collection (0/1/2) optimizes performance.

1
2
3
var obj = new object(); // heap allocation
// GC reclaims it automatically once unreachable; no manual `free` needed
GC.Collect(); // force collection (generally avoid calling manually)

IDisposable Interface

Classes that hold unmanaged resources (files/network/database connections) implement IDisposable, releasing resources in Dispose.

1
2
3
4
5
6
7
class FileWriter : IDisposable {
private StreamWriter? _sw;
public void Dispose() {
_sw?.Dispose(); // release unmanaged resources
}
}
using (var w = new FileWriter()) { } // auto Dispose

using Statement and Declaration

using ensures Dispose is called at the end of the scope (finally semantics). The using declaration (C# 8) automatically releases resources when the block ends.

1
2
3
4
5
using var reader = new StreamReader("file.txt");
string line = reader.ReadLine();
// Automatically disposed at end of scope; no explicit call needed
// Equivalent traditional form:
using (var r = new StreamReader("file.txt")) { }

Finalizer

The destructor ~Class() is the finalizer: called before GC reclaims the object, with no guaranteed timing. Use only for unmanaged resources; normally follow the Dispose pattern.

1
2
3
4
5
6
class Resource {
~Resource() {
// Called before GC, but timing is non-deterministic — don't rely on it
// Implement IDisposable and release explicitly instead
}
}

Weak Reference

WeakReference doesn't prevent the GC from collecting the target; used for caches (dictionary caching heavy objects, allowing reclamation). Target may become null at any time.

1
2
3
4
5
var weak = new WeakReference(new byte[1024]);
if (weak.IsAlive) {
var data = (byte[])weak.Target!; // may have already been collected
}
// After GC, IsAlive can become false

Object Pool and ArrayPool

For frequent large array allocations, use ArrayPool to reuse buffers and reduce GC pressure. Pair ArrayPool<T>.Shared.Rent with Return.

1
2
3
4
5
6
7
byte[] buf = ArrayPool<byte>.Shared.Rent(1024);
try {
// use buf
} finally {
ArrayPool<byte>.Shared.Return(buf);
}
// The rented array may be larger than requested; you must Return it after use

stackalloc Stack Allocation

stackalloc allocates memory on the stack: fast and doesn't trigger GC; suited for small temporary buffers. Stack space is limited, so use with caution.

1
2
3
4
5
6
int length = 4;
// stackalloc allocates on the stack: fast and does not trigger GC
Span<int> buffer = stackalloc int[length];
buffer[0] = 42;
foreach (var n in buffer) { Console.WriteLine(n); }
// Stack space is limited; use only for small temporary buffers

GC Memory Pressure

When allocating large amounts of native memory, use GC.AddMemoryPressure to inform the GC, prompting timely collection of managed objects and preventing uncontrolled memory growth.

1
2
3
4
5
6
7
8
9
10
var size = 64 * 1024 * 1024; // 64 MB
IntPtr buffer = System.Runtime.InteropServices.Marshal.AllocHGlobal(size);
GC.AddMemoryPressure(size); // tell the GC about the native memory pressure
try {
// use the native buffer (example: write a single byte)
System.Runtime.InteropServices.Marshal.WriteByte(buffer, 0, 1);
} finally {
System.Runtime.InteropServices.Marshal.FreeHGlobal(buffer);
GC.RemoveMemoryPressure(size); // remove the pressure after release
}

10.Object-Oriented Programming

Classes, properties, constructors, inheritance, polymorphism, interfaces, abstract classes, and access modifiers.

Class and Object

class defines a data type (reference type). Fields hold state, properties control access, methods define behavior, constructors initialize.

1
2
3
4
5
6
7
8
9
public class Person {
public string Name { get; set; } = "";
public int Age { get; set; }
public Person(string name, int age) {
Name = name; Age = age;
}
public string Greet() => $"Hi, {Name}";
}
var p = new Person("Nick", 30);

Properties

Properties are safe accessors for fields: get reads, set writes; access modifiers and validation logic can be added. The compiler generates backing fields.

1
2
3
4
5
6
7
8
9
10
private int _age;
public int Age {
get => _age;
set {
if (value < 0) throw new ArgumentOutOfRangeException();
_age = value;
}
}
// Auto-property:
public string Name { get; set; } = ""

Inheritance

C# supports single inheritance: class uses : to inherit a base class. A derived class is-a base class. Private members aren't inherited; protected members are accessible.

1
2
3
4
5
6
7
8
9
public class Animal {
public string Name { get; set; } = "";
public virtual void Speak() => Console.WriteLine("...");
}
public class Dog : Animal {
public override void Speak() => Console.WriteLine("Woof");
}
Animal a = new Dog();
a.Speak(); // "Woof" (polymorphism)

Polymorphism

virtual + override enables polymorphism: the method called via a base reference dispatches to the actual type. A method must be virtual to be overridden.

1
2
3
4
5
6
7
8
9
public class Shape {
public virtual double Area() => 0;
}
public class Circle : Shape {
public double R { get; set; }
public override double Area() => Math.PI * R * R;
}
Shape s = new Circle { R = 2 };
double area = s.Area(); // 12.57 (uses the actual runtime type)

Abstract Class

An abstract class can't be instantiated and may contain abstract methods (subclasses must implement). Abstract methods have no body. Used for template base classes.

1
2
3
4
5
6
7
8
9
public abstract class Shape {
public abstract double Area(); // no implementation
public void Describe() =>
Console.WriteLine($"Area: {Area()}");
}
public class Square : Shape {
public double Side { get; set; }
public override double Area() => Side * Side;
}

Interface

interface defines a contract: members have no implementation; implementing classes must provide them. C# supports multiple interface implementation (an alternative to multiple inheritance).

1
2
3
4
5
6
7
8
public interface ILogger {
void Log(string msg);
}
public class ConsoleLogger : ILogger {
public void Log(string msg) => Console.WriteLine(msg);
}
ILogger logger = new ConsoleLogger();
logger.Log("hi"); // program to an interface

Access Modifiers

public is open, private is closed, protected is visible to derived classes, internal is visible within the assembly. Defaults: class is private, members are private.

1
2
3
4
5
6
public class Account {
public decimal Balance { get; private set; } // readable externally, writable internally
private int _transactions; // only this class
protected void Audit() { } // only derived classes
}
// internal: visible within the same assembly (default for top-level classes)

sealed and object Methods

sealed classes can't be inherited; sealed override methods can't be overridden further. All classes implicitly inherit object (ToString/Equals/GetHashCode).

1
2
3
4
5
6
7
8
public sealed class Config { } // inheritance is forbidden
public class Base {
public virtual void Run() { }
}
public class Child : Base {
public override void Run() { } // can be overridden
}
// If `Run` is `sealed override` in Child, grandchildren cannot override it

Static Class and Extension Methods

static class can only contain static members and can't be instantiated. Extension methods are static methods in a static class; a this parameter lets them be called on instances.

1
2
3
4
5
6
public static class StringExt {
public static bool IsEmpty(this string s) =>
string.IsNullOrEmpty(s);
}
string s = "";
bool empty = s.IsEmpty(); // call extension method using instance syntax

11.Exception Handling

try/catch/finally, exception types, custom exceptions, the cost of exceptions, and best practices.

try / catch / finally

try holds code that may fail; catch handles it; finally runs regardless of success or failure (cleanup). Exceptions propagate upward.

1
2
3
4
5
6
7
try {
int.Parse("abc");
} catch (FormatException ex) {
Console.WriteLine(ex.Message); // handle the error
} finally {
Console.WriteLine("cleanup"); // always runs
}

Multiple catch and Exception Filters

Multiple catch clauses match by type, with more specific ones first. when provides a filter condition. Catch without a variable ignores the exception object.

1
2
3
4
5
6
try {
Process();
} catch (FileNotFoundException ex) when (ex.FileName == "cfg")
{ /* filter: only when the filename matches */ }
catch (IOException ex) { /* more general exception */ }
catch { /* catch everything */ }

Throwing Exceptions

throw new throws an exception; throw; (no argument) rethrows as-is (preserving the stack). throw new inside a catch resets the stack.

1
2
3
4
5
6
throw new ArgumentException("invalid value", nameof(value));
try { ... }
catch (Exception ex) {
// throw; // preserve the stack trace
// throw ex; // resets the stack trace (drops info — avoid)
}

Custom Exceptions

Custom exceptions inherit from Exception (conventionally with an Exception suffix), provide constructors, and preserve the inner exception via InnerException.

1
2
3
4
5
6
7
public class ConfigException : Exception {
public ConfigException() { }
public ConfigException(string message) : base(message) { }
public ConfigException(string message, Exception inner)
: base(message, inner) { }
}
throw new ConfigException("bad config");

finally Cleanup

finally guarantees resource release/state restoration regardless of whether try throws. return runs finally first. using is its syntactic sugar.

1
2
3
4
5
6
7
try {
lockObj.Enter();
DoWork();
} finally {
lockObj.Exit(); // release the lock whether or not work succeeds
}
// `return` happens after `finally`, so `finally` always runs

Cost of Exceptions

Exception catching is expensive; don't use exceptions for control flow. For predictable errors, use return codes, TryXxx, or the Result pattern.

1
2
3
4
5
6
// Slow: using exceptions for control flow
bool ok1;
try { int.Parse(s); ok1 = true; }
catch { ok1 = false; }
// Fast: TryParse returns the result directly
bool ok2 = int.TryParse(s, out _);

Global Exception Handling

Use try/catch at the top level to catch unhandled exceptions and log them. In ASP.NET, use middleware (UseExceptionHandler) for unified handling.

1
2
3
4
5
6
7
try {
await RunAsync();
} catch (Exception ex) {
Log(ex); // log it
Environment.ExitCode = 1; // mark as failed
}
// ASP.NET Core: app.UseExceptionHandler(...)

InnerException Chain

After catching an exception, throw a new one passing the original as InnerException, preserving the full error chain—more valuable for log diagnosis.

1
2
3
4
5
6
7
try {
LoadConfig();
} catch (FileNotFoundException ex) {
// wrap the exception while preserving the original cause
throw new InvalidOperationException("config missing", ex);
}
// Diagnosing: walk ex.InnerException to trace back to the root cause

12.Files and I/O

Static File/Directory/Path utilities, Stream read/write, async I/O, and binary handling.

Reading and Writing Text Files

File.ReadAllText/WriteAllText read/write small files in one go; ReadAllLines returns a line array. Use StreamReader for large files.

1
2
3
4
5
string content = File.ReadAllText("in.txt");
File.WriteAllText("out.txt", content);
string[] lines = File.ReadAllLines("in.txt");
File.WriteAllLines("out.txt", lines);
// Encoding: File.ReadAllText(path, Encoding.UTF8)

StreamReader Line by Line

Large files are read line by line in a streaming manner, not consuming memory. StreamReader.ReadLine loops until null (end of file).

1
2
3
4
5
6
using var reader = new StreamReader("big.log");
string? line;
while ((line = reader.ReadLine()) != null) {
Process(line);
}
// `using` guarantees the reader is released when finished

Directory Operations

Directory creates/deletes/enumerates directories; Directory.GetFiles/EnumerateFiles find files. Use SearchOption for recursive enumeration.

1
2
3
4
5
6
Directory.CreateDirectory("data");
if (Directory.Exists("data")) { }
string[] files = Directory.GetFiles("data", "*.txt");
// Recursive:
var all = Directory.EnumerateFiles("data", "*",
SearchOption.AllDirectories);

Path Handling

Path joins/parses paths cross-platform: Combine, GetExtension, GetFileName, ChangeExtension. Don't handcraft path separators.

1
2
3
4
string dir = Path.Combine("data", "files");
string ext = Path.GetExtension("a.txt"); // ".txt"
string name = Path.GetFileName("/a/b.txt"); // "b.txt"
string changed = Path.ChangeExtension("a.txt", ".md");

Async I/O

async/await I/O doesn't block threads: ReadAllTextAsync/WriteAllTextAsync/Stream methods. Required for UI/server concurrency.

1
2
3
4
5
6
string text = await File.ReadAllTextAsync("in.txt");
await File.WriteAllTextAsync("out.txt", text);
// Available in async Main / async methods
using var reader = new StreamReader("big.log");
string? line;
while ((line = await reader.ReadLineAsync()) != null) { }

Binary Read/Write

BinaryWriter/BinaryReader read/write by type; MemoryStream is an in-memory stream. Common in network and file binary protocols.

1
2
3
4
5
6
7
using var ms = new MemoryStream();
using var w = new BinaryWriter(ms);
w.Write(42); w.Write("data");
ms.Position = 0;
using var r = new BinaryReader(ms);
int n = r.ReadInt32();
string s = r.ReadString();

Console I/O

Console.ReadLine reads a line, ReadKey reads a keystroke, WriteLine outputs. Redirect Console.In/Out streams for testability.

1
2
3
4
5
Console.Write("Name: ");
string? name = Console.ReadLine();
var key = Console.ReadKey(); // read a single key press
Console.WriteLine($"Hello {name} ({key.KeyChar})");
// Input redirection: dotnet run < input.txt

File Information

FileInfo/DirectoryInfo provide file metadata and methods; File.Exists checks existence; GetCreationTime returns timestamps.

1
2
3
4
5
6
7
var fi = new FileInfo("a.txt");
if (fi.Exists) {
long bytes = fi.Length;
DateTime created = fi.CreationTime;
fi.CopyTo("b.txt");
}
bool ok = File.Exists("a.txt");

13.Common Pitfalls (FAQ)

The most common pitfalls C# developers fall into: value vs reference, string equality, LINQ laziness, async/await deadlocks, and more.

String == vs Equal

C#'s string == compares by value (unlike Java). But == uses ordinal comparison; Equals can specify StringComparison for case-insensitive matching, etc.

1
2
3
4
5
// BAD: relying on default ordering; CJK / casing may not match expectations
bool bad = a == "nick";
// GOOD: explicitly specify the comparison rules
bool good = a.Equals("nick", StringComparison.OrdinalIgnoreCase);

Pass by Value vs Pass by Reference

Method parameters are passed by value by default: objects pass a reference copy (modifying members affects the original); value types copy. Forgetting this is a common bug.

1
2
3
4
5
6
7
8
9
// BAD: assuming List is passed by value and won't be mutated
void Bad(List<int> l) => l.Add(1);
var list = new List<int>();
Bad(list); // list is actually mutated
// GOOD: explicitly acknowledge reference passing, or use `ref` to express intent
void Good(ref List<int> l) => l.Add(1);
var list2 = new List<int>();
Good(ref list2);

LINQ Lazy Evaluation

LINQ queries are lazy: nothing is computed until enumerated. Passing IQueryable/IEnumerable out and enumerating later may cause side effects or stale data.

1
2
3
4
5
6
// BAD: LINQ is lazy; further mutations to `list` change the query result
var q = list.Where(n => n > 0);
list.Add(5); // q's result changes
// GOOD: materialize a snapshot
var snapshot = list.Where(n => n > 0).ToList();

async void and Deadlocks

Event handlers may use async void; ordinary methods must return Task. In sync contexts (UI/WinForms), .Result/.Wait() can deadlock.

1
2
3
4
5
6
// BAD: async void method — exceptions can't be caught, tests become flaky
async void Bad() { await Task.Delay(10); }
// GOOD: return Task; await propagates exceptions
async Task Good() { await Task.Delay(10); }
// In UI code, don't block on async methods via .Result

Swallowing and Rethrowing Exceptions

Catching without handling silently swallows; throw ex resets the stack, losing the original chain. Logs must preserve InnerException.

1
2
3
4
5
6
7
8
// BAD: swallow exceptions — impossible to debug when something goes wrong
catch (Exception ex) { }
// GOOD: log it or rethrow as-is
catch (Exception ex) {
_logger.LogError(ex, "failed");
throw; // preserve the stack trace
}

Unreleased Resources

Failing to release Stream/HttpClient/database connections leaks resources. Always use using with IDisposable, or call Dispose after use.

1
2
3
4
5
6
7
// BAD: Stream is not released
var sr = new StreamReader("a.txt");
string t = sr.ReadToEnd();
// GOOD: `using` guarantees release
using var good = new StreamReader("a.txt");
string t2 = good.ReadToEnd();

Floating-Point Comparison

float/double have imprecise binary representation; direct == comparison yields surprises. Use an epsilon tolerance or decimal for exact arithmetic.

1
2
3
4
5
6
// BAD: exact equality on floats is unreliable
bool bad = (0.1 + 0.2) == 0.3; // false
// GOOD: compare with a tolerance
bool good = Math.Abs((0.1 + 0.2) - 0.3) < 1e-9;
// For money, use decimal: 0.1m + 0.2m == 0.3m is true

Modifying Collections During Iteration

Adding/Removing a collection inside foreach throws InvalidOperationException. Collect elements to remove, then delete after iteration.

1
2
3
4
5
6
7
8
// BAD: calling Remove inside foreach throws
foreach (var x in list) {
if (x < 0) list.Remove(x);
}
// GOOD: collect first, then remove all at once
var toRemove = list.Where(x => x < 0).ToList();
foreach (var x in toRemove) list.Remove(x);

Nullable and Null Reference

With Nullable enabled, the compiler helps catch null references. Avoid the ambiguity of returning null directly; use ?? / ?. for fallbacks.

1
2
3
4
5
6
7
// BAD: nullable disabled + direct dereference
string s = GetMaybe();
Console.WriteLine(s.Length); // possible NullReferenceException
// GOOD: nullable annotation + null coalescing
string? maybe = GetMaybe();
Console.WriteLine((maybe ?? "").Length);

Local Time vs UTC

Store/transfer time in UTC (DateTimeKind.Utc); convert to local when displaying. Mixing Kinds silently miscalculates time.

1
2
3
4
5
6
7
// BAD: default Kind unspecified — round-tripping misbehaves
var now = DateTime.Now;
// GOOD: store explicitly as UTC
var utc = DateTime.UtcNow;
var local = utc.ToLocalTime();
// DateTimeOffset carries its own offset; recommended for cross-timezone scenarios

14.Concurrency and Async

Task-based async programming, Parallel parallelism, lock synchronization, and thread-safe collections.

async / await

async methods return Task; await yields the thread and resumes after completion. The thread is freed for other work in between—no blocking.

1
2
3
4
5
6
async Task<string> FetchAsync() {
// await yields the thread without blocking
var data = await httpClient.GetStringAsync(url);
return data;
}
string result = await FetchAsync(); // the caller awaits too

Task and Return Values

Task represents an async operation; Task<T> carries a return value. Task.Run puts synchronous work on the thread pool; ContinueWith chains.

1
2
3
4
5
Task<int> t = Task.Run(() => Compute());
int result = await t;
var t2 = Task.Run(async () => await FetchAsync());
// Task.WhenAll: wait for several concurrently
var all = await Task.WhenAll(t, t2);

Parallel

Parallel.For/ForEach use the thread pool to execute independent work in parallel. CPU-intensive tasks leverage multiple cores; mind thread safety and over-parallelism.

1
2
3
4
5
Parallel.For(0, 100, i => {
Process(i); // runs in parallel
});
Parallel.ForEach(items, item => Process(item));
// Cap the degree of parallelism: new ParallelOptions { MaxDegreeOfParallelism = 4 }

lock Synchronization

lock ensures only one thread enters the critical section at a time. Lock objects are usually private readonly fields; avoid locking on this.

1
2
3
4
5
6
7
8
private readonly object _lock = new();
int _counter = 0;
void Increment() {
lock (_lock) {
_counter++; // atomic: thread-safe
}
}
// `lock` is syntactic sugar for Monitor.Enter/Exit

Thread

Thread manually manages threads: Start to begin, Join to wait. Task/Parallel fit most scenarios; Thread is for long-running background tasks.

1
2
3
4
5
6
var thread = new Thread(() => {
Console.WriteLine("worker");
});
thread.IsBackground = true; // background thread: ends when the main thread ends
thread.Start();
thread.Join(); // wait for completion

CancellationToken

CancellationToken is cooperative cancellation: the token triggers IsCancellationRequested, and async methods throw OperationCanceledException.

1
2
3
4
5
6
7
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
var data = await httpClient.GetStringAsync(url,
cts.Token);
try { await SlowAsync(cts.Token); }
catch (OperationCanceledException) { /* canceled */ }
// cts.Token.ThrowIfCancellationRequested() — check proactively

Concurrent Collections

Thread-safe collections: ConcurrentDictionary, ConcurrentQueue, ConcurrentBag. Replace manually locking reads/writes on regular collections.

1
2
3
4
5
6
var dict = new ConcurrentDictionary<string, int>();
dict.TryAdd("a", 1);
dict.TryUpdate("a", 2, 1); // conditional update
var queue = new ConcurrentQueue<int>();
queue.Enqueue(1);
if (queue.TryDequeue(out int v)) { }

Async Best Practices

Async all the way: don't block on .Result. ConfigureAwait(false) in library code avoids returning to the sync context.

1
2
3
4
5
6
7
// End-to-end await chain
async Task<string> OuterAsync() {
var data = await InnerAsync().ConfigureAwait(false);
return data;
}
// Use ConfigureAwait(false) in libraries / non-UI layers
// UI event handlers keep the context (omit it)

15.Networking and HTTP

HttpClient for requests, JsonSerializer for serialization, WebSocket, and Socket.

HttpClient Basics

HttpClient sends HTTP requests. GetStringAsync for simple text, GetAsync for full responses. HttpClient should be reused long-term (singleton).

1
2
3
4
5
6
7
var http = new HttpClient();
string html = await http.GetStringAsync("https://example.com");
var resp = await http.GetAsync("https://example.com/api");
if (resp.IsSuccessStatusCode) {
string body = await resp.Content.ReadAsStringAsync();
}
// DI / IHttpClientFactory: reusing a single instance is recommended

POST and JSON

PostAsJsonAsync sends JSON; PostAsync sends custom content. Read responses with ReadAsStringAsync. JsonSerializer handles serialization.

1
2
3
4
5
6
7
var data = new { Name = "Nick", Age = 30 };
var resp = await http.PostAsJsonAsync(url, data);
// Read the JSON response:
var person = await resp.Content
.ReadFromJsonAsync<Person>();
// Manual serialization:
string json = JsonSerializer.Serialize(data);

Headers and Query Parameters

HttpRequestMessage sets Headers (auth/User-Agent); URI builds query strings. Note: some APIs reject the default User-Agent.

1
2
3
4
5
6
var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Add("Authorization", "Bearer token");
req.Headers.Add("User-Agent", "MyApp/1.0");
var uri = new UriBuilder(url);
uri.Query = "key=value&page=2"; // build the query string manually
var resp = await http.SendAsync(req);

System.Text.Json

JsonSerializer.Serialize/Deserialize handles JSON. JsonSerializerOptions configures casing, enums, and ignoring null.

1
2
3
4
5
6
7
var opts = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
string json = JsonSerializer.Serialize(obj, opts);
var back = JsonSerializer.Deserialize<T>(json, opts);

Calling Web APIs

Composition: build request → check status → deserialize. HttpStatusCode checks; ReadFromJsonAsync does it in one line.

1
2
3
4
5
6
7
using var resp = await http.GetAsync($"api/users/{id}");
if (resp.StatusCode == HttpStatusCode.NotFound) {
return null;
}
resp.EnsureSuccessStatusCode(); // non-2xx throws
var user = await resp.Content.ReadFromJsonAsync<User>();
return user;

WebSocket

ClientWebSocket establishes a full-duplex channel; SendAsync/ReceiveAsync send/receive. Ideal for real-time push (chat, quotes).

1
2
3
4
5
6
7
8
using var ws = new ClientWebSocket();
await ws.ConnectAsync(uri, CancellationToken.None);
var buffer = new byte[1024];
var result = await ws.ReceiveAsync(buffer,
CancellationToken.None);
string msg = Encoding.UTF8.GetString(buffer, 0, result.Count);
await ws.SendAsync(payload, WebSocketMessageType.Text,
true, CancellationToken.None);

DNS and Socket

Dns.GetHostAddresses resolves hostnames; Socket is the low-level transport. Most apps only need HttpClient; Socket is for custom protocols.

1
2
3
4
5
var ips = await Dns.GetHostAddressesAsync("example.com");
// Lower-level Socket:
using var socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync("example.com", 80);

Download and Stream Processing

Download large files via Stream for streaming writes, avoiding large memory use. HttpCompletionOption.ResponseHeadersRead returns the stream immediately.

1
2
3
4
5
using var resp = await http.GetAsync(url,
HttpCompletionOption.ResponseHeadersRead);
await using var stream = await resp.Content.ReadAsStreamAsync();
await using var file = File.Create("download.bin");
await stream.CopyToAsync(file); // stream to disk without buffering it all in memory

16.Date and Time

DateTime and TimeSpan, time zones and offsets, formatting and parsing.

DateTime Basics

DateTime represents a date-time: Now is local, UtcNow is UTC, Today is the date. AddDays/AddHours perform date arithmetic.

1
2
3
4
5
DateTime now = DateTime.Now;
DateTime utc = DateTime.UtcNow;
DateTime today = DateTime.Today; // today at 00:00
DateTime nextWeek = now.AddDays(7);
var str = now.ToString("yyyy-MM-dd HH:mm");

TimeSpan

TimeSpan represents an interval: get it by subtracting two times, build with FromDays/FromHours, decompose into Days/Hours.

1
2
3
4
5
TimeSpan duration = end - start;
double hours = duration.TotalHours; // total hours (including days)
int h = duration.Hours; // hours component only
var delay = TimeSpan.FromMinutes(5);
if (duration > TimeSpan.Zero) { }

DateTimeOffset

DateTimeOffset carries a time zone offset; recommended for cross-zone scenarios. Safe conversion to/from UTC, avoiding local-time ambiguity.

1
2
3
4
5
DateTimeOffset now = DateTimeOffset.Now;
DateTimeOffset utc = DateTimeOffset.UtcNow;
var offset = new DateTimeOffset(2024, 1, 1, 12, 0, 0,
TimeSpan.FromHours(8)); // +08:00
DateTime utcTime = now.ToUniversalTime();

DateTimeKind

DateTime has a Kind: Unspecified/Local/Utc. Local time without a specified Kind will misbehave when converting to UTC; store as Utc.

1
2
3
4
5
DateTime local = DateTime.Now; // Kind = Local
DateTime utc = DateTime.UtcNow; // Kind = Utc
DateTime utcFromLocal = local.ToUniversalTime();
// Avoid: new DateTime(...) defaults to Unspecified
// For storage / transport, always use UTC or DateTimeOffset

Parsing and Formatting

DateTime.Parse/ParseExact parse; TryParse is safe. Format tokens: yyyy/MM/dd, HH:mm:ss, ffff for milliseconds.

1
2
3
4
5
var d = DateTime.Parse("2024-01-01");
bool ok = DateTime.TryParse("2024-01-01", out var dt);
var exact = DateTime.ParseExact("01/02/2024",
"MM/dd/yyyy", CultureInfo.InvariantCulture);
string s = dt.ToString("O"); // ISO 8601, round-trip safe

Time Zone Conversion

TimeZoneInfo performs time zone conversion: FindSystemTimeZoneById, ConvertTimeFromUtc. Servers usually store UTC; convert to local for display.

1
2
3
4
5
DateTime utc = DateTime.UtcNow;
TimeZoneInfo shanghai =
TimeZoneInfo.FindSystemTimeZoneById("Asia/Shanghai");
DateTime local = TimeZoneInfo.ConvertTimeFromUtc(utc, shanghai);
// For a fixed offset, DateTimeOffset is simpler

Stopwatch Timing

Stopwatch is for high-precision timing (millisecond resolution). Start/Stop/Elapsed/ElapsedMilliseconds are the standard for performance measurement.

1
2
3
4
5
6
var sw = Stopwatch.StartNew();
DoWork();
sw.Stop();
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
// or sw.Elapsed.TotalMilliseconds
// Restart() reuses the same stopwatch

DateOnly and TimeOnly

DateOnly represents only a date; TimeOnly only a time (C# 10), free of time zone concerns—ideal for birthdays, calendars, and scheduling.

1
2
3
4
5
6
DateOnly today = DateOnly.FromDateTime(DateTime.Now);
DateOnly birthday = new(2000, 6, 15);
TimeOnly start = new(9, 30);
TimeOnly end = new(18, 0);
TimeSpan span = end - start; // 8.5 hours
Console.WriteLine(today); // prints today's date

17.Processes and System

Launching external processes, environment variables, paths, and system information.

Launching Processes

Process.Start launches an external program (shell command, other executable). ArgumentList passes arguments safely, avoiding injection from concatenation.

1
2
3
4
5
6
var psi = new ProcessStartInfo("git") {
ArgumentList = { "log", "-1" }, // argument array — no injection
RedirectStandardOutput = true, // capture stdout
};
using var proc = Process.Start(psi)!;
string output = await proc.StandardOutput.ReadToEndAsync();

Environment Variables

Environment.GetEnvironmentVariable reads; SetEnvironmentVariable writes (process-level). GetEnvironmentVariables returns all.

1
2
3
4
5
string? path = Environment.GetEnvironmentVariable("PATH");
Environment.SetEnvironmentVariable("MY_VAR", "value");
// Machine-wide:
Environment.SetEnvironmentVariable("MY_VAR", "v",
EnvironmentVariableTarget.Machine);

System Information

Environment provides system info: OSVersion, MachineName, CurrentDirectory, ProcessorCount, TickCount.

1
2
3
4
5
string os = Environment.OSVersion.ToString();
string machine = Environment.MachineName;
string dir = Environment.CurrentDirectory;
int cores = Environment.ProcessorCount;
string user = Environment.UserName;

Command-Line Parsing

args carries command-line arguments; Environment.GetCommandLineArgs gets the full set (including program name). For CLI tools, parse arguments.

1
2
3
4
5
6
7
// Top of Program.cs
if (args.Length == 0) {
Console.Error.WriteLine("usage: app <file>");
return 1;
}
// Treat arguments starting with `--` as options (implement yourself)
foreach (var a in args.Skip(1)) { /* handle */ }

Exit and Signals

Environment.Exit(1) exits immediately; ExitCode sets the exit code. The AppDomain.ProcessExit event runs cleanup.

1
2
3
4
5
AppDomain.CurrentDomain.ProcessExit += (s, e) => {
SaveState(); // clean up before exit
};
Environment.ExitCode = 2; // let the runtime exit naturally
// or call Environment.Exit(0) directly

Enumerating Processes

Process.GetProcesses enumerates system processes, reading Id/ProcessName/WorkingSet64, etc.; Kill terminates a process.

1
2
3
4
5
6
foreach (var p in Process.GetProcesses()) {
Console.WriteLine($"{p.Id}: {p.ProcessName}");
}
var current = Process.GetCurrentProcess();
long mem = current.WorkingSet64;
Process.GetProcessById(pid)?.Kill(); // terminate a specific process

Windows Registry

Microsoft.Win32.Registry reads/writes the registry (Windows only). GetValue/SetValue access keys; appropriate permissions are required.

1
2
3
4
5
6
7
8
using Microsoft.Win32;
// Read:
object? val = Registry.CurrentUser.OpenSubKey("Software\\App")
?.GetValue("Setting");
// Write:
using var key = Registry.CurrentUser.CreateSubKey("Software\\App");
key.SetValue("Setting", 42);
// Note: Windows-only; cross-platform apps should avoid depending on it

Application Path

AppContext.BaseDirectory is the program's running directory; Environment.ProcessPath is the current process path. Use them to locate resource files.

1
2
3
4
string baseDir = AppContext.BaseDirectory; // app's runtime directory
string? path = Environment.ProcessPath; // full path of the current process
string file = Path.Combine(baseDir, "data.json");
// Relative paths depend on the working directory, which the caller can affect — prefer BaseDirectory

18.Regular Expressions

Regex matching, capture groups, replacement, and common patterns.

Regex Basics

Regex.IsMatch tests; Matches finds all; Match finds the first. Use @ verbatim strings to avoid double-escaping.

1
2
3
4
5
6
var r = new Regex(@"^\d{3}-\d{4}$");
bool ok = r.IsMatch("123-4567");
foreach (Match m in Regex.Matches(text, @"\b\w+\b")) {
Console.WriteLine(m.Value);
}
// Verbatim `@` string: \d is the regex's \d, not a string escape

Capture Groups

Parentheses capture substrings; Groups[1] for indexed, named groups (?<name>...) via Groups["name"]. Used to extract fields.

1
2
3
4
5
6
7
var r = new Regex(@"(\d{4})-(\d{2})-(\d{2})");
var m = r.Match("date: 2024-01-01");
if (m.Success) {
string year = m.Groups[1].Value; // 2024
}
// Named group: Regex(@"(?<year>\d{4})-")
string y = m.Groups["year"].Value;

Replacement

Regex.Replace replaces with patterns; $1/$2 reference capture groups. Useful for formatting, masking, and cleaning text.

1
2
3
4
5
string masked = Regex.Replace(
phone, @"(\d{3})\d{4}(\d{4})", "$1****$2");
string cleaned = Regex.Replace(
text, @"[\s\t]+", " ");
// Replace callback: Regex.Replace(text, pat, m => Process(m.Value))

RegexOptions

RegexOptions.IgnoreCase ignores case, Multiline makes ^/$ match per line, Compiled speeds up repeated use.

1
2
3
4
5
var r = new Regex(@"^[a-z]+$",
RegexOptions.IgnoreCase | RegexOptions.Multiline);
// Compiled: only worthwhile when the pattern is reused many times
var fast = new Regex(@"pattern", RegexOptions.Compiled);
// IgnorePatternWhitespace allows comments inside the pattern

Common Patterns

Common regex patterns for email, URL, IP, phone numbers. Note: for complex validation (e.g., real email), use a dedicated library instead of regex.

1
2
3
4
var email = @"^[^@\s]+@[^@\s]+\.[^@\s]+$";
var ipv4 = @"^(?:\d{1,3}\.){3}\d{1,3}$";
var url = @"^https?://[^\s]+$";
// These are simple checks; use a dedicated library for production-grade validation

Quantifiers and Anchors

* zero or more, + one or more, ? zero or one, {n,m} specific count; ^ start of line, $ end of line, \b word boundary.

1
2
3
4
5
6
@"\d+" // one or more digits
@"colou?r" // color or colour
@"\d{2,4}" // 2 to 4 digits
@"^start" // starts with "start" (line start)
@"end$" // ends with "end"
@"\bword\b" // the standalone word "word"

Assertions and Lookaround

Zero-width assertions don't consume characters: (?<=...) lookbehind, (?=...) lookahead, (?!...) negative lookahead; match position conditions.

1
2
3
4
5
6
7
8
// Lookbehind (?<=@): extract the domain part after @
string domain = Regex.Match("[email protected]",
@"(?<=@)[^@]+").Value; // example.com
// Lookahead (?=\d): test whether a letter is immediately followed by a digit
bool hasDigit = Regex.IsMatch("a1", @"[a-z](?=\d)"); // true
// Negative lookahead (?!...): match a word that doesn't start with "world"
var first = Regex.Match("hello world",
@"\b(?!world)\w+").Value; // hello

Backtracking and ReDoS

Nested quantifiers and alternation cause backtracking; malicious input can trigger exponential matching (ReDoS). Limit timeouts or disable backtracking.

1
2
3
4
5
6
7
8
// Dangerous: nested quantifiers can cause exponential backtracking on long input
// var risky = new Regex(@"(a+)+$");
// Mitigation 1: always set a match timeout
var withTimeout = new Regex(@"(a+)+$", RegexOptions.None,
TimeSpan.FromSeconds(2));
// Mitigation 2: NonBacktracking disables backtracking (.NET 7+)
var safe = new Regex(@"(a+)+$", RegexOptions.NonBacktracking);
// For user-supplied regex or text, always add a timeout and length cap

19.Build and Debug

dotnet CLI, project configuration, debugging, logging, and conditional compilation.

dotnet CLI

dotnet build compiles, run runs, test tests, publish publishes. --configuration Release builds a release configuration.

1
2
3
4
5
// $ dotnet build # compile Debug
// $ dotnet run # compile and run
// $ dotnet test # run tests
// $ dotnet publish -c Release -o out
// $ dotnet clean # clear build outputs

csproj Configuration

csproj controls the build: TargetFramework, Nullable, ImplicitUsings, PackageReference dependencies, LangVersion.

1
2
3
4
5
6
7
8
9
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

Debugging and Logging

Debug.WriteLine only in Debug builds; Trace works in all builds. ILogger records log levels. Console.WriteLine for temporary debugging.

1
2
3
4
5
Debug.WriteLine($"value={x}"); // Debug builds only
Trace.TraceInformation("start"); // all builds
var logger = LoggerFactory.Create(b =>
b.AddConsole()).CreateLogger("App");
logger.LogInformation("Processing {Id}", id);

Conditional Compilation

#if DEBUG / #elif / #endif trims code by symbol. Project-level DefineConstants defines custom symbols.

1
2
3
4
5
6
#if DEBUG
Console.WriteLine("debug build");
#elif RELEASE
Console.WriteLine("release build");
#endif
// csproj:<DefineConstants>TRACE;MY_FLAG</DefineConstants>

Unit Tests

xUnit/NUnit/MSTest assert behavior. [Fact] is a test method; Assert.Equal asserts. dotnet test runs them.

1
2
3
4
5
6
7
[Fact]
public void Add_ReturnsSum() {
var calc = new Calculator();
var result = calc.Add(2, 3);
Assert.Equal(5, result);
}
// xUnit test class: public class CalculatorTests

NuGet Package Management

dotnet add package adds dependencies; restore restores them. PackageReference records versions in csproj.

1
2
3
4
// $ dotnet add package Newtonsoft.Json
// $ dotnet restore # restore dependencies
// $ dotnet list package # list dependencies
// Dependencies are recorded as PackageReference entries in the csproj

Publishing and Single File

dotnet publish produces a production version; PublishSingleFile packages a single file; SelfContained requires no runtime install.

1
2
3
4
5
6
7
// Publish a Release build to the `out` directory
// $ dotnet publish -c Release -o out
// Single-file (target machine needs .NET installed):
// $ dotnet publish -r win-x64 -p:PublishSingleFile=true -o out
// Self-contained (bundles the runtime — larger, but no install needed on target):
// $ dotnet publish -r win-x64 --self-contained true -o out
// Trim unused code to reduce size: -p:PublishTrimmed=true

Static Analyzers

Roslyn analyzers check code quality and potential defects at compile time; warnings can be escalated to errors to block problematic builds.

1
2
3
4
5
6
// Analyzers report code-quality and defect warnings at compile time
class AnalyzerDemo {
public int Add(int a, int b) => a + b;
}
// Treat warnings as errors: dotnet build -warnaserror
// Enable the built-in quality analyzers by setting AnalysisLevel to "latest" in the csproj

Official Links

Direct links to the official docs and resources.

About this Cheatsheet

This page is a self-contained cheatsheet for C# 12 (.NET 8), covering about 80% of the language core, common BCL types, and async programming in real-world projects. The content leans toward modern idioms: properties and auto-properties, LINQ, async/await, record types, pattern matching, nullable reference types. C# was introduced by Anders Hejlsberg in 2000 alongside the .NET platform, and is the core language of the Windows ecosystem, Unity game development, and backend services, emphasizing a harmony of type safety and productivity. 19 sections each focus on one topic: basic syntax, variables, types and reference/value semantics, control flow, functions, strings, collections, memory management (GC), object-oriented programming, error handling, I/O, common pitfalls, concurrency, networking, time, processes, regex, and build tools. Each subsection includes a concept introduction plus a directly copyable code snippet. All code and text are rendered locally in the browser; no data leaves your device. For authoritative references, see the official Microsoft Learn documentation.

Version 2.1.0