DevTools

Cheatsheet C#

Linguagem de programação da Microsoft

Back to languages
C#
122 cards found
Categories:
Versions:

Basic Syntax


11 cards
Variables
int age = 30;
string name = "Anna";
double price = 9.99;
bool active = true;
char letter = 'A';

// Type inferred by the compiler
var total = 100;      // int
var message = "Hello"; // string

In C# you declare the type before the name. var lets the compiler infer the type from the value.

String interpolation
string name = "Anna";
int age = 30;

string s = $"A {name} tem {age} years";
string calc = $"FromNow a 5 years: {age + 5}";

// Formatting
double price = 9.5;
string p = $"Price: {price:F2}€";  // 9,50€

The $ prefix lets you insert expressions inside { }. The formats (e.g. F2) control the decimal places.

Comments
// Single-line comment

/* Multi-line
   comment */

/// <summary>
/// XML documentation (shows in IntelliSense)
/// </summary>
int Add(int a, int b) => a + b;

// TODO: pending task
// NOTE: important note

// comments one line and /* */ several. The /// generates XML documentation that Visual Studio shows in IntelliSense.

Data types
// Integer numbers
int n = 10;                 // 32 bits
long big = 9999999999L;
short small = 100;

// Numbers with a decimal point
float f = 3.14f;
double d = 3.14;
decimal price = 9.99m;      // money

// Others
bool ok = true;
char c = 'A';
string s = "text";
object obj = 1;

Use decimal for monetary values (more precise). The suffix (f, m, L) indicates the type of the literal.

Nullable
int? x = null;       // int that accepts null
int y = x ?? 0;      // 0 if x is null
int z = x!.Value;    // forces it (careful!)

// Check before using
if (x.HasValue) {
    Console.WriteLine(x.Value);
}

string? name = null; // nullable reference

By default, value types do not accept null. The ? makes them nullable. The ?? gives an alternative value if it is null.

Console: input and output
// Output
Console.WriteLine("Hello!");       // with line break
Console.Write("without break");
Console.WriteLine($"Value: {10:F2}");

// Input (always returns a string)
string? name = Console.ReadLine();
Console.WriteLine($"Hello, {name}");

// Read a key
var tecla = Console.ReadKey();

Console.WriteLine writes and moves to a new line; Console.Write does not. Console.ReadLine() reads what was typed (always the a string).

Constants
// Compile-time constant
const int Max = 100;
const string App = "Lesson";

// Read-only (set at runtime)
readonly int _id;
public Person(int id) {
    _id = id;
}

const is fixed at compile time. readonly can be set once (in the constructor) and after that never changes.

Conversions
// String to number
int n = int.Parse("123");
double d = double.Parse("3.14");

// Safer (does not throw an exception)
if (int.TryParse("abc", out int r)) {
    Console.WriteLine(r);
}

// Between types
double dd = Convert.ToDouble("3.14");
int i = (int)3.9;  // cast: 3 (truncates)

Use TryParse instead of Parse when the input may be invalid — it returns false instead of throwing an exception.

Verbatim strings (@)
// Without @: you need to escape the backslashes
string path = "C:\\folders\\file.txt";

// With @: literal, the it is written
string path2 = @"C:\folders\file.txt";

// Multiple lines preserved
string sql = @"SELECT *
FROM clients
WHERE active = 1";

The @ prefix creates a literal string: backslashes and line breaks are kept the they are. Ideal for file paths and SQL.

Operators
// Arithmetic
10 + 3    // 13
10 / 3    // 3 (integer!)
10.0 / 3  // 3.333
10 % 3    // 1 (remainder)

// Comparison
5 == 5    // true
5 != 3    // true
5 >= 3    // true

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

Watch out: 10 / 3 gives 3 (integer division). Use 10.0 / 3 to get decimals. && is AND and || is OR.

Useful string methods
string s = "  Hello World  ";

s.Trim()           // "Hello World"
s.ToUpper()        // "  HELLO WORLD  "
s.ToLower()
s.Replace("World", "C#")
s.Split(" ")       // array of words
s.Contains("Hello")  // true
s.Length           // 13

Strings are immutable: the methods return a new string. Trim() removes spaces at the ends and Split() divides into parts.

Flow Control


9 cards
if / else
int age = 20;

if (age >= 18) {
    Console.WriteLine("adult");
} else if (age >= 13) {
    Console.WriteLine("teenager");
} else {
    Console.WriteLine("child");
}

The condition goes between parentheses and the block between braces. The else if chains several conditions.

foreach
var fruits = new[] { "apple", "pear", "grapes" };

foreach (var fruit in fruits) {
    Console.WriteLine(fruit);
}

// With index (if you need it)
for (int i = 0; i < fruits.Length; i++) {
    Console.WriteLine($"{i}: {fruits[i]}");
}

The foreach goes through each element of a collection without needing an index. Simpler and more readable than the for for iterating.

Nested loops
// Multiplication table
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        Console.Write($"{i * j,4}");
    }
    Console.WriteLine();
}

// break 2 does not exist in C# — use a flag or a method
for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (i * j > 6) goto end;
    }
}
end:;

Loops inside loops go through grids and matrices. To exit several levels, extract to a method (with return) or use a control variable.

switch
int day = 1;

switch (day) {
    case 1:
        Console.WriteLine("Monday");
        break;
    case 2:
        Console.WriteLine("Tuesday");
        break;
    default:
        Console.WriteLine("Other");
        break;
}

The switch compares a value with several cases. Each case needs a break (or return). The default is the fallback case.

while / do-while
int x = 5;
while (x > 0) {
    Console.WriteLine(x);
    x--;
}

// Runs at least once
do {
    x++;
} while (x < 10);

The while tests the condition before executing. The do-while executes first and tests afterwards — it always runs at least once.

