C# & .NET Framework The .NET Grand Finale
💡
Exercise 50

🏆 The .NET Grand Finale — Final Boss 50 XP Hard

Ctrl+Enter Run Ctrl+S Save

🏆 THE FINAL BOSS

You've built an entire city with C# and .NET. You've mastered variables, control flow, OOP, advanced patterns, async programming, exception handling, and runtime internals. Now it's time to prove you're a true .NET Architect.

These 20 questions are senior-level interview questions — the kind that separate junior developers from architects. They cover GC internals, async state machines, modern C# features, performance optimization, and framework deep-dives.

No hints. No safety net. Just you and the code.

Good luck, Architect. 🏗️

📋 Instructions
🧠 Quiz Time
0 / 20 answered
1
In .NET's generational garbage collector, what happens during a Gen 2 collection that does NOT happen during a Gen 0 collection?
A. Only unreachable objects are reclaimed
B. The Large Object Heap (LOH) is also collected and objects in Gen 0 and Gen 1 are also examined
C. The finalizer thread is started for the first time
D. All managed threads are permanently suspended
A Gen 2 collection is a full collection — it examines all three generations (0, 1, 2) AND the Large Object Heap. Gen 0 collections only examine Gen 0 objects, making them much faster. The finalizer thread runs independently and is not started by a specific generation's collection. Threads are suspended during GC (stop-the-world) but not permanently.
2
When you mark a method as `async` and use `await`, what does the C# compiler generate behind the scenes?
A. A new Thread is created and started for each await expression
B. The method is converted into a state machine struct implementing IAsyncStateMachine with MoveNext() logic
C. The method body is wrapped in a Task.Run() call automatically
D. The CLR interprets the method differently at runtime using reflection
The C# compiler transforms async methods into a state machine (a struct in Release builds, a class in Debug). It implements IAsyncStateMachine with a MoveNext() method that contains the original logic split across states at each await point. The AsyncTaskMethodBuilder manages the Task lifecycle. No new threads are created by async/await itself — it's purely a compiler transformation.
3
When should you prefer `ValueTask<T>` over `Task<T>`?
A. Always — ValueTask<T> is strictly superior to Task<T> in every scenario
B. When the async method frequently completes synchronously, avoiding heap allocation of a Task object
C. When you need to await the result multiple times in different places
D. When the return type is a reference type rather than a value type
ValueTask<T> is a discriminated union of T and Task<T>. When the result is available synchronously (e.g., reading from a cache), it avoids allocating a Task object on the heap. However, ValueTask<T> has restrictions: it can only be awaited once, should not be consumed concurrently, and should not call .GetAwaiter().GetResult() unless completed. For most code, Task<T> is safer and simpler.
4
What is the primary purpose of `ConfigureAwait(false)` in library code?
A. It makes the async operation run on a background thread instead of the UI thread
B. It tells the awaiter not to capture and marshal back to the original SynchronizationContext, preventing potential deadlocks
C. It disables exception handling for the awaited task
D. It forces the continuation to run synchronously on the same thread
By default, await captures the current SynchronizationContext (e.g., the UI thread in WPF/WinForms) and posts the continuation back to it. In library code, this is unnecessary and can cause deadlocks if the caller blocks with .Result or .Wait(). ConfigureAwait(false) opts out of context capture, allowing the continuation to run on any available thread pool thread.
5
What is `Span<T>` and why is it significant for performance?
A. A heap-allocated wrapper around arrays that provides bounds checking
B. A ref struct that provides a type-safe, memory-safe view over contiguous memory regions without heap allocations
C. A thread-safe collection type for concurrent access to array elements
D. A generic collection that replaces List<T> for better performance in all scenarios
Span<T> is a ref struct (stack-only) that represents a contiguous region of arbitrary memory — managed arrays, native memory, or stack-allocated memory. It enables slicing and processing without copying or allocating. Being a ref struct, it cannot be boxed, stored in fields of classes, or used across await boundaries. Memory<T> is the heap-friendly counterpart that can be stored in fields and used in async code.
6
What does `IAsyncDisposable` provide that `IDisposable` does not?
A. It allows disposal of managed resources only, while IDisposable handles unmanaged resources
B. It defines a DisposeAsync() method returning ValueTask, enabling non-blocking cleanup of resources that require async operations
C. It automatically disposes objects when they go out of scope without a using statement
D. It runs the disposal logic on a separate thread to avoid blocking
IAsyncDisposable defines `ValueTask DisposeAsync()`, enabling resources that require asynchronous cleanup (e.g., flushing an async stream, closing a network connection gracefully) to be disposed without blocking. It's used with `await using` syntax: `await using var conn = new AsyncDbConnection();`. This is critical for ASP.NET Core and other async-heavy frameworks.
7
In C# 8's nullable reference types feature, what does the `?` annotation on a reference type (e.g., `string?`) signify?
A. The variable is stored as a Nullable<string> value type on the stack
B. It's a compile-time annotation indicating the variable may legitimately hold null, and the compiler will warn if you dereference it without a null check
C. It makes the string immutable and read-only at runtime
D. It causes a NullReferenceException to be thrown at compile time if null is assigned
Nullable reference types (NRT) in C# 8+ are purely a compile-time feature — they add no runtime overhead or type changes. `string?` tells the compiler this variable may be null, so it emits warnings if you dereference it without checking. `string` (non-nullable) means the compiler warns if you assign null to it. This is enforced via [NullableContext] attributes and flow analysis, not runtime checks.
8
What are `init`-only setters and `record` types designed to support in C# 9+?
A. Mutable data transfer objects optimized for serialization performance
B. Immutable-by-default data types with value-based equality, supporting non-destructive mutation via `with` expressions
C. Thread-safe collections that prevent concurrent modification
D. Database entity types that automatically map to SQL tables
Records (`record class` and `record struct`) provide value-based equality (Equals, GetHashCode, ==), a synthesized ToString(), and support `with` expressions for non-destructive mutation (creating a copy with some properties changed). Init-only setters (`init` keyword) allow properties to be set during object initialization but not afterward, enforcing immutability. Together, they make it easy to model immutable data.
9
What are C# Source Generators, and at which phase do they execute?
A. Runtime code generation using System.Reflection.Emit that creates types dynamically
B. Compile-time components that participate in the Roslyn compilation pipeline, inspecting syntax/semantic models and emitting additional C# source files
C. Post-compilation IL rewriters that modify the output assembly
D. MSBuild tasks that run before compilation to generate .cs files from templates
Source Generators are a Roslyn compiler feature (introduced in .NET 5) that lets you write code that runs during compilation. They receive a GeneratorExecutionContext with access to the syntax trees and semantic model of the user's code, and can add new source files to the compilation. Unlike reflection or IL weaving, the generated code is visible, debuggable, and has zero runtime cost. Used by System.Text.Json, Regex, and logging source generators.
10
What is .NET Native AOT (Ahead-of-Time) compilation, and what is its primary trade-off?
A. It compiles C# to JavaScript for browser execution, trading type safety for portability
B. It compiles IL directly to native machine code at publish time, enabling faster startup and smaller footprint but sacrificing unrestricted reflection and runtime code generation
C. It interprets IL at runtime without JIT compilation, trading performance for simplicity
D. It converts C# to C++ source code, trading compilation speed for runtime performance
Native AOT compiles everything at publish time into a self-contained native executable — no JIT, no IL, no runtime compilation. Benefits: near-instant startup, smaller memory footprint, no .NET runtime dependency. Trade-offs: no dynamic assembly loading, limited reflection (only what's statically analyzable via trim analysis), no Reflection.Emit or runtime code generation. Ideal for cloud functions, CLI tools, and microservices.
11
What is `Channel<T>` in System.Threading.Channels, and when would you use it?
A. A network communication channel for sending data between processes via TCP
B. A high-performance, async-friendly producer-consumer data structure for passing messages between tasks
C. A database connection channel that pools connections for concurrent queries
D. A UI channel for marshaling events from background threads to the main thread
Channel<T> is a bounded or unbounded async producer-consumer queue. Producers call `Writer.WriteAsync()` and consumers call `Reader.ReadAsync()`. It's designed for high-throughput scenarios with backpressure support. Bounded channels block producers when full, preventing memory exhaustion. It replaces older patterns like BlockingCollection<T> with a modern, allocation-efficient, async-native API.
12
What is a key architectural difference between `System.Text.Json` and `Newtonsoft.Json`?
A. System.Text.Json only supports serialization, not deserialization
B. System.Text.Json uses a high-performance Utf8JsonReader/Writer operating on UTF-8 bytes directly, avoiding string allocations, while Newtonsoft operates on UTF-16 strings
C. Newtonsoft.Json is faster because it uses native code, while System.Text.Json is pure managed code
D. System.Text.Json requires .NET Framework 4.8 while Newtonsoft works on .NET Core
System.Text.Json processes JSON as UTF-8 bytes (Utf8JsonReader/Utf8JsonWriter), avoiding the cost of transcoding to/from UTF-16 strings. It's designed for low-allocation, high-throughput scenarios. Newtonsoft.Json operates on UTF-16 strings (JsonTextReader/JsonTextWriter) and is more feature-rich (better support for dynamic, polymorphism, custom converters). System.Text.Json also supports source-generated serialization for AOT compatibility.
13
In ASP.NET Core Minimal APIs (.NET 6+), what is fundamentally different about endpoint routing compared to traditional MVC controllers?
A. Minimal APIs don't support middleware or dependency injection
B. Endpoints are defined as lambda expressions or method references mapped directly to routes, eliminating the need for controller classes and attribute-based routing
C. Minimal APIs bypass the ASP.NET Core pipeline entirely for better performance
D. They require manual HTTP parsing since the framework doesn't bind parameters
Minimal APIs use `app.MapGet("/route", handler)` syntax where handlers are lambdas or method groups. Parameters are bound from route values, query strings, headers, or the request body through a convention-based system. They still use the full ASP.NET Core middleware pipeline and support DI, filters (in .NET 7+), and model binding. They reduce ceremony compared to MVC controllers for simple APIs.
14
In ASP.NET Core's built-in dependency injection, what is the difference between Transient, Scoped, and Singleton lifetimes?
A. Transient creates one instance per app, Scoped per request, Singleton per method call
B. Transient creates a new instance every time it's requested; Scoped creates one instance per scope (typically per HTTP request); Singleton creates one instance for the application's lifetime
C. All three are identical but named differently for documentation purposes
D. Transient is for value types, Scoped for interfaces, Singleton for classes
Transient: new instance every time the service is resolved from the container. High garbage collection but no shared state issues. Scoped: one instance per scope (in ASP.NET Core, a scope is created per HTTP request). Perfect for DbContext and unit-of-work patterns. Singleton: one instance for the entire application lifetime, shared across all requests and threads. Critical pitfall: never inject a Scoped service into a Singleton — it creates a 'captive dependency' that holds onto a stale scoped instance.
15
In ASP.NET Core's middleware pipeline, what determines the order in which middleware components process an HTTP request?
A. Middleware is sorted alphabetically by class name at startup
B. The order of `app.Use*()` / `app.Map()` calls in the Configure/Program pipeline defines the exact order — each middleware calls `next()` to pass to the next in the chain
C. ASP.NET Core automatically optimizes the order based on the middleware type
D. Middleware order is determined by dependency injection registration order
Middleware order is explicitly defined by the order of `app.UseXyz()` calls. Each middleware receives a RequestDelegate (the next middleware) and decides whether to call it. The pipeline is a Russian nesting doll: requests flow in through each middleware, hit the endpoint, then flow back out in reverse order. Order matters critically — e.g., UseAuthentication must come before UseAuthorization, and UseExceptionHandler should be first to catch all exceptions.
16
In Entity Framework Core, what is change tracking and how does it affect performance?
A. Change tracking logs all SQL queries to a file for debugging purposes
B. The DbContext maintains a snapshot of entity states (Added, Modified, Deleted, Unchanged) and detects changes by comparing current values to the snapshot during SaveChanges()
C. Change tracking uses database triggers to monitor data changes outside the application
D. It tracks schema changes and automatically generates new migrations
EF Core's change tracker takes a snapshot of entity property values when they're loaded from the database. During SaveChanges(), it compares current values to the snapshot (DetectChanges) to determine what SQL to generate. This has memory overhead (storing snapshots) and CPU cost (comparison). For read-only queries, use `.AsNoTracking()` to skip snapshot creation, significantly reducing memory usage and improving query performance.
17
What is the Roslyn compiler platform, and what capability does it provide to developers beyond compilation?
A. Roslyn is a binary compiler that converts IL to native code, replacing the JIT
B. Roslyn exposes the C#/VB compiler as a set of APIs with full access to syntax trees, semantic models, and the compilation pipeline, enabling analyzers, code fixes, and source generators
C. Roslyn is an IDE plugin that provides syntax highlighting but has no compiler functionality
D. Roslyn is a package manager for .NET NuGet packages
Roslyn ('compiler as a service') reimplemented the C# and VB compilers as a set of open APIs. Developers can access SyntaxTree (AST), SemanticModel (types, symbols, flow analysis), and the full Compilation pipeline. This enables: Roslyn Analyzers (custom compile-time warnings/errors), Code Fix Providers (automated refactorings), Source Generators, and scripting (C# REPL). Visual Studio's IntelliSense, refactoring, and live error detection all use Roslyn.
18
What does the `unsafe` keyword enable in C#, and what does the `fixed` statement do within unsafe code?
A. `unsafe` disables all runtime checks; `fixed` makes a variable constant (like readonly)
B. `unsafe` allows direct pointer manipulation and bypasses type safety; `fixed` pins a managed object in memory so the GC won't move it while you hold a pointer to it
C. `unsafe` enables multi-threaded code; `fixed` locks a thread to a specific CPU core
D. `unsafe` removes bounds checking from arrays; `fixed` prevents array resizing
The `unsafe` keyword enables pointer types (int*, byte*), pointer arithmetic, and the address-of operator (&). The GC can move managed objects during compaction, which would invalidate pointers. The `fixed` statement pins an object so the GC won't relocate it for the duration of the fixed block. Common uses: interop with native code, high-performance buffer manipulation, and working with hardware. Requires `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>` in the project file.
19
How can you create a custom awaitable type in C# without inheriting from Task?
A. Implement the ITask interface from System.Threading
B. Provide a GetAwaiter() method that returns an object implementing INotifyCompletion with IsCompleted, GetResult(), and OnCompleted()
C. Decorate the class with the [Awaitable] attribute from System.Runtime.CompilerServices
D. Override the implicit cast operator to convert your type to Task
The C# `await` pattern is duck-typed — any type that exposes a `GetAwaiter()` method (instance or extension) returning an awaiter is awaitable. The awaiter must implement `INotifyCompletion` (or `ICriticalNotifyCompletion` for perf) and provide: `bool IsCompleted { get; }` to check synchronous completion, `T GetResult()` to retrieve the result or rethrow exceptions, and `void OnCompleted(Action continuation)` to register a callback. This pattern enables awaiting custom types like ValueTask, YieldAwaitable, and even TimeSpan via extension methods.
20
When using BenchmarkDotNet to measure C# code performance, what is the significance of achieving 'allocation-free' code in a hot path?
A. Allocation-free code runs on the GPU instead of the CPU for better parallelism
B. Zero heap allocations in hot paths eliminate GC pressure, preventing Gen 0 collections that cause latency spikes, which is critical for low-latency systems like trading engines and game loops
C. Allocation-free means the code uses no variables at all, reducing register pressure
D. It means the code compiles to fewer IL instructions, making the assembly smaller
Every heap allocation contributes to GC pressure. When Gen 0 fills up, a collection pauses all managed threads (stop-the-world). In latency-sensitive code (trading systems, game engines, real-time audio), even microsecond pauses are unacceptable. BenchmarkDotNet's [MemoryDiagnoser] shows allocations per operation. Techniques for allocation-free code: use Span<T>, stackalloc, ArrayPool<T>, value types, string.Create(), and ref structs. Profile real workloads — premature optimization of cold paths wastes engineering effort.
main.py
Hi! I'm Rex 👋
Output
Ready. Press ▶ Run or Ctrl+Enter.