iZONE

C# Cheatsheet

A practical C# guide covering syntax, OOP, LINQ, async programming, collections, .NET development, and modern language features.

Resources

What is C#?

C# is Microsoft's general-purpose language that runs on the .NET runtime. It's used for everything from Windows desktop apps to web APIs, mobile apps with MAUI, and games with Unity. That said — most developers pick it up for web or desktop work first.

where C# is used

web and APIsASP.NET Core
  • ASP.NET Core — the most popular C# web framework.
  • Used heavily in enterprise backends and REST APIs.
desktop appsWPF / MAUI
  • WPF and WinForms for Windows desktop applications.
  • MAUI for cross-platform desktop and mobile.
game developmentUnity
  • Unity uses C# as its scripting language.
  • One of the top two choices for indie and professional game dev.
.NET runtimeCLR handles the rest
  • C# compiles to IL (Intermediate Language), not machine code.
  • The CLR (Common Language Runtime) runs it — handles memory, threads, garbage collection.

your first C# program

CSharp

// modern style — C# 9+ (top-level statements)
// no class or Main needed
Console.WriteLine("Hello, World!");

// ─────────────────────────────────────────────
// classic style — works in all versions
using System;

namespace MyApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

comments and output

CSharp

// single-line comment

/*
  multi-line comment
  spans several lines
*/

/// <summary>
/// XML documentation comment — shown in
/// IntelliSense and generated docs.
/// </summary>
/// <param name="name">The user's name.</param>
/// <returns>A greeting string.</returns>
string Greet(string name) => $"Hello, {name}!";

// TODO: refactor this later
// shown as a task in Visual Studio

// output
Console.WriteLine("With newline");
Console.Write("Without newline ");
Console.Write("same line");

// formatted output
Console.WriteLine($"Score: {95 * 2}");
Console.WriteLine("Name: {0}, Age: {1}", "Alice", 28);

Variables & Data Types

C# is strongly typed — every variable has a type. Most of the time var lets the compiler figure it out for you. And if you're working with money, always use decimal. That's not a suggestion.

value types

CSharp

int    count   = 42;
long   bigNum  = 9_000_000_000L;
short  small   = 30_000;
byte   b       = 255;

float   f = 3.14f;          // needs f suffix
double  d = 3.14159265;     // default decimal type
decimal m = 19.99m;         // needs m suffix — use for money

bool flag = true;
char c    = 'A';

// underscores make large numbers readable
int million = 1_000_000;

declaring variables — var and const

CSharp

// explicit type
int age  = 25;
string name = "Alice";

// var — compiler infers the type
var score  = 100;        // int
var city   = "Colombo";  // string
var price  = 9.99;       // double

// const — fixed at compile time
const double Pi = 3.14159;
const int MaxSize = 100;

// readonly — fixed after construction
readonly string AppName = "MyApp";

// declare now, assign later
// must assign before using
int count;
count = 0;

type conversion

CSharp

// implicit — automatic when safe
int    i = 42;
long   l = i;      // int → long (no data loss)
double d = i;      // int → double

// explicit cast — may lose data
double price = 9.99;
int    cents = (int) price;    // 9 (decimal lost)

// Convert class — flexible conversions
int    n = Convert.ToInt32("42");
double x = Convert.ToDouble("3.14");
string s = Convert.ToString(42);
bool   b = Convert.ToBoolean("true");

// Parse — throws if invalid
int parsed = int.Parse("42");

// TryParse — safe, no exception
if (int.TryParse("42", out int result))
{
    Console.WriteLine(result); // 42
}

if (int.TryParse("abc", out int bad))
{
    // never runs — "abc" isn't a number
}

Strings

C# strings have two things Java developers immediately notice: interpolation with $ is built in and clean, and == actually compares content by default. Both are welcome improvements.

interpolation, verbatim, and concatenation

CSharp

string name  = "Alice";
int    score = 95;

// interpolation — cleanest way
string msg = $"Hello, {name}! Score: {score * 2}";

// expression inside {}
Console.WriteLine($"Next year: {2024 + 1}");

