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.
Practice now
Study without an account
Scheduled with the default Askesis timings. Progress is saved in this browser.
All 50 cards
- 1Value types vs reference types in C#Value types (
int,double,bool,decimal, enums andstructs) hold their data directly and are copied on assignment; variables of reference types (classes,string, arrays, delegates) hold a reference to a shared object - 2What 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, sobis independentIfPointwere a class, both variables would refer to one object and it would print2. - 3Does
==compare string contents in C#?Yes.stringoverloads==to compare characters, unlike Java. For most other classes,==compares references unless the type overloads itIf both sides are typed asobject,==falls back to comparing references, even for strings. - 4
decimalvsdoubledoubleis binary floating point, fast but inexact (0.1 + 0.2 != 0.3);decimalis base-10 with 28–29 significant digits, the right choice for moneyDecimal literals need themsuffix:19.99m. - 5Format values inside a stringcsharp
var msg = $"Hi {name}, you owe {amount:C} ({ratio:P1})";Interpolated strings take format specifiers after a colon:Ccurrency,P1percent with one decimal,F2two decimals. - 6What is
int??A nullable value type, short forNullable<int>: anintthat can also benull. CheckHasValue, or use??to supply a default - 7
??and??=csharpstring display = name ?? "anonymous"; cache ??= new Dictionary<string, int>();??returns the right side when the left is null;??=assigns only when the variable is null. - 8What does
order?.Customer?.Nameevaluate to ifCustomeris null?null: the null-conditional operator?.short-circuits the rest of the chain instead of throwingNullReferenceException - 9
constvsreadonlyfieldsconstis a compile-time constant baked into every caller;readonlyis assigned once, at its declaration or in a constructor, and can hold a value computed at runtimeChanging a publicconstrequires recompiling the assemblies that use it. - 10What are nullable reference types?With
<Nullable>enable</Nullable>(C# 8+),stringmeans “never null” andstring?means “may be null”, and the compiler warns when you might dereference or assign nullNew project templates enable it by default since .NET 6. - 11What does the
!inuser!.Namedo?It is the null-forgiving operator: it silences the compiler’s nullability warning and does nothing at runtimeIfuserreally is null, you still get aNullReferenceException. - 12With nullable reference types on, can a
stringparameter still receive null?Yes. The annotations only produce compile-time warnings; callers can ignore them or have nullability disabled, so validate public inputscsharpArgumentNullException.ThrowIfNull(name); - 13Declare a property with a compiler-generated backing fieldcsharp
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. - 14A property that can only be set while the object is being createdcsharp
public string Name { get; init; } var user = new User { Name = "Ada" }; // OK user.Name = "Bob"; // compile errorinitaccessors arrived in C# 9. - 15What does the
requiredmodifier do?Forces callers to set the property in an object initializer, or the code doesn’t compile (C# 11+)csharppublic required string Email { get; init; } - 16A read-only property computed from other propertiescsharp
public string FullName => $"{First} {Last}";An expression-bodied property has no backing field; it is recomputed on every read. - 17Trim a value in a property setter without declaring a backing fieldcsharp
public string Name { get; set => field = value.Trim(); }Thefieldkeyword (C# 14, .NET 10) refers to the compiler-generated backing field. - 18What does
public record Person(string Name, int Age);declare?A class with init-only propertiesNameandAge, a constructor, value-based equality, a readableToString()and deconstruction (C# 9+)record struct(C# 10) is the value-type version. - 19What 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 printFalse - 20Copy a record with one property changedcsharp
var older = ada with { Age = 37 };Awithexpression creates a new record; the original is unchanged. - 21C# access modifiers
public(everywhere),private(the containing type),protected(plus derived types),internal(the same assembly)Members areprivateby default; top-level types areinternalby default. - 22Interfaces in C#A contract a class or struct promises to implement; a type can implement many interfaces but inherit from only one classSince C# 8, interface members can have default implementations.
- 23How do you let a subclass override a method?Mark it
virtual(orabstract) in the base class andoverridein the derived class; methods aren’t virtual by defaultMark a classsealedto forbid inheriting from it. - 24What 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:newhides the base method instead of overriding it, so the call is resolved through the variable’s typeAWithoverrideinstead ofnew, it would printB. - 25What 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 propertiescsharp
public class OrderService(IOrderRepo repo) { public Order Get(int id) => repo.Find(id); } - 26Add a method to
stringwithout modifying itcsharppublic static class StringExtensions { public static bool IsBlank(this string s) => string.IsNullOrWhiteSpace(s); } bool blank = " ".IsBlank(); // trueAn extension method is a static method whose first parameter is markedthis; LINQ is built from them. - 27Get the sorted names of all adults with LINQcsharp
var names = people .Where(p => p.Age >= 18) .OrderBy(p => p.Name) .Select(p => p.Name) .ToList(); - 28What 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 theWhereruns whenCount()enumerates it, after4was added - 29Why call
ToList()on a LINQ query you use several times?A deferred query reruns its whole pipeline every time it is enumerated;ToList()orToArray()runs it once and stores the resultsWith Entity Framework, each enumeration of an unmaterialized query can be another database round trip. - 30
First()vsFirstOrDefault()vsSingle()Firstthrows if the sequence is empty;FirstOrDefaultreturnsdefault(null, 0…) instead;Singlethrows unless there is exactly one element - 31
SelectvsSelectManySelectmaps each element to one result;SelectManymaps each element to a sequence and flattens them into onecsharpvar allTags = posts.SelectMany(p => p.Tags); - 32Count people per city with LINQcsharp
var perCity = people .GroupBy(p => p.City) .Select(g => new { City = g.Key, Count = g.Count() }); - 33Why prefer
Any()overCount() > 0?Any()stops at the first element, whileCount()may have to enumerate the whole sequence - 34Write
people.Where(p => p.Age >= 18).Select(p => p.Name)in query syntaxcsharpvar names = from p in people where p.Age >= 18 select p.Name;The compiler translates query syntax into the same method calls. - 35
IEnumerable<T>vsIQueryable<T>LINQ onIEnumerableruns as C# code in memory; onIQueryableit builds an expression tree that a provider such as Entity Framework Core translates, e.g. into SQL - 36What does
awaitdo?Pauses theasyncmethod until the awaited task completes, without blocking the thread, then resumes it with the resultcsharppublic async Task<User> GetUserAsync(int id) { var json = await _http.GetStringAsync($"/users/{id}"); return JsonSerializer.Deserialize<User>(json)!; } - 37Why avoid
async void?The caller can’t await it or catch its exceptions, which typically crash the process instead. ReturnTaskorTask<T>;async voidis only for event handlers - 38Run two independent async calls concurrentlycsharp
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. - 39Why is calling
.Resultor.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)Preferawaitall the way up the call chain. - 40Give up on an async operation after five secondscsharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var data = await http.GetStringAsync(url, cts.Token);Pass aCancellationTokendown through async methods; cancellation surfaces as anOperationCanceledException. - 41What happens if you call an async method without awaiting it?It still starts, runs synchronously until its first incomplete
await, then returns aTask; any exception is stored in that task and goes unnoticed if nothing awaits it - 42Consume an asynchronous stream of itemscsharp
await foreach (var line in File.ReadLinesAsync(path)) { Console.WriteLine(line); }File.ReadLinesAsync(.NET 7+) returns anIAsyncEnumerable<string>(C# 8+); write your own withyield returninside anasyncmethod. - 43Make sure a file is disposed at the end of the methodcsharp
using var stream = File.OpenRead(path); // … use stream … // Dispose() runs when the enclosing scope endsAusingdeclaration (C# 8+) works with anyIDisposable;using (…) { }blocks are the older form. - 44A
switchexpression with relational patternscsharpstring Describe(int n) => n switch { < 0 => "negative", 0 => "zero", _ => "positive", };_is the discard pattern that matches anything. - 45How do C# generics differ from Java’s?They are reified: type arguments exist at runtime, so
typeof(T)works and aList<int>storesints without boxingConstrain type parameters withwhere, e.g.where T : IComparable<T>orwhere T : new(). - 46
FuncvsActionBuilt-in delegate types:Func<…, TResult>returns a value,Action<…>returnsvoidcsharpFunc<int, int> square = x => x * x; Action<string> log = Console.WriteLine; - 47The 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 resolvedInjecting a scoped service into a singleton is a classic bug. - 48How 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 collectordotnet new console,dotnet runanddotnet add package <name>cover day-to-day use. - 49Create an array, a list and a combined list with collection expressionscsharp
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. - 50Look up a dictionary key that may be missingcsharp
if (ages.TryGetValue("ada", out var age)) { Console.WriteLine(age); }The indexerages["ada"]throwsKeyNotFoundExceptionfor 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.
More in Programming
JavaScript
Master the JavaScript every developer uses daily: scope, closures, equality, array methods, promises, async/await and the event loop.
50 cardsAdvanced 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 cardsTypeScript
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 cardsPython
Learn idiomatic Python fast: comprehensions, f-strings, args and kwargs, generators, decorators, context managers and the standard library.
50 cardsGit & terminal
The Git commands and shell skills developers use every day: branching, rebasing, undoing mistakes, pipes, redirects, permissions and processes.
50 cardsJava
Core Java with short snippets: equals vs ==, records, sealed types, collections, generics, exceptions, streams, lambdas and JVM basics.
50 cardsKotlin
Kotlin fundamentals with short snippets: null safety, val vs var, data and sealed classes, extension functions, scope functions and coroutines.
50 cardsSwift
Swift fundamentals with short snippets: optionals, structs vs classes, protocols, enums with associated values, closures, ARC, async/await and SwiftUI.
50 cardsFlutter
Flutter and Dart basics: null safety, futures and streams, stateless vs stateful widgets, setState, hot reload, keys, layout and state management.
50 cardsPHP
Modern PHP 8 with short snippets: strict types, arrays, match, nullsafe and named arguments, enums, readonly, Composer, PDO and password_hash.
50 cardsRuby
Ruby fundamentals with short snippets: blocks, procs and lambdas, symbols, Enumerable, mixins, truthiness, method_missing, gems and Bundler.
50 cardsGo
Go (Golang) fundamentals with short, idiomatic snippets: slices vs arrays, maps, defer, error wrapping, interfaces, goroutines, channels, select and context.
50 cardsRust
Learn the Rust that the compiler keeps asking about: ownership, borrowing, lifetimes, Option and Result, traits, closures, smart pointers and async.
50 cardsC++
Modern C++ in short snippets: references vs pointers, RAII, smart pointers, move semantics, STL containers and algorithms, const, templates and UB.
50 cardsHaskell
Haskell fundamentals with short code: types and type classes, pattern matching, laziness, Maybe and Either, folds, Functor, Applicative and Monad.
50 cardsElixir
Elixir fundamentals with short snippets: immutability, pattern matching, the pipe operator, Enum vs Stream, processes, GenServer, supervisors and mix.
50 cardsx86-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 cardsRegex
Learn regular expressions for good: anchors, classes, greedy vs lazy quantifiers, groups, lookarounds, flags, replacements and the backtracking trap.
50 cards