askesis

C# essentials: 50 cards on LINQ, async/await, records and nullability

C# and .NET fundamentals with short snippets: value vs reference types, properties, records, nullable reference types, LINQ, async/await and DI.

ProgrammingBeginner50 cardsFree

Practice now

Study without an account

0 of 50 seen0 graduated
Value types vs reference types in C#

Scheduled with the default Askesis timings. Progress is saved in this browser.

All 50 cards

  1. 1
    Value types vs reference types in C#
    Value types (int, double, bool, decimal, enums and structs) hold their data directly and are copied on assignment; variables of reference types (classes, string, arrays, delegates) hold a reference to a shared object
  2. 2
    What does this print?
    csharp
    struct Point { public int X; }
    
    var a = new Point { X = 1 };
    var b = a;
    b.X = 2;
    Console.WriteLine(a.X);
    1: assigning a struct copies it, so b is independent
    If Point were a class, both variables would refer to one object and it would print 2.
  3. 3
    Does == compare string contents in C#?
    Yes. string overloads == to compare characters, unlike Java. For most other classes, == compares references unless the type overloads it
    If both sides are typed as object, == falls back to comparing references, even for strings.
  4. 4
    decimal vs double
    double is binary floating point, fast but inexact (0.1 + 0.2 != 0.3); decimal is base-10 with 28–29 significant digits, the right choice for money
    Decimal literals need the m suffix: 19.99m.
  5. 5
    Format values inside a string
    csharp
    var msg = $"Hi {name}, you owe {amount:C} ({ratio:P1})";
    Interpolated strings take format specifiers after a colon: C currency, P1 percent with one decimal, F2 two decimals.
  6. 6
    What is int??
    A nullable value type, short for Nullable<int>: an int that can also be null. Check HasValue, or use ?? to supply a default
  7. 7
    ?? and ??=
    csharp
    string display = name ?? "anonymous";
    cache ??= new Dictionary<string, int>();
    ?? returns the right side when the left is null; ??= assigns only when the variable is null.
  8. 8
    What does order?.Customer?.Name evaluate to if Customer is null?
    null: the null-conditional operator ?. short-circuits the rest of the chain instead of throwing NullReferenceException
  9. 9
    const vs readonly fields
    const is a compile-time constant baked into every caller; readonly is assigned once, at its declaration or in a constructor, and can hold a value computed at runtime
    Changing a public const requires recompiling the assemblies that use it.
  10. 10
    What are nullable reference types?
    With <Nullable>enable</Nullable> (C# 8+), string means “never null” and string? means “may be null”, and the compiler warns when you might dereference or assign null
    New project templates enable it by default since .NET 6.
  11. 11
    What does the ! in user!.Name do?
    It is the null-forgiving operator: it silences the compiler’s nullability warning and does nothing at runtime
    If user really is null, you still get a NullReferenceException.
  12. 12
    With nullable reference types on, can a string parameter still receive null?
    Yes. The annotations only produce compile-time warnings; callers can ignore them or have nullability disabled, so validate public inputs
    csharp
    ArgumentNullException.ThrowIfNull(name);
  13. 13
    Declare a property with a compiler-generated backing field
    csharp
    public string Name { get; set; }
    This auto-property can later get custom logic without changing the class’s public API, which a public field can’t.
  14. 14
    A property that can only be set while the object is being created
    csharp
    public string Name { get; init; }
    
    var user = new User { Name = "Ada" }; // OK
    user.Name = "Bob";                   // compile error
    init accessors arrived in C# 9.
  15. 15
    What does the required modifier do?
    Forces callers to set the property in an object initializer, or the code doesn’t compile (C# 11+)
    csharp
    public required string Email { get; init; }
  16. 16
    A read-only property computed from other properties
    csharp
    public string FullName => $"{First} {Last}";
    An expression-bodied property has no backing field; it is recomputed on every read.
  17. 17
    Trim a value in a property setter without declaring a backing field
    csharp
    public string Name
    {
        get;
        set => field = value.Trim();
    }
    The field keyword (C# 14, .NET 10) refers to the compiler-generated backing field.
  18. 18
    What does public record Person(string Name, int Age); declare?
    A class with init-only properties Name and Age, a constructor, value-based equality, a readable ToString() and deconstruction (C# 9+)
    record struct (C# 10) is the value-type version.
  19. 19
    What does this print?
    csharp
    var a = new Person("Ada", 36);
    var b = new Person("Ada", 36);
    Console.WriteLine(a == b);
    True: records compare by value. With an ordinary class, two separate instances aren’t equal, so it would print False
  20. 20
    Copy a record with one property changed
    csharp
    var older = ada with { Age = 37 };
    A with expression creates a new record; the original is unchanged.
  21. 21
    C# access modifiers
    public (everywhere), private (the containing type), protected (plus derived types), internal (the same assembly)
    Members are private by default; top-level types are internal by default.
  22. 22
    Interfaces in C#
    A contract a class or struct promises to implement; a type can implement many interfaces but inherit from only one class
    Since C# 8, interface members can have default implementations.
  23. 23
    How do you let a subclass override a method?
    Mark it virtual (or abstract) in the base class and override in the derived class; methods aren’t virtual by default
    Mark a class sealed to forbid inheriting from it.
  24. 24
    What does this print?
    csharp
    class A { public virtual string Hi() => "A"; }
    class B : A { public new string Hi() => "B"; }
    
    A x = new B();
    Console.WriteLine(x.Hi());
    A: new hides the base method instead of overriding it, so the call is resolved through the variable’s type A
    With override instead of new, it would print B.
  25. 25
    What is a primary constructor on a class?
    Parameters declared on the class itself (C# 12+), in scope in every member; unlike a record’s, they don’t become public properties
    csharp
    public class OrderService(IOrderRepo repo)
    {
        public Order Get(int id) => repo.Find(id);
    }
  26. 26
    Add a method to string without modifying it
    csharp
    public static class StringExtensions
    {
        public static bool IsBlank(this string s) =>
            string.IsNullOrWhiteSpace(s);
    }
    
    bool blank = "  ".IsBlank(); // true
    An extension method is a static method whose first parameter is marked this; LINQ is built from them.
  27. 27
    Get the sorted names of all adults with LINQ
    csharp
    var names = people
        .Where(p => p.Age >= 18)
        .OrderBy(p => p.Name)
        .Select(p => p.Name)
        .ToList();
  28. 28
    What does this print?
    csharp
    var nums = new List<int> { 1, 2, 3 };
    var big = nums.Where(n => n > 1);
    nums.Add(4);
    Console.WriteLine(big.Count());
    3: LINQ queries are deferred, so the Where runs when Count() enumerates it, after 4 was added
  29. 29
    Why call ToList() on a LINQ query you use several times?
    A deferred query reruns its whole pipeline every time it is enumerated; ToList() or ToArray() runs it once and stores the results
    With Entity Framework, each enumeration of an unmaterialized query can be another database round trip.
  30. 30
    First() vs FirstOrDefault() vs Single()
    First throws if the sequence is empty; FirstOrDefault returns default (null, 0…) instead; Single throws unless there is exactly one element
  31. 31
    Select vs SelectMany
    Select maps each element to one result; SelectMany maps each element to a sequence and flattens them into one
    csharp
    var allTags = posts.SelectMany(p => p.Tags);
  32. 32
    Count people per city with LINQ
    csharp
    var perCity = people
        .GroupBy(p => p.City)
        .Select(g => new { City = g.Key, Count = g.Count() });
  33. 33
    Why prefer Any() over Count() > 0?
    Any() stops at the first element, while Count() may have to enumerate the whole sequence
  34. 34
    Write people.Where(p => p.Age >= 18).Select(p => p.Name) in query syntax
    csharp
    var names = from p in people
                where p.Age >= 18
                select p.Name;
    The compiler translates query syntax into the same method calls.
  35. 35
    IEnumerable<T> vs IQueryable<T>
    LINQ on IEnumerable runs as C# code in memory; on IQueryable it builds an expression tree that a provider such as Entity Framework Core translates, e.g. into SQL
  36. 36
    What does await do?
    Pauses the async method until the awaited task completes, without blocking the thread, then resumes it with the result
    csharp
    public async Task<User> GetUserAsync(int id)
    {
        var json = await _http.GetStringAsync($"/users/{id}");
        return JsonSerializer.Deserialize<User>(json)!;
    }
  37. 37
    Why avoid async void?
    The caller can’t await it or catch its exceptions, which typically crash the process instead. Return Task or Task<T>; async void is only for event handlers
  38. 38
    Run two independent async calls concurrently
    csharp
    var userTask = GetUserAsync(id);
    var ordersTask = GetOrdersAsync(id);
    await Task.WhenAll(userTask, ordersTask);
    
    var user = await userTask;   // already complete
    var orders = await ordersTask;
    Both tasks start before either is awaited, so they overlap.
  39. 39
    Why is calling .Result or .Wait() on a task risky?
    It blocks the thread until the task finishes, and can deadlock where a synchronization context needs that same thread to continue (UI apps, classic ASP.NET)
    Prefer await all the way up the call chain.
  40. 40
    Give up on an async operation after five seconds
    csharp
    using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
    var data = await http.GetStringAsync(url, cts.Token);
    Pass a CancellationToken down through async methods; cancellation surfaces as an OperationCanceledException.
  41. 41
    What happens if you call an async method without awaiting it?
    It still starts, runs synchronously until its first incomplete await, then returns a Task; any exception is stored in that task and goes unnoticed if nothing awaits it
  42. 42
    Consume an asynchronous stream of items
    csharp
    await foreach (var line in File.ReadLinesAsync(path))
    {
        Console.WriteLine(line);
    }
    File.ReadLinesAsync (.NET 7+) returns an IAsyncEnumerable<string> (C# 8+); write your own with yield return inside an async method.
  43. 43
    Make sure a file is disposed at the end of the method
    csharp
    using var stream = File.OpenRead(path);
    // … use stream …
    // Dispose() runs when the enclosing scope ends
    A using declaration (C# 8+) works with any IDisposable; using (…) { } blocks are the older form.
  44. 44
    A switch expression with relational patterns
    csharp
    string Describe(int n) => n switch
    {
        < 0 => "negative",
        0 => "zero",
        _ => "positive",
    };
    _ is the discard pattern that matches anything.
  45. 45
    How do C# generics differ from Java’s?
    They are reified: type arguments exist at runtime, so typeof(T) works and a List<int> stores ints without boxing
    Constrain type parameters with where, e.g. where T : IComparable<T> or where T : new().
  46. 46
    Func vs Action
    Built-in delegate types: Func<…, TResult> returns a value, Action<…> returns void
    csharp
    Func<int, int> square = x => x * x;
    Action<string> log = Console.WriteLine;
  47. 47
    The three service lifetimes in .NET dependency injection
    AddSingleton: one instance for the app. AddScoped: one per scope, i.e. per HTTP request in ASP.NET Core. AddTransient: a new instance every time it is resolved
    Injecting a scoped service into a singleton is a classic bug.
  48. 48
    How does C# code run?
    The compiler turns it into IL (intermediate language) stored in assemblies (.dll); the .NET runtime (CLR) JIT-compiles the IL to native code and manages memory with a garbage collector
    dotnet new console, dotnet run and dotnet add package <name> cover day-to-day use.
  49. 49
    Create an array, a list and a combined list with collection expressions
    csharp
    int[] a = [1, 2, 3];
    List<int> b = [4, 5];
    List<int> all = [..a, ..b];
    Collection expressions and the .. spread element arrived in C# 12.
  50. 50
    Look up a dictionary key that may be missing
    csharp
    if (ages.TryGetValue("ada", out var age))
    {
        Console.WriteLine(age);
    }
    The indexer ages["ada"] throws KeyNotFoundException for a missing key.

Frequently asked questions

Which C# and .NET versions do the cards target?

Current .NET (10, with C# 14). Most cards apply to any modern version; newer features are marked, like required members (C# 11), primary constructors and collection expressions (C# 12) and the field keyword (C# 14).

Does the deck cover ASP.NET Core or Entity Framework?

Only where the language meets them, such as dependency injection lifetimes and IQueryable. The focus is C# itself.

I know Java. Is C# very different?

The syntax and object model are close, but C# adds properties, value-type structs, reified generics, LINQ and async/await. Several cards point out the differences.

Intermediate

JavaScript

Master the JavaScript every developer uses daily: scope, closures, equality, array methods, promises, async/await and the event loop.

50 cards
Intermediate

Advanced JS

Fifty advanced JavaScript cards: event-loop ordering puzzles, generators and async iteration, this binding, Proxy and Reflect, modules, AbortController and coercion edge cases.

50 cards
Intermediate

TypeScript

Learn the TypeScript you use every day: narrowing, generics, keyof, mapped and conditional types, utility types, satisfies and strict mode, with code on every card.

50 cards
Beginner

Python

Learn idiomatic Python fast: comprehensions, f-strings, args and kwargs, generators, decorators, context managers and the standard library.

50 cards
Beginner

Git & terminal

The Git commands and shell skills developers use every day: branching, rebasing, undoing mistakes, pipes, redirects, permissions and processes.

50 cards
Beginner

Java

Core Java with short snippets: equals vs ==, records, sealed types, collections, generics, exceptions, streams, lambdas and JVM basics.

50 cards
Beginner

Kotlin

Kotlin fundamentals with short snippets: null safety, val vs var, data and sealed classes, extension functions, scope functions and coroutines.

50 cards
Beginner

Swift

Swift fundamentals with short snippets: optionals, structs vs classes, protocols, enums with associated values, closures, ARC, async/await and SwiftUI.

50 cards
Beginner

Flutter

Flutter and Dart basics: null safety, futures and streams, stateless vs stateful widgets, setState, hot reload, keys, layout and state management.

50 cards
Beginner

PHP

Modern PHP 8 with short snippets: strict types, arrays, match, nullsafe and named arguments, enums, readonly, Composer, PDO and password_hash.

50 cards
Beginner

Ruby

Ruby fundamentals with short snippets: blocks, procs and lambdas, symbols, Enumerable, mixins, truthiness, method_missing, gems and Bundler.

50 cards
Beginner

Go

Go (Golang) fundamentals with short, idiomatic snippets: slices vs arrays, maps, defer, error wrapping, interfaces, goroutines, channels, select and context.

50 cards
Intermediate

Rust

Learn the Rust that the compiler keeps asking about: ownership, borrowing, lifetimes, Option and Result, traits, closures, smart pointers and async.

50 cards
Intermediate

C++

Modern C++ in short snippets: references vs pointers, RAII, smart pointers, move semantics, STL containers and algorithms, const, templates and UB.

50 cards
Intermediate

Haskell

Haskell fundamentals with short code: types and type classes, pattern matching, laziness, Maybe and Either, folds, Functor, Applicative and Monad.

50 cards
Intermediate

Elixir

Elixir fundamentals with short snippets: immutability, pattern matching, the pipe operator, Enum vs Stream, processes, GenServer, supervisors and mix.

50 cards
Intermediate

x86-64 assembly

x86-64 assembly in Intel syntax: registers and sub-registers, flags, mov and lea, jumps, the stack frame, the System V calling convention and Linux syscalls.

50 cards
Beginner

Regex

Learn regular expressions for good: anchors, classes, greedy vs lazy quantifiers, groups, lookarounds, flags, replacements and the backtracking trap.

50 cards