// verbatim — no escape needed for backslashes
string path = @"C:UsersAliceDocuments";
string multi = @"Line one
Line two
Line three";

// raw string (C# 11+) — no escaping at all
string json = """
    {
        "name": "Alice",
        "age": 28
    }
    """;

// concatenation with +
string full = "Hello, " + name + "!";

common string methods

CSharp

string s = "  Hello, C#!  ";

// length and access
s.Length              // 14
s[0]                  // ' ' (first char)

// trim whitespace
string clean = s.Trim();        // "Hello, C#!"
s.TrimStart();                  // remove leading
s.TrimEnd();                    // remove trailing

// case
clean.ToUpper()       // "HELLO, C#!"
clean.ToLower()       // "hello, c#!"

// search
clean.Contains("C#")            // true
clean.StartsWith("Hello")       // true
clean.EndsWith("!")             // true
clean.IndexOf("C")              // 7

// modify (returns new string — strings are immutable)
clean.Replace("C#", "World")    // "Hello, World!"

// extract
clean.Substring(7)              // "C#!"
clean.Substring(7, 2)           // "C#"

// split
"a,b,c".Split(',')              // ["a","b","c"]

// null and empty checks
string.IsNullOrEmpty(null)      // true
string.IsNullOrEmpty("")        // true
string.IsNullOrWhiteSpace("  ") // true

== comparison and StringBuilder

CSharp

// == compares content — works correctly in C#
string a = "hello";
string b = "hello";
a == b           // true ✅ (unlike Java)
a.Equals(b)      // true

// case-insensitive comparison
a.Equals("HELLO",
    StringComparison.OrdinalIgnoreCase)  // true

// StringBuilder — for building in loops
// ❌ slow: creates a new string every iteration
string result = "";
for (int i = 0; i < 1000; i++)
    result += i;

// ✅ fast: modifies in place
var sb = new System.Text.StringBuilder();
for (int i = 0; i < 1000; i++)
    sb.Append(i);

string final = sb.ToString();

// StringBuilder methods
var sb2 = new System.Text.StringBuilder("Hello");
sb2.Append(", World");      // "Hello, World"
sb2.Insert(5, "!");         // "Hello!, World"
sb2.Replace("World", "C#"); // "Hello!, C#"
sb2.Remove(5, 1);           // "Hello, C#"

Operators

C# operators mostly follow what you'd expect. The ones worth paying extra attention to are the null-related ones — ??, ?., and ??= — which show up everywhere in modern C# code.

arithmetic, comparison, and logical

CSharp

// arithmetic
int a = 10, b = 3;
a + b    // 13
a - b    // 7
a * b    // 30
a / b    // 3   (integer division — decimal dropped)
a % b    // 1   (remainder)

// get decimal result from int division
(double) a / b   // 3.333...
10.0 / 3         // 3.333...

// compound assignment
int x = 10;
x += 5;   // 15
x -= 3;   // 12
x *= 2;   // 24
x /= 4;   // 6
x %= 5;   // 1

// increment / decrement
x++;   // post-increment
++x;   // pre-increment
x--;   // post-decrement

// comparison
5 == 5    // true
5 != 3    // true
5 > 3     // true
5 <= 5    // true

// logical
true && false  // false (AND)
true || false  // true  (OR)
!true          // false (NOT)

// ternary
int age = 20;
string label = age >= 18 ? "Adult" : "Minor";

null-related operators — ??, ?., ??=

CSharp

// ?? — fallback when null
string name = null;
string display = name ?? "Guest";
// "Guest"

// chain ?? for multiple fallbacks
string a = null, b = null, c = "found";
string result = a ?? b ?? c;
// "found"

// ?. — call method/property only if not null
// returns null instead of throwing
string? input = null;
int? len = input?.Length;
// null — no NullReferenceException

// chain ?.
string? city = user?.Address?.City;
// null if user or Address is null

// ??= — assign only if currently null
string? cached = null;
cached ??= LoadFromDatabase();
// only calls LoadFromDatabase if cached is null