switch expression (C# 8+)
int day = 1;

string name = day switch {
    1 => "Monday",
    2 => "Tuesday",
    3 => "Wednesday",
    _ => "Other"      // fallback case
};

Modern and compact version of the switch. Use => to return the value and _ the the fallback case. No break.

Ternary operator
int age = 20;

string state = age >= 18 ? "adult" : "smallest";

// Equivalent to:
// if (age >= 18) state = "adult";
// else state = "smallest";

Compact form of an if/else on one line: condition ? value_if_true : value_if_false.

for
for (int i = 0; i < 10; i++) {
    Console.WriteLine(i);  // 0 to 9
}

// Descending
for (int i = 10; i > 0; i--) {
    Console.WriteLine(i);
}

The for has three parts: initialization, condition and increment. Ideal when you know how many times you want to repeat.

break and continue
for (int i = 0; i < 10; i++) {
    if (i == 3) continue;  // skips 3
    if (i == 7) break;     // stops at 7
    Console.WriteLine(i);  // 0,1,2,4,5,6
}

foreach (var item in list) {
    if (item == null) continue;
    Process(item);
}

continue jumps to the next iteration; break ends the loop immediately. Useful for skipping cases or stopping early.

Methods


10 cards
Simple method
int Add(int a, int b) {
    return a + b;
}

// No return (void)
void Greet(string name) {
    Console.WriteLine($"Hello, {name}!");
}

int r = Add(2, 3);  // 5

A method declares the return type, the name and the parameters. Use void when it returns nothing.

params
int Add(params int[] nums) {
    return nums.Sum();
}

Add(1, 2, 3);        // 6
Add(1, 2, 3, 4, 5);  // 15

// It also accepts an array
Add(new[] { 1, 2, 3 });

params lets you pass a variable number of arguments. Internally, they arrive the an array.

Recursion
// Method that calls itself
long Factorial(int n) {
    if (n <= 1) return 1;      // base case (mandatory!)
    return n * Factorial(n - 1);
}

Factorial(5);  // 120

// Fibonacci
int Fib(int n) => n <= 1 ? n : Fib(n - 1) + Fib(n - 2);

A recursive method calls itself. It always needs a base case to stop, otherwise it enters an infinite loop (StackOverflowException).

Expression-bodied
// Body the a single expression (=>)
int Double(int x) => x * 2;

string Greet(string n) => $"Hello, {n}";

void Hello() => Console.WriteLine("Hello");

For one-line methods, use => without braces or return. More concise and readable.

Asynchronous method
async Task<int> GetAsync() {
    await Task.Delay(1000);  // simulates a wait
    return 42;
}

// No return
async Task PrintAsync() {
    await Task.Delay(500);
    Console.WriteLine("done");
}

Methods with async return Task (no value) or Task<T> (with value). The await waits without blocking the thread.

Extension methods
public static class StringExtensions {
    // this on the 1st parameter = string extension
    public static string CapitalizeFirst(this string s) {
        if (string.IsNullOrEmpty(s)) return s;
        return char.ToUpper(s[0]) + s.Substring(1);
    }
}

// Usage: looks like a native method
"hello".CapitalizeFirst();  // "Hello"

An extension method adds methods to existing types without modifying them. It is declared in a static class with this on the first parameter.

Optional and named parameters
void Connect(string host, int port = 3306, int timeout = 30) {
    Console.WriteLine($"{host}:{port}");
}

Connect("localhost");              // uses defaults
Connect("localhost", 5432);
Connect(port: 5432, host: "db");  // by name

Parameters with = value are optional. You can pass arguments by name, in any order, for more clarity.

Local methods
int Calculate(int x) {
    // Function inside the function
    int Square(int n) => n * n;

    return Square(x) + Square(x + 1);
}

You can define functions inside functions. The local function (e.g. Square) only exists inside the method that creates it.

ref and out
// ref: comes in with a value and can be changed
void Increment(ref int x) {
    x++;
}
int n = 5;
Increment(ref n);  // n = 6

// out: only returns a value
bool Tenta(out int result) {
    result = 42;
    return true;
}

ref passes by reference (reads and writes). out is only for returning values — the method must assign it a value.

Method overloading
// Same name, different signatures
int Add(int a, int b) => a + b;
double Add(double a, double b) => a + b;
int Add(int a, int b, int c) => a + b + c;

Add(1, 2);        // calls the 1st
Add(1.5, 2.5);    // calls the 2nd
Add(1, 2, 3);     // calls the 3rd

Overloading allows several methods with the same name (e.g. Add), the long the the parameters differ (type or quantity). The compiler picks the right one.

Classes and Objects


10 cards
Class with properties
public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
}

var p = new Person {
    Name = "Anna",
    Age = 30
};
Console.WriteLine(p.Name);

Auto-properties ({ get; set; }) create the internal field automatically. The new { ... } syntax initializes the properties.

Enum
enum State { Active, Inactive, Suspended }

State e = State.Active;

if (e == State.Active) {
    Console.WriteLine("active");
}

// Convert
State doTexto = Enum.Parse<State>("Inactive");

An enum defines a set of named constants. It makes the code more readable than using numbers (0, 1, 2...).

Indexers
public class Week {
    private readonly string[] _days = new string[7];

    // Access with [] like an array
    public string this[int index] {
        get => _days[index];
        set => _days[index] = value;
    }
}

var s = new Week();
s[0] = "Monday";
Console.WriteLine(s[0]);

An indexer (this[...]) allows accessing an object with array syntax (obj[0]). Useful for classes that wrap collections.