// combine ?? and ?.
string result2 = user?.Name ?? "Anonymous";

Control Flow

C# switch got a major upgrade starting in C# 8 — switch expressions, property patterns, and tuple patterns. The old form still works, but the new form is considerably more expressive.

if, else if, else

CSharp

int score = 75;

if (score >= 90)
{
    Console.WriteLine("A");
}
else if (score >= 75)
{
    Console.WriteLine("B");
}
else if (score >= 60)
{
    Console.WriteLine("C");
}
else
{
    Console.WriteLine("F");
}

// single-statement if (no braces)
// only for very simple cases
if (score > 50)
    Console.WriteLine("Passed");

// if expression (C# 9+)
string grade = score >= 90 ? "A"
             : score >= 75 ? "B"
             : score >= 60 ? "C"
             : "F";

switch — classic and expression form

CSharp

// classic switch
string day = "MON";
switch (day)
{
    case "MON":
        Console.WriteLine("Monday");
        break;
    case "SAT":
    case "SUN":
        Console.WriteLine("Weekend");
        break;
    default:
        Console.WriteLine("Weekday");
        break;
}

// switch expression (C# 8+) — cleaner
string label = day switch
{
    "MON"          => "Monday",
    "TUE"          => "Tuesday",
    "SAT" or "SUN" => "Weekend",
    _              => "Other day"  // _ = default
};

// pattern matching with when
int n = 42;
string desc = n switch
{
    0                => "zero",
    < 0              => "negative",
    > 0 and < 100    => "small positive",
    _                => "large"
};

// property pattern
var user = new { Name = "Alice", Age = 28 };
string category = user switch
{
    { Age: < 18 }  => "Minor",
    { Age: >= 65 } => "Senior",
    _              => "Adult"
};

Loops

C# has the standard four loop types. foreach is the cleanest for collections — use it whenever you only need to read each item. for is better when you need the index or need to modify elements.

for and foreach

CSharp

// for loop
for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i); // 0 1 2 3 4
}

// count down
for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i); // 5 4 3 2 1
}

// loop through array by index
int[] nums = { 10, 20, 30, 40 };
for (int i = 0; i < nums.Length; i++)
{
    Console.WriteLine(nums[i]);
}

// foreach — cleaner for reading
foreach (int n in nums)
{
    Console.WriteLine(n);
}

// foreach over a list
var names = new List<string>
    { "Alice", "Bob", "Carol" };

foreach (var name in names)
{
    Console.WriteLine($"Hello, {name}");
}

while, do-while, break, continue

CSharp

// while
int i = 0;
while (i < 5)
{
    Console.WriteLine(i);
    i++;
}

// do-while — runs at least once
int j = 10;
do
{
    Console.WriteLine(j); // prints 10
    j++;
} while (j < 5);

// break — exit loop immediately
for (int k = 0; k < 10; k++)
{
    if (k == 5) break;
    Console.Write(k + " "); // 0 1 2 3 4
}

// continue — skip this iteration
for (int k = 0; k < 5; k++)
{
    if (k == 2) continue;
    Console.Write(k + " "); // 0 1 3 4
}

Arrays & Collections

Arrays are fixed-size and fast. For most real work you'll reach for List<T> instead — it grows as needed and has a much richer API. Dictionary and HashSet round out the three you'll use most.

arrays

CSharp

// declare and initialise
int[]    nums   = { 1, 2, 3, 4, 5 };
string[] names  = { "Alice", "Bob" };
bool[]   flags  = new bool[3]; // [false, false, false]

// access and update
nums[0]          // 1
nums[^1]         // 5 (last element — C# 8+)
nums[1] = 99;

// length
nums.Length      // 5

// sort and search
Array.Sort(nums);
Array.Reverse(nums);
int idx = Array.IndexOf(nums, 3); // index or -1

// 2D array
int[,] matrix = {
    { 1, 2, 3 },
    { 4, 5, 6 }
};
matrix[0, 0]  // 1
matrix[1, 2]  // 6

// array slicing (C# 8+)
int[] slice = nums[1..4];  // index 1 to 3

List<T>

CSharp

using System.Collections.Generic;

// create
var nums  = new List<int> { 1, 2, 3 };
var names = new List<string>();

// add
names.Add("Alice");
names.Add("Bob");
names.Insert(1, "Carol"); // insert at index

// access
names[0]           // "Alice"
names.Count        // 3
names[^1]          // last item (C# 8+)

// search
names.Contains("Bob")        // true
names.IndexOf("Carol")       // 1
names.Find(n => n.Length > 3) // "Alice"

// remove
names.Remove("Bob");     // by value
names.RemoveAt(0);       // by index
names.RemoveAll(n => n.StartsWith("A"));

// iterate
foreach (var name in names)
    Console.WriteLine(name);

// sort
names.Sort();
names.Sort((a, b) =>
    a.Length.CompareTo(b.Length));

// convert from array
var list = new List<int>(nums);
int[] arr = names.ToArray();

Dictionary<TKey, TValue> and HashSet<T>

CSharp

// ── Dictionary ──────────────────────────────
var scores = new Dictionary<string, int>
{
    { "Alice", 90 },
    { "Bob",   85 }
};

// add and update
scores["Carol"]  = 92;    // add new key
scores["Alice"]  = 95;    // update existing

// access — use TryGetValue to avoid exceptions
if (scores.TryGetValue("Alice", out int s))
    Console.WriteLine(s);  // 95

scores.GetValueOrDefault("Dave", 0);  // 0

// check and remove
scores.ContainsKey("Bob")   // true
scores.Remove("Bob");

// iterate
foreach (var (key, val) in scores)
    Console.WriteLine($"{key}: {val}");

// ── HashSet ──────────────────────────────
var tags = new HashSet<string>();

tags.Add("csharp");
tags.Add("dotnet");
tags.Add("csharp");   // ignored — already exists

tags.Contains("dotnet")  // true
tags.Count               // 2
tags.Remove("dotnet");

// set operations
var a = new HashSet<int> { 1, 2, 3, 4 };
var b = new HashSet<int> { 3, 4, 5, 6 };

a.UnionWith(b);        // {1,2,3,4,5,6}
a.IntersectWith(b);    // {3,4}
a.ExceptWith(b);       // {1,2}

Methods

C# methods are straightforward, but a few features are worth knowing early — expression-bodied methods, optional parameters, and out parameters. You'll see all three regularly in real codebases.

defining and calling methods

CSharp

// standard method
static int Add(int a, int b)
{
    return a + b;
}

// expression-bodied — same thing, shorter
static int Add(int a, int b) => a + b;

// void — returns nothing
static void PrintSum(int a, int b)
    => Console.WriteLine(a + b);

// multiple return values with tuple
static (int Min, int Max) MinMax(int[] arr)
    => (arr.Min(), arr.Max());

// calling methods
int sum = Add(3, 4);        // 7
PrintSum(5, 6);             // 11

var result = MinMax(new[] { 3, 1, 9, 2 });
result.Min   // 1
result.Max   // 9

optional params, named args, out and ref

CSharp

// optional parameters — must come last
static void CreateUser(
    string name,
    string role    = "user",
    bool   isActive = true)
{
    Console.WriteLine($"{name} | {role} | {isActive}");
}

CreateUser("Alice");
// Alice | user | true

CreateUser("Bob", "admin");
// Bob | admin | true

// named arguments — skip to the one you need
CreateUser("Carol", isActive: false);
// Carol | user | false

// out — method returns a value via parameter
// used by TryParse pattern
static bool Divide(
    int a, int b, out double result)
{
    if (b == 0) { result = 0; return false; }
    result = (double) a / b;
    return true;
}

if (Divide(10, 3, out double r))
    Console.WriteLine(r); // 3.333...

// ref — pass by reference, can modify original
static void Double(ref int value)
    => value *= 2;

int n = 5;
Double(ref n);
Console.WriteLine(n); // 10