Constructor
public class Person {
    public string Name { get; }
    public int Age { get; }

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

var p = new Person("Anna", 30);

The constructor has the same name the the class and runs when the object is created. Properties with only get are set here (immutable).

Static
public static class Mathematics {
    public static int Double(int x) => x * 2;
    public const double Pi = 3.14159;
}

// Use without creating an instance
int r = Mathematics.Double(5);

static members belong to the class, not to an instance. A static class only has static members and cannot be instantiated.

required (C# 11)
public class Client {
    public required string Name { get; init; }
    public required string Email { get; init; }
    public string? Phone { get; init; }  // optional
}

// Forces you to set Name and Email
var c = new Client {
    Name = "Anna",
    Email = "ana@mail.com"
};
// new Client();  // ERROR: required properties are missing

The required modifier forces you to set those properties in the initializer. The compiler warns you if you forget any — it prevents incomplete objects.

Primary constructor (C# 12)
public class Person(string name, int age) {
    public string Name => name;
    public int Age => age;

    public string Introduce() => $"I am {name}";
}

The primary constructor declares the parameters (e.g. name, age) right on the class. They are available throughout the body. Much shorter syntax.

Struct
public struct Point {
    public int X { get; set; }
    public int Y { get; set; }
}

var p = new Point { X = 1, Y = 2 };

A struct is a value type (like int), copied on assignment. Ideal for small and simple data; classes are reference types.

Record
public record Point(int X, int Y);

var p = new Point(1, 2);
var p2 = p with { X = 5 };  // copy with X=5

Console.WriteLine(p);  // Point { X = 1, Y = 2 }
Console.WriteLine(p == new Point(1, 2));  // true

A record is an immutable type with value equality (it compares the content, not the reference). The with creates a modified copy.

Init-only properties
public class Person {
    public string Name { get; init; }
    public int Age { get; init; }
}

// They can only be set at creation
var p = new Person {
    Name = "Anna",
    Age = 30
};

// p.Name = "Ray";  // compilation ERROR!

The init modifier (C# 9+) allows setting the property only at initialization. After that it is immutable — ideal for read-only objects.

Inheritance and Interfaces


8 cards
Inheritance
public class Animal {
    public virtual void Speak() {
        Console.WriteLine("...");
    }
}

public class Dog : Animal {
    public override void Speak() {
        Console.WriteLine("Au!");
    }
}

A class inherits from another with :. The virtual method in the base can be override (replaced) in the derived class.

sealed
public sealed class Final {
    // cannot be inherited
}

// You can also seal a method
public class A {
    public sealed override void Speak() { }
}

A sealed class cannot be inherited. A sealed override method prevents it from being overridden further down the hierarchy.

Abstract class
public abstract class Shape {
    public abstract double Area();

    public void Describe() {
        Console.WriteLine($"Area: {Area()}");
    }
}

public class Circle : Shape {
    public override double Area() => 3.14;
}

An abstract class cannot be instantiated and can have abstract methods (with no body) that the children are required to implement.

is and the
object obj = new Dog();

// is: checks the type (and can extract)
if (obj is Dog dog) {
    dog.Speak();
}

// the: converts (null if it fails)
Dog? c = obj the Dog;
if (c != null) c.Speak();

is checks whether an object is of a type (and extracts the variable). the tries to convert and returns null if it cannot.

Interface
public interface IAnimal {
    void Speak();
    string Name { get; }
}

public class Dog : IAnimal {
    public string Name => "Rex";
    public void Speak() => Console.WriteLine("Au");
}

An interface is a contract: it defines what a class must have, without implementing it. A class can implement several interfaces.

Interfaces with default implementation
public interface ILog {
    void Write(string msg);

    // Default implementation (C# 8+)
    void Error(string msg) {
        Write($"ERROR: {msg}");
    }
}

Since C# 8, interfaces can have methods with a body (default implementation). Classes inherit them without needing to reimplement them.

base
public class Animal {
    public Animal(string name) {
        Name = name;
    }
    public string Name { get; }
}

public class Dog : Animal {
    public Dog(string name) : base(name) {
        // calls the base constructor
    }
}

base(...) calls the parent class constructor. base.Method() calls a method of the parent class.

Polymorphism in practice
public abstract class Animal {
    public abstract string Speak();
}
public class Dog : Animal {
    public override string Speak() => "Au!";
}
public class Cat : Animal {
    public override string Speak() => "Miau!";
}

// Each object responds in its own way
var animals = new List<Animal> { new Dog(), new Cat() };
foreach (var a in animals) {
    Console.WriteLine(a.Speak());  // Au! / Miau!
}

Polymorphism is treating different objects uniformly: the variable is of type Animal, but each subclass runs its own override.

Collections and LINQ


12 cards
List<T>
var list = new List<int> { 1, 2, 3 };

list.Add(4);          // adds
list.Remove(1);       // removes the value 1
list.RemoveAt(0);     // removes by index
list.Contains(2);     // true
list.Count;           // quantity
int first = list[0];

The List<T> is a dynamic list (it grows on its own). The <T> defines the element type. More flexible than an array.

LINQ: aggregation
var nums = new List<int> { 1, 5, 8, 12 };

nums.Count(x => x > 5)           // 2
nums.Any(x => x > 10)            // true (any?)
nums.All(x => x > 0)             // true (all?)
nums.FirstOrDefault(x => x > 5)  // 8
nums.Sum()                       // 26
nums.Average()                   // 6.5
nums.Max()                       // 12

Operations that reduce the collection to a single value: Count, Sum, Average, Max. Any/All test conditions.

LINQ: GroupBy
var people = new[] {
    new { Name = "Anna", City = "Lisbon" },
    new { Name = "Ray", City = "Porto" },
    new { Name = "Eve", City = "Lisbon" },
};

var byCity = people
    .GroupBy(p => p.City)
    .Select(g => new {
        City = g.Key,
        Total = g.Count(),
        Names = string.Join(", ", g.Select(x => x.Name))
    });

GroupBy groups elements by a key. Each group (IGrouping) has the Key and the elements — use Count(), Select, etc. inside each one.

Dictionary
var dict = new Dictionary<string, int> {
    ["one"] = 1,
    ["two"] = 2
};

dict["tres"] = 3;                  // add
int v = dict["one"];                // access
dict.TryGetValue("one", out int r); // safe
dict.ContainsKey("one");            // true

The Dictionary stores key/value pairs with fast access by the key. The TryGetValue avoids errors if the key does not exist.

LINQ: ordering
var people = new List<Person> { /* ... */ };

var ordenadas = people
    .OrderBy(p => p.Name)            // ascending
    .ThenByDescending(p => p.Age)  // then age (desc)
    .ToList();

var top = people
    .OrderByDescending(p => p.Age)
    .Take(3);

OrderBy sorts ascending and OrderByDescending descending. ThenBy adds tiebreakers. Take limits the results.

LINQ: Distinct, Union and Except
var a = new[] { 1, 2, 3, 3, 4 };
var b = new[] { 3, 4, 5 };

a.Distinct()        // 1, 2, 3, 4 (no repeats)
a.Union(b)          // 1, 2, 3, 4, 5 (join without duplicates)
a.Intersect(b)      // 3, 4 (in common)
a.Except(b)         // 1, 2 (in A but not in B)

Set operations: Distinct removes duplicates, Union joins, Intersect returns the common ones and Except subtracts.

Array
int[] nums = { 1, 2, 3 };
nums[0] = 10;
nums.Length;  // 3

// Create with a fixed size
int[] arr = new int[5];

// Iterate
for (int i = 0; i < nums.Length; i++) {
    Console.WriteLine(nums[i]);
}

An array has a fixed size (set at creation) and is faster than a list. Use Length for the size.

HashSet, Queue and Stack
// HashSet: unique values
var set = new HashSet<int> { 1, 2, 2, 3 };  // {1,2,3}

// Queue: FIFO (first in, first out)
var queue = new Queue<string>();
queue.Enqueue("A");
queue.Dequeue();  // "A"

// Stack: LIFO (last in, first out)
var stack = new Stack<int>();
stack.Push(1);
stack.Pop();  // 1

HashSet stores unique values. Queue is a queue (FIFO) and Stack a stack (LIFO). Each one serves an access pattern.

LINQ: First, Single and Last
var nums = new[] { 1, 2, 3, 4 };

nums.First(x => x > 2)            // 3
nums.FirstOrDefault(x => x > 9)   // 0 (does not throw an error)
nums.Single(x => x == 4)          // 4 (error if there are 2)
nums.Last()                       // 4
nums.ElementAt(1)                 // 2

// Prefer the OrDefault versions to avoid exceptions

First returns the first, Last the last, Single the only one (throws an error if there are several). The OrDefault versions return the default value instead of throwing an exception.

LINQ: Where / Select
using System.Linq;

var nums = new List<int> { 1, 5, 8, 12, 3 };

var result = nums
    .Where(x => x > 5)      // filters: 8, 12
    .Select(x => x * 2)     // transforms: 16, 24
    .ToList();

LINQ runs queries over collections. Where filters and Select transforms each element. It is chained together.

IEnumerable vs List
// IEnumerable: read-only, lazy evaluation
IEnumerable<int> Pares() {
    yield return 2;
    yield return 4;
}

// List: in memory, allows Add/Remove
var list = new List<int> { 1, 2, 3 };

// LINQ returns IEnumerable (does not execute yet)
var query = list.Where(x => x > 1);
var materializada = query.ToList();  // executes now

IEnumerable<T> is read-only and evaluates lazily (yield). List<T> is in memory and allows changes. ToList() materializes a query.

LINQ: Join
var clients = new[] { new { Id = 1, Name = "Anna" } };
var orders = new[] { new { ClienteId = 1, Total = 50 } };

var result = clients.Join(
    orders,
    c => c.Id,          // key from clients
    p => p.ClienteId,   // key from orders
    (c, p) => new { c.Name, p.Total }
);

// Readable alternative (query syntax)
var q = from c in clients
        join p in orders on c.Id equals p.ClienteId
        select new { c.Name, p.Total };

Join crosses two collections by a common key (like a SQL JOIN). The query syntax with join ... on ... equals is more readable.

Exceptions


7 cards
try / catch / finally
try {
    int r = 10 / 0;
} catch (DivideByZeroException ex) {
    Console.WriteLine($"Error: {ex.Message}");
} catch (Exception ex) {
    Console.WriteLine("Other error");
} finally {
    Console.WriteLine("executa always");
}

The try tests code; the catch handles the error (you can catch specific types). The finally always runs, whether there is an error or not.

Custom exception
public class InsufficientBalanceException : Exception {
    public InsufficientBalanceException(string msg)
        : base(msg) { }
}

throw new InsufficientBalanceException(
    "Insufficient balance");

Create your own exceptions by inheriting from Exception. Useful for errors specific to your application, with clear names.

throw
void DefineAge(int age) {
    if (age < 0) {
        throw new ArgumentException(
            "A age not can be negativa");
    }
}

// Common exceptions:
// ArgumentNullException
// InvalidOperationException

Use throw to raise an exception when something is wrong. This interrupts execution and informs whoever called the method.

try/finally without catch
var connection = OpenConnection();
try {
    // use the connection (may throw an error)
    connection.Execute();
} finally {
    // ALWAYS runs (even with an exception)
    connection.Close();
}

// Even better: using (does this by itself)
using var l2 = OpenConnection();
l2.Execute();

finally without catch guarantees resource cleanup without swallowing the error (the exception keeps propagating). The using is the preferred alternative.

using (resources)
// Classic form
using (var fs = File.OpenRead("f.txt")) {
    // use the file
}  // closes automatically

// Modern form (C# 8+)
using var sr = new StreamReader("f.txt");
var text = sr.ReadToEnd();

The using releases resources automatically (files, connections) at the end of the block, even with an error. It works with IDisposable objects.

Exception best practices
// 1. Catch specific types first
catch (FileNotFoundException ex) { /* ... */ }
catch (Exception ex) { /* generic last */ }

// 2. Rethrow without losing the stack trace
try { /* ... */ }
catch (Exception) {
    throw;        // right (preserves the origin)
    // throw ex;   // wrong (resets the stack)
}

// 3. Never do this:
catch { }  // silently swallows the error

Catch specific exceptions before the generic ones. Use throw; (without the variable) to rethrow preserving the stack trace. Never ignore errors silently.

Exception filters
try {
    // risky code
} catch (Exception ex) when (ex is IOException) {
    Console.WriteLine("Error de IO");
} catch (Exception ex) when (ex.Message.Contains("timeout")) {
    Console.WriteLine("Time expired");
}

The when clause filters exceptions by a condition. It only catches if the condition is true — more precise than several catch.

Async / Await


8 cards
async / await
async Task<string> LerAsync() {
    string text = await File.ReadAllTextAsync("f.txt");
    return text;
}

// Call
string content = await LerAsync();

async/await allows asynchronous code that looks synchronous. The await waits for the task without blocking the thread (useful in UI and web).

Cancellation
var cts = new CancellationTokenSource();

async Task WorkAsync(CancellationToken token) {
    while (!token.IsCancellationRequested) {
        await Task.Delay(100, token);
        // does work...
    }
}

cts.Cancel();  // requests cancellation

The CancellationToken allows canceling tasks midway. Pass it to the async methods and check IsCancellationRequested.

Task.WhenAll
async Task BuscarTudo() {
    Task<string> t1 = BuscarA();
    Task<string> t2 = BuscarB();

    await Task.WhenAll(t1, t2);  // waits for both

    string a = t1.Result;
    string b = t2.Result;
}

Task.WhenAll waits for several tasks in parallel. Faster than waiting one at a time when they are independent.

Task.WhenAny
Task<string> t1 = FetchServerA();
Task<string> t2 = FetchServerB();

// Waits for the FIRST to finish
Task<string> winner = await Task.WhenAny(t1, t2);
string result = await winner;

// Timeout with WhenAny
var task = FetchData();
var timeout = Task.Delay(5000);
if (await Task.WhenAny(task, timeout) == timeout) {
    Console.WriteLine("Time expired");
}

Task.WhenAny returns the first task to finish — useful for redundancy (asking 2 servers) or to implement timeouts.

Task.Run
async Task Process() {
    // Heavy work on another thread
    int result = await Task.Run(() => {
        return CalcularPesado();
    });
}

Task.Run runs heavy work on a separate thread, freeing the main thread (e.g. so the interface does not "freeze").

await foreach (IAsyncEnumerable)
// Produces values asynchronously
async IAsyncEnumerable<int> GenerupTosync() {
    for (int i = 0; i < 5; i++) {
        await Task.Delay(100);
        yield return i;
    }
}

// Consumes asynchronously
await foreach (var n in GenerupTosync()) {
    Console.WriteLine(n);
}

IAsyncEnumerable<T> is an asynchronous sequence: each element can be produced with await. It is consumed with await foreach (ideal for streams and pagination).

ValueTask
async ValueTask<int> GetAsync() {
    if (_cache != 0) return _cache;  // synchronous
    return await FetchFromNetwork();
}

ValueTask is a lighter alternative to Task when the result is often already available (it avoids allocations). Use it sparingly.

async void: caveats
// WRONG: cannot await nor capture errors
async void Send() {
    await Task.Delay(100);
    throw new Exception();  // crashes the process!
}

// RIGHT: returns Task
async Task EnviarAsync() {
    await Task.Delay(100);
}

await EnviarAsync();  // can wait and capture

Avoid async void: you cannot await it and exceptions escape (they can crash the application). Use async Taskasync void only in event handlers.

Tips and Good Practices


9 cards
Null-coalescing (??)
string name = null;
string s = name ?? "Anonymous";  // "Anonymous"

// Assigns if it is null
List<int> list = null;
list ??= new List<int>();

// Chain
string r = a ?? b ?? c ?? "default";

The ?? returns the left value if it is not null, otherwise the right one. The ??= only assigns if the variable is null.

Properties with logic
private int _age;
public int Age {
    get => _age;
    set => _age = value > 0 ? value : 0;
}

// Calculated read-only
public string FullName => $"{Name} {Surname}";

A property can have logic in the get/set (validation, calculation). The value is the value received in the set. => creates read-only properties.

Pattern matching with switch
string Sort(object obj) => obj switch {
    int n when n < 0     => "negative",
    int n                => "positive",
    string { Length: 0 } => "string empty",
    string s             => $"text: {s}",
    null                 => "null",
    _                    => "other type"
};

Sort(-5);   // "negative"
Sort("");   // "string empty"

switch expression with patterns: it combines type (int n), property ({ Length: 0 }), condition (when) and null. Far more powerful than the traditional switch.

Null-conditional (?.)
string name = null;

int? len = name?.Length;   // null (no error!)
list?.Add(1);             // only adds if list != null

// Chain
string city = person?.Address?.City;

The ?. accesses a member only if the object is not null — otherwise it returns null. It avoids the dreaded NullReferenceException.

using static
using static System.Console;
using static System.Math;

WriteLine("Hello");     // instead of Console.WriteLine
double r = Sqrt(16);  // instead of Math.Sqrt

using static imports the static members of a class. It lets you use them without repeating the class name.

Pattern matching
object obj = 42;

// Pattern with condition
if (obj is int n and > 0) {
    Console.WriteLine($"positive: {n}");
}

// Type pattern with property
if (obj is string { Length: > 3 } s) {
    Console.WriteLine(s);
}

Pattern matching checks type, value and properties in a single expression. It is combined with and, or and not.

var vs explicit type
// var: inferred type
var list = new List<int>();
var client = GetClient();

// Explicit type: clearer
int total = Calculate();
List<int> nums = new();  // target-typed new (C# 9+)

Use var when the type is obvious (e.g. new List<int>()). Use the explicit type when it makes the code more readable. new() omits the type on the right.

Literal strings (C# 11+)
string json = """
    {
        "name": "Anna",
        "age": 30
    }
    """;

// With interpolation
string s = $"""
    Hello {name}
    """;

Strings with """ (three quotes) preserve line breaks and quotes without escaping. Ideal for JSON, SQL or HTML in the code.

Ranges and indices (C# 8)
var nums = new[] { 0, 1, 2, 3, 4, 5 };

nums[^1]      // 5 (last)
nums[^2]      // 4 (second to last)

nums[1..4]    // {1, 2, 3} (indices 1 to 3)
nums[..3]     // {0, 1, 2} (from the start up to 3)
nums[3..]     // {3, 4, 5} (from 3 to the end)
nums[..]      // copy of all

Range r = 2..5;
nums[r]       // {2, 3, 4}

The ^ operator counts from the end (^1 is the last). .. creates a range (Range) to extract sub-arrays. Available since C# 8.

Strings


8 cards
StringBuilder
using System.Text;

var sb = new StringBuilder();
sb.Append("Hello");          // joins without creating a new object
sb.Append(' ');
sb.AppendLine("World");    // + line break
sb.Insert(0, ">>> ");      // inserts at a position
sb.Replace("World", "C#"); // replaces

string result = sb.ToString();

StringBuilder builds text efficiently when there are many concatenations (it does not create a new object on each Append, unlike +). Essential inside loops.

char methods
char c = '7';

char.IsDigit(c)            // true
char.IsLetter('a')         // true
char.IsLetterOrDigit('?')  // false
char.IsUpper('A')          // true
char.IsWhiteSpace(' ')     // true

char.ToUpper('a')          // 'A'
char.ToLower('A')          // 'a'

// Check whether a string has digits
"abc123".Any(char.IsDigit)   // true

The char class has utilities for validating characters: IsDigit, IsLetter, IsUpper, IsWhiteSpace. ToUpper/ToLower convert.

Split and Join
string csv = "apple,pear,grapes";
string[] fruits = csv.Split(',');       // splits

// Ignore empty entries
"a,,b".Split(',', StringSplitOptions.RemoveEmptyEntries);

// Join back together
string joined = string.Join(" | ", fruits);
// "apple | pear | grapes"

string line = string.Join(",", new[] { "x", "y" });

Split divides a string by a separator. string.Join joins parts with a delimiter. RemoveEmptyEntries ignores empty parts.

String comparison
string a = "Hello";
string b = "hello";

a == b              // false (case matters)
a.Equals(b)         // false

// Ignore uppercase/lowercase
a.Equals(b, StringComparison.OrdinalIgnoreCase);  // true

a.StartsWith("He")  // true
a.EndsWith("o")     // true
a.Contains("LLO", StringComparison.OrdinalIgnoreCase);  // true

By default, the comparison is case-sensitive. Use StringComparison.OrdinalIgnoreCase to ignore it. StartsWith/EndsWith check the ends.

Substring and IndexOf
string s = "Hello World C#";

s.Substring(4)       // "World C#"
s.Substring(4, 5)    // "World"

s.IndexOf("World")   // 4 (position)
s.LastIndexOf("o")   // last occurrence

// Extract the domain from an email
string email = "ana@mail.com";
string dominio = email.Substring(email.IndexOf('@') + 1);

Substring extracts a part (start position and, optionally, length). IndexOf returns the position of the first occurrence or -1 if it does not exist.

string.Format
double price = 1234.5;

string.Format("Price: {0:C}", price);   // currency
string.Format("{0,10}", "dir");         // right-aligned
string.Format("{0} e {1}", "A", "B");   // multiple

// Equivalent with interpolation
$"Price: {price:C}";

// Common numeric formats
$"{1234.5:N2}"   // "1,234.50"
$"{0.85:P0}"     // "85%"
$"{255:X}"       // "FF" (hexadecimal)

string.Format replaces {0}, {1} with the values. The formats :C (currency), :N2 (number), :P0 (percentage) and :X (hex) control the output.

Padding and Trim
"42".PadLeft(5, '0')     // "00042"
"Hello".PadRight(8, '.')   // "Hello....."

"  text  ".Trim()       // "text"
"  text  ".TrimStart()  // "text  "
"  text  ".TrimEnd()    // "  text"

// Alignment in interpolation
$"{"esq",-8}|"   // "esq     |" (left)
$"{42,5}"        // "   42" (right)

PadLeft/PadRight pad with a character up to the length. Trim removes spaces. In interpolation, ,N aligns (negative = left).

Concatenation
string a = "Hello";
string b = "World";

string r1 = a + " " + b;              // + operator
string r2 = string.Concat(a, " ", b); // method
string r3 = $"{a} {b}";               // interpolation (preferred)

// Join several strings
var partes = new[] { "a", "b", "c" };
string r4 = string.Concat(partes);    // "abc"

// In loops, use StringBuilder

For simple cases use + or interpolation ($""). string.Concat joins several strings. Inside loops, prefer StringBuilder for performance.

Generics


5 cards
Generic classes
public class Box<T> {
    public T Content { get; set; }
}

var boxInt = new Box<int> { Content = 42 };
var boxStr = new Box<string> { Content = "hello" };

// The type is set at usage
int n = boxInt.Content;      // int (no cast)
string s = boxStr.Content;   // string

A generic class uses a type parameter <T> set at the moment of use. The same code works for any type, safely (no object or casts).

Generic interfaces
// .NET collections are built on generic interfaces
IEnumerable<int> seq = new List<int>();
IList<string> list = new List<string>();
IDictionary<string, int> map = new Dictionary<string, int>();
IComparer<int> comp = Comparer<int>.Default;

// Program against the interface (flexible)
int Add(IEnumerable<int> nums) => nums.Sum();

.NET collections are built on generic interfaces: IEnumerable<T>, IList<T>, IDictionary<K,V>. Programming against the interface makes the code flexible.

Generic methods
// The type is inferred from the argument
T First<T>(T[] items) => items[0];

First(new[] { 1, 2, 3 });       // int
First(new[] { "a", "b" });      // string

// Swap two values of any type
void Swap<T>(ref T a, ref T b) {
    (a, b) = (b, a);
}

A generic method declares <T> after the name. The compiler infers the type from the arguments — you do not need to indicate it. It works for any type.

Constraints (where)
// T must be a class
public class Repository<T> where T : class { }

// T must implement IComparable
T Max<T>(T a, T b) where T : IComparable<T> {
    return a.CompareTo(b) >= 0 ? a : b;
}

// Several constraints at once
void Process<T>(T item)
    where T : class, IDisposable, new() { }

where restricts the accepted types: class, struct, new() (parameterless constructor), interfaces or a base class. It lets you use specific members safely.

default(T)
default(int)       // 0
default(string)    // null
default(bool)      // false
default(DateTime)  // 01/01/0001

// Essential in generics (unknown type)
T? Find<T>(List<T> list, Func<T, bool> filter) {
    foreach (var item in list) {
        if (filter(item)) return item;
    }
    return default;   // not found
}

default(T) returns the default value of a type: 0 for numbers, null for references, false for bool. Indispensable in generic code.

Delegates and Events


6 cards
Lambda expressions
// Lambda: compact anonymous function
Func<int, int> double = x => x * 2;
double(5);  // 10

// With several parameters
Func<int, int, int> sum = (a, b) => a + b;

// With a body (block)
Func<int, string> classify = n => {
    if (n >= 10) return "approved";
    return "failed";
};

// Widely used in LINQ
nums.Where(x => x > 5);

A lambda (x => expression) is a concise anonymous function. It is widely used in LINQ and the an argument of methods that take Func/Action.

Custom delegates
// Define a delegate (method signature)
public delegate int Operation(int a, int b);

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

Operation op = Add;   // points to Add
op(2, 3);              // 5

op = Multiply;      // now points to Multiply
op(2, 3);              // 6

// In new code, prefer Func<int, int, int>

A delegate defines a method signature that can be referenced. In new code prefer Func/Action — custom delegates are still used in public APIs and events.

Action
// Action: delegate with no return (void)
Action<string> print = msg => Console.WriteLine(msg);
print("Hello");

// With several parameters
Action<int, int> add = (a, b) => Console.WriteLine(a + b);

// Pass behavior the an argument
void Execute(Action action) => action();
Execute(() => Console.WriteLine("done"));

// List.ForEach uses Action
new List<int> { 1, 2 }.ForEach(n => Console.WriteLine(n));

Action is a delegate that represents a method with no return (void), with 0 to 16 parameters. Ideal for passing behavior the an argument.

Events (event)
public class Timer {
    // Event (uses the EventHandler delegate)
    public event EventHandler? Tick;

    public void Fire() {
        Tick?.Invoke(this, EventArgs.Empty);
    }
}

// Subscribe (+=) and unsubscribe (-=)
var t = new Timer();
t.Tick += (s, e) => Console.WriteLine("tick!");
t.Fire();   // "tick!"

event exposes a notification that external code can subscribe to (+=) or cancel (-=). It is fired with Invoke. It is the base of the observer pattern in .NET.

Func
// Func: delegate WITH return (last type = return)
Func<int> get = () => 42;
get();  // 42

Func<int, int> double = x => x * 2;          // int -> int
Func<string, int, bool> largest = (s, n) => s.Length > n;

// Practical use: receive a criterion
int[] Filter(int[] nums, Func<int, bool> criteria) {
    return nums.Where(criteria).ToArray();
}
Filter(new[] { 1, 5, 8 }, x => x > 3);  // {5, 8}

Func<T, TResult> is a delegate with a return — the last generic type is the returned type. It is used to pass transformations and criteria to methods.

Predicate
// Predicate: receives T, returns bool
Predicate<int> isEven = n => n % 2 == 0;
isEven(4);  // true

// List methods that use Predicate
var nums = new List<int> { 1, 2, 3, 4, 5 };
nums.Find(isEven);            // 2 (first even)
nums.FindAll(isEven);         // {2, 4}
nums.Exists(isEven);          // true
nums.RemoveAll(n => n < 3);  // removes 1 and 2

Predicate<T> is equivalent to Func<T, bool>: it receives a value and returns true/false. It is used in Find, FindAll, Exists and RemoveAll.

Files and JSON


8 cards
Read file
using System.IO;

// Read everything at once (small files)
string text = File.ReadAllText("grades.txt");

// Asynchronous version (does not block the UI)
string texto2 = await File.ReadAllTextAsync("grades.txt");

// Read the bytes
byte[] data = File.ReadAllBytes("image.png");

File.ReadAllText reads the whole file into a string. For large files use ReadLines or StreamReader. The Async version does not block.

Path
string path = @"C:\data\photo.jpg";

Path.GetFileName(path)                  // "photo.jpg"
Path.GetExtension(path)                 // ".jpg"
Path.GetFileNameWithoutExtension(path)  // "photo"
Path.GetDirectoryName(path)             // "C:\data"

// Join paths safely
string new = Path.Combine("C:", "data", "f.txt");

// Systin timerary folder
Path.GetTempPath();

Path handles paths: GetFileName, GetExtension, GetDirectoryName. Path.Combine joins parts with the correct separator.

Write file
// Overwrites (or creates) the file
File.WriteAllText("output.txt", "Hello World");

// Append to the end (append)
File.AppendAllText("log.txt", "new line\n");

// Asynchronous version
await File.WriteAllTextAsync("output.txt", "content");

// Write bytes
File.WriteAllBytes("data.bin", new byte[] { 1, 2, 3 });

File.WriteAllText creates or overwrites; File.AppendAllText appends to the end. Both close the file automatically.

Check existence
// Check before using
if (File.Exists("config.json")) {
    var json = File.ReadAllText("config.json");
}

if (!Directory.Exists("logs")) {
    Directory.CreateDirectory("logs");
}

// Details about the file
var info = new FileInfo("photo.jpg");
info.Length;         // size in bytes
info.LastWriteTime;  // last modification

File.Exists and Directory.Exists check for existence. FileInfo gives details such the the size (Length) and the date (LastWriteTime).

Lines (Read/WriteAllLines)
// Read the an array of lines
string[] lines = File.ReadAllLines("data.csv");

// Read lazily (memory efficient)
foreach (var line in File.ReadLines("big.csv")) {
    if (line.StartsWith("#")) continue;
}

// Write several lines (each element = 1 line)
File.WriteAllLines("list.txt", new[] { "a", "b", "c" });

ReadAllLines returns an array of lines. ReadLines reads line by line lazily — ideal for large files. WriteAllLines writes each element the a line.

JSON: serialize
using System.Text.Json;

var person = new { Name = "Anna", Age = 30 };

// Object -> JSON
string json = JsonSerializer.Serialize(person);
// {"Name":"Anna","Age":30}

// With options (indented)
var options = new JsonSerializerOptions { WriteIndented = true };
string pretty = JsonSerializer.Serialize(person, options);

JsonSerializer.Serialize (from System.Text.Json) converts an object into JSON. The WriteIndented option formats it with indentation.

Directory
// Create folder
Directory.CreateDirectory("data/backup");

// List files (with optional filter)
string[] files = Directory.GetFiles("data");
string[] soTxt = Directory.GetFiles("data", "*.txt");

// List subfolders
string[] folders = Directory.GetDirectories("data");

// Delete folder (true = recursive)
Directory.Delete("data/backup", true);

Directory manages folders: CreateDirectory, GetFiles (accepts filters like *.txt), GetDirectories and Delete.

JSON: deserialize
using System.Text.Json;

string json = """{"Name":"Anna","Age":30}""";

// JSON -> object (class or record)
var person = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine(person!.Name);  // "Anna"

// Ignore upper/lower case in the names
var options = new JsonSerializerOptions {
    PropertyNameCaseInsensitive = true
};

JsonSerializer.Deserialize<T> converts JSON into an object of type T. PropertyNameCaseInsensitive accepts property names in any case.

Dates and Time


6 cards
Current DateTime
DateTime now = DateTime.Now;    // local date and time
DateTime today = DateTime.Today;   // date only (00:00)
DateTime utc = DateTime.UtcNow;   // UTC time

now.Year        // 2026
now.Month       // 7
now.Day         // 24
now.Hour        // 14
now.Minute      // 30
now.DayOfWeek   // Thursday
now.DayOfYear   // day of the year

DateTime.Now is the local date/time and DateTime.UtcNow is UTC. Access the components with Year, Month, Day, Hour and DayOfWeek.

Format dates
var d = new DateTime(2026, 7, 24, 15, 30, 0);

d.ToString("dd/MM/yyyy")        // "24/07/2026"
d.ToString("dd/MM/yyyy HH:mm")  // "24/07/2026 15:30"
d.ToString("yyyy-MM-dd")        // "2026-07-24" (ISO)
d.ToString("dddd")              // "Friday"
d.ToString("MMM")               // "jul"

// Standard formats
d.ToShortDateString()   // "24/07/2026"
d.ToLongTimeString()    // "15:30:00"

// In interpolation
$"{d:dd/MM/yyyy}";

Format with ToString("..."): dd day, MM month, yyyy year, HH:mm time. The same formats work in interpolation ({d:dd/MM/yyyy}).

Create dates
// Create a specific date
var natal = new DateTime(2026, 12, 25);
var reuniao = new DateTime(2026, 7, 24, 15, 30, 0);

// DateOnly and TimeOnly (C# 10+)
var data = new DateOnly(2026, 12, 25);
var hour = new TimeOnly(15, 30);

// Useful
natal.DayOfWeek               // Friday
DateTime.DaysInMonth(2026, 2) // 28 (feb)

Create a date with new DateTime(year, month, day, hour, min, sec). DateOnly and TimeOnly (C# 10+) represent only the date or only the time.

Parsing dates
// Convert a string into DateTime
DateTime d = DateTime.Parse("24/07/2026");

// Safer (does not throw an exception)
if (DateTime.TryParse("31/02/2026", out var data)) {
    Console.WriteLine(data);
} else {
    Console.WriteLine("Data invalid");
}

// Specific format
DateTime.ParseExact("2026-07-24", "yyyy-MM-dd",
    CultureInfo.InvariantCulture);

DateTime.Parse converts a string into a date. Use TryParse for invalid inputs (it does not throw an exception). ParseExact requires the exact format.

Add and subtract time
var today = DateTime.Now;

today.AddDays(7)      // one week from now
today.AddHours(-2)    // 2 hours ago
today.AddMonths(1)
today.AddMinutes(90)

// Difference between dates (TimeSpan)
var natal = new DateTime(2026, 12, 25);
TimeSpan falta = natal - today;
falta.Days           // remaining days

Add or subtract time with AddDays, AddHours, AddMonths — they return a new DateTime (they do not change the original). Subtracting two dates gives a TimeSpan.

TimeSpan
// Represents a duration (not a date)
var duration = new TimeSpan(2, 30, 0);   // 2h30m
var oneDay = TimeSpan.FromDays(1);
var halfHour = TimeSpan.FromMinutes(30);

duration.Hours         // 2
duration.TotalMinutes  // 150

// Operations
var sum = oneDay + halfHour;
var double = duration * 2;

// Use with DateTime
DateTime tomorrow = DateTime.Now + oneDay;

TimeSpan represents a duration. It is created with FromDays/FromMinutes or with the constructor. TotalMinutes gives the total value in that unit.

Tuplos


5 cards
Basic tuples
// Tuple: group values without creating a class
var point = (1, 2);
point.Item1   // 1
point.Item2   // 2

var person = ("Anna", 30, true);
person.Item1  // "Anna"

// With explicit types
(int x, int y) pos = (5, 10);

A tuple groups several values into a single structure without creating a class. Without names, they are accessed by Item1, Item2, etc.

Return multiple values
// Method that returns several values
(int min, int max) MinMax(int[] nums) {
    return (nums.Min(), nums.Max());
}

var result = MinMax(new[] { 3, 1, 7, 4 });
result.min   // 1
result.max   // 7

// Or deconstruct right away
var (smallest, largest) = MinMax(new[] { 3, 1, 7, 4 });

Tuples are ideal for returning several values from a method without using out parameters or creating a class. It is deconstructed at the destination.

Named fields
// Give names to the fields
var person = (Name: "Anna", Age: 30);
person.Name   // "Anna"
person.Age  // 30

// In method returns
(string city, int cp) GetLocation() {
    return ("Lisbon", 1000);
}

var local = GetLocation();
local.city  // "Lisbon"

Give names to the fields (Name:, Age:) to make the tuple more readable. It is accessed by name instead of Item1.

Deconstruction
var person = (Name: "Anna", Age: 30);

// Extract into separate variables
var (name, age) = person;
Console.WriteLine($"{name}, {age}");

// Ignore fields with _
var (n, _) = person;

// Deconstruct a KeyValuePair
foreach (var (key, value) in dictionary) {
    Console.WriteLine($"{key} = {value}");
}

Deconstruction extracts the tuple fields into individual variables: var (name, age) = person. Use _ to ignore fields.

Swap values (swap)
int a = 1, b = 2;

// Swap without a temporary variable
(a, b) = (b, a);
// now a = 2, b = 1

string x = "hello", y = "world";
(x, y) = (y, x);

With tuples, swapping two values is trivial: (a, b) = (b, a) does the swap without needing a temporary variable.