// params — variable number of arguments
static int Sum(params int[] numbers)
    => numbers.Sum();

Sum(1, 2);          // 3
Sum(1, 2, 3, 4, 5); // 15

OOP — Classes & Objects

C# classes are clean and expressive. Properties with get and set replace getter/setter methods entirely, and records (C# 9+) make immutable data classes a one-liner.

class structure and creating objects

CSharp

public class Person
{
    // auto-property — compiler generates backing field
    public string Name { get; set; }
    public int    Age  { get; set; }

    // read-only property
    public string Id { get; } = Guid.NewGuid().ToString();

    // property with validation
    private int _score;
    public int Score
    {
        get => _score;
        set => _score = value < 0 ? 0 : value;
    }

    // constructor
    public Person(string name, int age)
    {
        Name = name;
        Age  = age;
    }

    // method
    public string Introduce()
        => $"Hi, I'm {Name}, age {Age}";
}

// create an object
var alice = new Person("Alice", 28);
var bob   = new Person("Bob",   32);

// access properties and methods
Console.WriteLine(alice.Name);        // Alice
Console.WriteLine(alice.Introduce()); // Hi, I'm Alice...

// object initialiser syntax
var carol = new Person("Carol", 25)
{
    Score = 90
};

access modifiers, static, and record types

CSharp

// access modifiers
public class BankAccount
{
    private double _balance;        // only this class
    protected string OwnerId;      // this + subclasses
    internal string BranchCode;    // same project
    public string AccountNumber;   // everywhere

    public double GetBalance() => _balance;

    public void Deposit(double amount)
    {
        if (amount > 0) _balance += amount;
    }
}

// static members
public class MathHelper
{
    public static double Pi = 3.14159;

    // call on class, not instance
    public static int Square(int n) => n * n;
}
MathHelper.Square(5);   // 25
MathHelper.Pi;          // 3.14159

// record — immutable data type (C# 9+)
// generates equals, hashcode, toString automatically
public record User(string Name, int Age, string Email);

var alice = new User("Alice", 28, "[email protected]");
Console.WriteLine(alice);
// User { Name = Alice, Age = 28, Email = alice@... }

// with expression — copy with one change
var older = alice with { Age = 29 };

OOP — Inheritance & Interfaces

C# supports single class inheritance and multiple interface implementation. Classes are not sealed by default — add sealed when you explicitly don't want a class extended.

inheritance — virtual, override, abstract

CSharp

// base class
public class Animal
{
    public string Name { get; set; }

    public Animal(string name) => Name = name;

    // virtual — can be overridden
    public virtual void Speak()
        => Console.WriteLine($"{Name} makes a sound");
}

// subclass — : Animal means "extends Animal"
public class Dog : Animal
{
    public string Breed { get; set; }

    public Dog(string name, string breed) : base(name)
        => Breed = breed;

    // override the parent method
    public override void Speak()
        => Console.WriteLine($"{Name} barks");

    public void Fetch()
        => Console.WriteLine($"{Name} fetches!");
}

// abstract class — cannot be instantiated
public abstract class Shape
{
    public abstract double Area();  // must implement

    public void Describe()          // shared method
        => Console.WriteLine($"Area: {Area()}");
}

public class Circle : Shape
{
    double _r;
    public Circle(double r) => _r = r;

    public override double Area()
        => Math.PI * _r * _r;
}

var dog = new Dog("Rex", "Labrador");
dog.Speak();    // Rex barks
dog.Fetch();    // Rex fetches!

Animal a = new Dog("Buddy", "Poodle");
a.Speak();      // Buddy barks (polymorphism)

interfaces

CSharp

// define an interface
public interface IPrintable
{
    void Print();

    // default implementation (C# 8+)
    void PrintTwice()
    {
        Print();
        Print();
    }
}

public interface ISaveable
{
    bool Save(string path);
}

// implement multiple interfaces
public class Report : IPrintable, ISaveable
{
    public string Title { get; set; }

    public void Print()
        => Console.WriteLine($"Printing: {Title}");

    public bool Save(string path)
    {
        Console.WriteLine($"Saving to {path}");
        return true;
    }
    // PrintTwice() comes from default impl — free
}

var r = new Report { Title = "Q3 Results" };
r.Print();           // Printing: Q3 Results
r.PrintTwice();      // prints twice
r.Save("report.pdf");

// interface as a type
IPrintable printable = r;
printable.Print();

Nullability

C# has two kinds of null handling. Nullable value types (int?) have been around since C# 2. Nullable reference types — where string? means a string that can be null — arrived in C# 8 and are now the standard.

nullable value types — int?, bool?, struct?

CSharp

// nullable value types
int?  age     = null;
bool? isReady = null;

// check before using
if (age.HasValue)
    Console.WriteLine(age.Value);

// safe fallback
int display = age ?? 0;           // 0 if null
int safe    = age.GetValueOrDefault(0); // same

// nullable arithmetic
int? a = 5;
int? b = null;
int? sum = a + b;  // null (null propagates)

// pattern matching with nullables
if (age is int n)
    Console.WriteLine($"Age is {n}");

// nullable in method signatures
static string FormatAge(int? age)
    => age.HasValue
        ? $"Age: {age.Value}"
        : "Age unknown";

nullable reference types (C# 8+)

CSharp

// enable in .csproj (recommended project-wide)
// <Nullable>enable</Nullable>

// or per-file
#nullable enable

string  name   = "Alice";   // cannot be null
string? middle = null;      // can be null

// compiler warns if you dereference without checking
Console.WriteLine(middle.Length); // ← warning!

// safe patterns
if (middle != null)
    Console.WriteLine(middle.Length); // safe

Console.WriteLine(middle?.Length);    // null-conditional
Console.WriteLine(middle ?? "N/A");   // fallback

// null-forgiving operator !
// tells compiler: I know this isn't null
string value = GetMaybeNull()!;
// use only when you're 100% sure

The null-forgiving operator ! tells the compiler 'trust me, this isn't null'. If you're wrong, you get a NullReferenceException at runtime. Use it only when you're certain.

Exception Handling

Exception handling in C# follows the try/catch/finally pattern. The using statement is worth learning early — it makes resource cleanup automatic and is used constantly in file and database code.

try, catch, finally, when

CSharp

try
{
    int result = 10 / 0; // throws DivideByZeroException
    string s = null;
    s.Length;            // throws NullReferenceException
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Divide error: {ex.Message}");
}
catch (NullReferenceException ex)
{
    Console.WriteLine($"Null error: {ex.Message}");
}
catch (Exception ex) when (ex.Message.Contains("file"))
{
    // 'when' filter — catch only if condition is true
    Console.WriteLine("File-related error");
}
catch (Exception ex)
{
    // catch-all — always last
    Console.WriteLine($"Error: {ex.Message}");
}
finally
{
    // always runs — use for cleanup
    Console.WriteLine("Cleanup complete");
}

// throw and rethrow
static void ValidateAge(int age)
{
    if (age < 0)
        throw new ArgumentException(
            "Age cannot be negative", nameof(age));
}

// rethrow preserving the stack trace
catch (Exception ex)
{
    Log(ex);
    throw;  // not 'throw ex' — that resets trace
}

custom exceptions and using statement

CSharp

// custom exception
public class InsufficientFundsException : Exception
{
    public decimal Amount { get; }

    public InsufficientFundsException(decimal amount)
        : base($"Insufficient funds. Need {amount} more.")
    {
        Amount = amount;
    }
}

// throw it
throw new InsufficientFundsException(50.00m);

// catch it
try { /* ... */ }
catch (InsufficientFundsException ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine($"Short by: {ex.Amount}");
}

// using statement — auto-dispose
using (var reader = new StreamReader("file.txt"))
{
    string content = reader.ReadToEnd();
}
// reader.Dispose() called automatically

// using declaration (C# 8+) — no braces
using var reader2 = new StreamReader("file.txt");
string line = reader2.ReadLine();
// disposed at end of enclosing scope

LINQ

LINQ — Language-Integrated Query — lets you filter, transform, and query collections using the same syntax regardless of the source. It's one of the things C# developers genuinely miss when working in other languages.

method syntax — the everyday approach

CSharp

using System.Linq;

var nums = new List<int> { 1, 2, 3, 4, 5, 6 };

// filter
nums.Where(n => n % 2 == 0)
// [2, 4, 6]

// transform
nums.Select(n => n * 10)
// [10, 20, 30, 40, 50, 60]

// sort
nums.OrderBy(n => n)          // ascending
nums.OrderByDescending(n => n) // descending

// aggregates
nums.Sum()     // 21
nums.Average() // 3.5
nums.Min()     // 1
nums.Max()     // 6
nums.Count()                      // 6
nums.Count(n => n > 3)            // 3

// first and single
nums.First()                      // 1 (throws if empty)
nums.FirstOrDefault()             // 1 (null/0 if empty)
nums.FirstOrDefault(n => n > 10)  // 0 (not found)
nums.Single(n => n == 3)          // 3 (throws if not exactly 1)

// check
nums.Any(n => n > 5)  // true
nums.All(n => n > 0)  // true

// chain operations
var result = nums
    .Where(n => n % 2 == 0)   // [2, 4, 6]
    .Select(n => n * n)        // [4, 16, 36]
    .OrderByDescending(n => n) // [36, 16, 4]
    .ToList();

query syntax and useful operations

CSharp

var nums = new[] { 1, 2, 3, 4, 5, 6 };

// query syntax — reads like SQL
var evens = from n in nums
            where n % 2 == 0
            orderby n descending
            select n * 10;
// [60, 40, 20]

// group by
var words = new[] { "hi", "hello", "hey", "bye" };

var grouped = words.GroupBy(w => w[0]);
foreach (var group in grouped)
{
    Console.WriteLine($"{group.Key}: {string.Join(", ", group)}");
}
// h: hi, hello, hey
// b: bye

// pagination
nums.Take(3)         // [1, 2, 3]
nums.Skip(3)         // [4, 5, 6]
nums.Take(2..4)      // [3, 4] (C# 8+ range)

// distinct — remove duplicates
new[] { 1, 2, 2, 3, 3, 3 }.Distinct()
// [1, 2, 3]

// flatten nested collections
var nested = new[] {
    new[] { 1, 2 },
    new[] { 3, 4 }
};
nested.SelectMany(n => n)
// [1, 2, 3, 4]

// working with objects
var users = new List<User>
{
    new User("Alice", 28, "[email protected]"),
    new User("Bob",   17, "[email protected]"),
    new User("Carol", 32, "[email protected]")
};

var adults = users
    .Where(u => u.Age >= 18)
    .OrderBy(u => u.Name)
    .Select(u => u.Name)
    .ToList();
// ["Alice", "Carol"]

Async / Await

async and await are how C# handles operations that take time — network calls, file reads, database queries. The code looks synchronous and reads top-to-bottom, but it doesn't block the thread while waiting.

async, await, and Task

CSharp

using System.Threading.Tasks;

// async method returning a value
async Task<string> FetchUserAsync(int id)
{
    // await pauses here without blocking
    await Task.Delay(1000);    // simulates network
    return $"User {id}";
}

// async method returning nothing — use Task not void
async Task SendEmailAsync(string to)
{
    await Task.Delay(500);
    Console.WriteLine($"Email sent to {to}");
}

// calling async methods — must await
async Task RunAsync()
{
    string user = await FetchUserAsync(1);
    Console.WriteLine(user);   // "User 1"

    await SendEmailAsync("[email protected]");
}

// top-level await (C# 9+ / .NET 5+)
var result = await FetchUserAsync(1);
Console.WriteLine(result);

// async void — only for event handlers
async void Button_Click(object s, EventArgs e)
{
    await DoWorkAsync();
}

parallel tasks and best practices

CSharp

// ❌ sequential — one after another (~2 seconds)
var user1 = await FetchUserAsync(1);  // wait 1s
var user2 = await FetchUserAsync(2);  // wait 1s

// ✅ parallel — both run at same time (~1 second)
var task1 = FetchUserAsync(1);
var task2 = FetchUserAsync(2);
var results = await Task.WhenAll(task1, task2);
// results[0] = "User 1"
// results[1] = "User 2"

// Task.WhenAny — continue on first to finish
var first = await Task.WhenAny(task1, task2);
Console.WriteLine(await first);

// ConfigureAwait(false) — in library code
// avoids deadlocks in certain contexts
async Task LibraryMethodAsync()
{
    await SomeOperationAsync()
        .ConfigureAwait(false);
}

// CancellationToken — support cancellation
async Task FetchAsync(CancellationToken ct)
{
    await Task.Delay(5000, ct);  // throws if cancelled
}

var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(3));
await FetchAsync(cts.Token);

Never use .Result or .Wait() on a Task in async code — it blocks the thread and can cause deadlocks. Always await instead.

Tips & Good Habits

These are the habits that make C# code safer and easier to work with. Some prevent real bugs. Others just make your code easier to read — which matters more than it sounds when you're working on a team.

always use decimal for money

CSharp

// ❌ double — imprecise for money
double price   = 0.1 + 0.2;
Console.WriteLine(price);
// 0.30000000000000004  ← not 0.3!

// ✅ decimal — exact
decimal price2 = 0.1m + 0.2m;
Console.WriteLine(price2);
// 0.3  ← correct

// always use decimal for financial values
decimal productPrice = 19.99m;
decimal taxRate      = 0.15m;
decimal total        = productPrice * (1 + taxRate);
// 22.9885m  ← exact

C# naming conventions

CSharp

// ✅ correct naming
public class UserService         // PascalCase class
{
    private string _connectionString; // _camelCase field
    public int MaxRetries = 3;   // PascalCase constant

    public async Task<User> GetUserAsync(int userId)
    {                           // PascalCase + Async
        int localCount = 0;     // camelCase local var
        return await FindAsync(userId);
    }
}

public interface IUserRepository // I prefix
{
    Task<User> FindAsync(int id);
}

// ❌ inconsistent naming
public class userservice {
    public int maxretries = 3;
    public async Task<User> getUser(int ID) { }
}

prefer var for readability

CSharp

// ✅ use var when the type is obvious from context
var name   = "Alice";                    // clearly string
var count  = 42;                         // clearly int
var users  = new List<User>();           // clearly List<User>
var result = GetUser(id);                // method name says it

// ✅ use var in foreach
foreach (var user in users)
    Console.WriteLine(user.Name);

// consider explicit type when it adds clarity
Dictionary<string, List<int>> lookup
    = new Dictionary<string, List<int>>();
// var is fine here too — just preference

// ❌ var that hides what the type actually is
var x = Process();   // what does this return?
// explicit is clearer:
OrderResult x = Process();

handy one-liners worth bookmarking

CSharp

// join a collection into a string
string csv = string.Join(", ", names);
// "Alice, Bob, Carol"

// generate a range of numbers
var range = Enumerable.Range(1, 10).ToList();
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// nameof — refactor-safe string of a name
Console.WriteLine(nameof(userName));  // "userName"
throw new ArgumentNullException(nameof(input));

// pattern matching with is
object obj = "hello";
if (obj is string s && s.Length > 3)
    Console.WriteLine(s.ToUpper());

// switch on type
static string Describe(object o) => o switch
{
    int n    => $"integer: {n}",
    string s => $"string: {s}",
    null     => "null",
    _        => "something else"
};

// quick null check and throw
static void Process(string input)
{
    ArgumentNullException.ThrowIfNull(input);
    // input is guaranteed non-null here
}

// target-typed new (C# 9+) — skip repeating type
List<string> names = new();         // same as new List<string>()
Dictionary<string, int> map = new();
Was this helpful?

No login required to share feedback

More Cheatsheets

Keep your reference handy

Explore more zero-to-hero cheatsheets for the tools you use daily.