C# 15 Practice Test: 20 Questions with Answers (.NET 11)

Test your C# 15 knowledge with 20 realistic practice questions covering union types, closed hierarchies, extension indexers, and .NET 11 memory rules.

Taking a realistic C# 15 practice test is the best way to verify if you are truly ready for .NET 11's major language updates. C# 15 introduces fundamental shifts to the type system and runtime ergonomics: native discriminated unions, closed class hierarchies, collection expression constructor arguments, extension indexers, and labeled loop break/continue. Reading about new syntax is one thing, but knowing how the Roslyn compiler behaves when edge cases, type constraints, and exhaustiveness checks come into play is what separates theory from production readiness.

C# 15 and .NET 11 Practice Questions Exam Banner
Test your skills on C# 15: 20 realistic compiler and architectural scenario questions.

In this free C# 15 practice test, you will solve 20 scenario-based questions covering every major feature shipping with .NET 11. Each question tests realistic code snippets, edge cases, compiler errors, and runtime performance nuances. If you need a comprehensive refresher before starting, check out our companion architecture guide: What's New in C# 15: Practical Features and Code Examples.

C# 15 & .NET 11 Practice Questions

Q1: Discriminated Union Declaration Syntax

An enterprise architecture team is designing an internal workflow engine. They want to define a strongly typed HttpResponseState that can strictly hold one of three distinct outcome records: OkResponse, BadRequestResponse, or NotFoundResponse.

Which of the following is the valid C# 15 declaration syntax for this discriminated union?

Check Answer
Explanation: The correct answer is public union HttpResponseState(OkResponse, BadRequestResponse, NotFoundResponse);.

• Option A is correct: C# 15 introduces the contextual keyword union. It uses a parenthesized parameter-style list of case types followed by a semicolon, mirroring primary constructor syntax.
• Option B is incorrect: Curly braces { } are reserved for namespaces, types, and enum blocks. They are not used for union type parameterization.
• Option C is incorrect: Pipe-separated | syntax is found in TypeScript and F#, but is not the C# 15 grammar.
• Option D is incorrect: Angle brackets < > signify generic type parameters rather than closed union case definitions.

Q2: Union Pattern Matching & Compiler Exhaustiveness

Review the following C# 15 code definition and switch expression:

public record Circle(double Radius);
public record Square(double Side);
public record Triangle(double Base, double Height);

public union Shape(Circle, Square, Triangle);

public double CalculateArea(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Square s => s.Side * s.Side
};

What occurs when this code is compiled?

Check Answer
Explanation: The correct answer is A compilation error (CS8509) is emitted because the switch expression does not handle all union cases.

• Why B is correct: The defining characteristic of native C# 15 union types is compiler-enforced exhaustiveness. Because the compiler knows with mathematical certainty that Shape can only be Circle, Square, or Triangle, omitting Triangle without a default arm produces a compile-time error.
• Why A & D are incorrect: The C# compiler never silently injects default return values or silent discard arms for sum types; doing so would undermine type safety.
• Why C is incorrect: Unlike legacy open class hierarchies where unhandled matches only emitted compiler warnings, union type mismatches are treated strictly by the compiler to prevent unhandled cases from reaching production.

Q3: Implicit Conversion Operators for Union Types

A developer writes an authentication service utilizing a C# 15 union:

public record Success(string Token);
public record InvalidCredentials(string Reason);
public record AccountLocked(DateTimeOffset Until);

public union AuthResult(Success, InvalidCredentials, AccountLocked);

public class AuthService
{
    public AuthResult Login(string user, string pass)
    {
        return new Success("jwt-bearer-token-12345");
    }
}

Why does the return new Success(...) statement compile without requiring explicit casting or factory methods?

Check Answer
Explanation: The correct answer is The compiler automatically synthesizes implicit conversion operators from each case type to the union type.

• Option A is correct: When a union is declared, the Roslyn compiler generates static implicit conversion operators (public static implicit operator AuthResult(Success value)) for every listed case. This allows seamless assignment and return operations without boilerplate.
• Option B is incorrect: Union cases do not share an inheritance hierarchy with the union itself. They remain standalone types.
• Option C & D are incorrect: Conversions are strictly resolved at compile time through generated static operators, not runtime dynamic boxing or duck typing.

Q4: Internal Storage Representation of C# 15 Unions

An architect is evaluating the memory layout of C# 15 unions for a high-frequency trading application.

How does the C# 15 compiler represent a declared union type in intermediate language (IL) by default?

Check Answer
Explanation: The correct answer is As a struct implementing the internal IUnion interface with an object? Value backing property.

• Option B is correct: By default, the Roslyn compiler emits unions as value types implementing IUnion. The active case instance is held in a single object? Value reference property accompanied by a type tag/index discriminator.
• Option A is incorrect: Overlapping memory layouts with reference types (managed pointers) would cause garbage collector memory corruption, which the CLR prevents.
• Option C & D are incorrect: C# 15 unions are first-class compiled types, not open generic structs or delegate closures.

Q5: Value Type Boxing Inside Union Types

Consider a high-throughput messaging system that handles sensor readings using readonly record struct:

public readonly record struct MetricPacket(int SensorId, double Value);
public readonly record struct HeartbeatPacket(long Timestamp);

public union IngestionEvent(MetricPacket, HeartbeatPacket);

// Invoked 500,000 times per second
public void Ingest(MetricPacket packet)
{
    IngestionEvent evt = packet;
    Process(evt);
}

What memory allocation behavior occurs on the IngestionEvent evt = packet; line?

Check Answer
Explanation: The correct answer is The value-type struct is boxed to object? on the managed heap because of the default union backing representation.

• Why A is correct: Because the default C# 15 union backing implementation relies on an object? Value property to store the underlying instance, passing a struct (value type) forces boxing to the managed heap. For ultra-hot paths (e.g., hundreds of thousands of ops/sec), this generates GC allocations that architects must account for.
• Why B is incorrect: Inline unmanaged storage is not yet the universal default representation for arbitrary mixed structs.
• Why C is incorrect: Structs are fully legal cases in C# 15 unions.
• Why D is incorrect: Thread-local storage is not utilized for local variable assignments.

Q6: Closed Hierarchies vs. Sealed Classes

What is the fundamental architectural distinction between the sealed modifier and the C# 15 closed modifier on classes?

Check Answer
Explanation: The correct answer is sealed prohibits all inheritance, whereas closed permits inheritance exclusively within the declaring assembly.

• Option B is correct: Prior to C# 15, classes were either completely open (abstract or unsealed) or completely shut (sealed). The closed modifier bridges this gap by creating finite inheritance hierarchies restricted to the declaring assembly, enabling controlled domain modeling.
• Option A is incorrect: sealed prevents inheritance completely across all scopes.
• Option C & D are incorrect: Both modifiers are fully enforced compiler constructs and have nothing to do with interface restrictions or IDE-only linter rules.

Q7: Cross-Assembly Derivation with Closed Hierarchies

Assembly Billing.Core.dll exports the following public domain hierarchy:

namespace Billing.Core;

public abstract closed class PaymentMethod { }
public class CreditCardMethod : PaymentMethod { }
public class WireTransferMethod : PaymentMethod { }

In a separate assembly named Billing.Plugins.Stripe.dll that references Billing.Core, an engineer writes:

using Billing.Core;

namespace Billing.Plugins.Stripe;

public class StripeMethod : PaymentMethod { }

What happens when building Billing.Plugins.Stripe.dll?

Check Answer
Explanation: The correct answer is Compilation fails with an error stating that types outside the declaring assembly cannot inherit from a closed class.

• Why A is correct: The closed modifier explicitly restricts direct subtyping to the declaring assembly boundary. Any external assembly attempting to derive from PaymentMethod will fail compilation with a hard error.
• Why B is incorrect: Access modifiers (like internal) on the derived class cannot bypass the assembly boundary restriction.
• Why C & D are incorrect: This constraint is enforced at compile time, eliminating the possibility of runtime type loading errors or silent warnings.

Q8: Pattern Matching Exhaustiveness with Closed Classes

Consider the following closed hierarchy located in the same project:

public abstract closed class OrderState;
public class PendingState : OrderState;
public class ShippedState : OrderState;
public class CancelledState : OrderState;

public string GetStatusMessage(OrderState state) => state switch
{
    PendingState => "Awaiting fulfillment",
    ShippedState => "In transit"
};

How does the C# 15 compiler treat this switch expression?

Check Answer
Explanation: The correct answer is It emits a compilation error (CS8509) because the compiler knows all possible subtypes of OrderState and CancelledState is unhandled.

• Why A is correct: Because OrderState is marked closed, the compiler has complete visibility into its finite subtypes (PendingState, ShippedState, and CancelledState). Just like with union types, omitting a subtype causes a compiler error rather than a runtime surprise.
• Why B is incorrect: Closed hierarchies were designed specifically to grant standard class hierarchies the exact same compile-time exhaustiveness guarantees that discriminated unions possess.
• Why C & D are incorrect: The compiler does not synthesize default values or downgrade missing arms to optional hints.

Q9: Collection Expressions with Constructor Arguments

A developer needs to initialize a List<int> with an initial capacity of 512 items using C# 15 collection expression syntax.

Which of the following is the correct syntax?

Check Answer
Explanation: The correct answer is List<int> list = [with(capacity: 512), 10, 20, 30];.

• Option A is correct: In C# 15, collection expressions support constructor and builder arguments using the with(...) syntax placed strictly as the first element within the brackets.
• Option B is incorrect: Bare named arguments are not recognized within collection expressions without the with(...) wrapper.
• Option C is incorrect: The with(...) clause must appear at the beginning of the expression to parameterize the collection builder before items are added.
• Option D is incorrect: The syntax uses parentheses with(...) representing constructor parameters, not object initializer braces with { }.

Q10: Custom Comparers and Key Collisions via with(...)

Examine the following C# 15 code snippet:

Dictionary<string, int> userRoles = [
    with(StringComparer.OrdinalIgnoreCase),
    new("Administrator", 1),
    new("ADMINISTRATOR", 2)
];

What occurs when this code executes at runtime?

Check Answer
Explanation: The correct answer is An ArgumentException is thrown because "Administrator" and "ADMINISTRATOR" collide under OrdinalIgnoreCase.

• Why A is correct: The with(...) clause successfully passes StringComparer.OrdinalIgnoreCase to the underlying Dictionary constructor. When the collection elements are added via Add(key, value), the second duplicate key produces a standard runtime ArgumentException: An item with the same key has already been added.
• Why B is incorrect: Collection expressions use collection addition semantics (which throw on duplicate dictionary keys) rather than indexer assignment semantics (which overwrite).
• Why C & D are incorrect: with(...) accepts any arguments that match an existing constructor or collection builder factory.

Q11: with(...) Applicability on Primitive Arrays

An engineer writes the following statement intending to allocate an array of pre-sized capacity:

int[] buffer = [with(capacity: 64), 1, 2, 3];

What is the outcome of compiling this code?

Check Answer
Explanation: The correct answer is Compilation error because raw arrays (T[]) do not have constructors or factory methods accepting capacity arguments.

• Option A is correct: The with(...) syntax resolves directly against the targeted type's constructors or collection builder pattern. Because primitive arrays (int[]) are fixed-length runtime structures lacking a (int capacity) constructor, the compiler cannot bind the argument and emits an error.
• Option B & C are incorrect: The compiler does not guess sizing semantics or silently ignore developer parameters.
• Option D is incorrect: The targeted type is explicitly an array, not a Span<int>.

Q12: Extension Indexer Block Declaration

An SDK provides a sealed third-party class SensorBuffer that only exposes double Read(int channel). You want to enable indexer syntax (e.g. buffer[channel]) across your project without wrapping the class.

Which C# 15 syntax correctly declares this extension indexer?

Check Answer
Explanation: The correct answer is public static class SensorExtensions { extension(SensorBuffer buf) { public double this[int channel] => buf.Read(channel); } }.

• Option A is correct: C# 15 introduces extension indexers using the modern extension block syntax (extension(TargetType targetInstance) { ... }) introduced in C# 14 for extension members. Inside the block, standard indexer syntax this[...] is declared with access to the named receiver.
• Option B is incorrect: Traditional extension method syntax with the this keyword on parameters cannot be applied to indexers.
• Option C & D are incorrect: These use non-existent syntax variants not recognized by the Roslyn compiler.

Q13: Ref Returns in Extension Indexers

Consider the following extension indexer defined on a custom low-latency structure:

public static class NativeBufferExtensions
{
    extension(ref MemoryBlock block)
    {
        public ref int this[int offset] => ref block.GetSlot(offset);
    }
}

Is this valid C# 15 code, and what capability does it provide to callers?

Check Answer
Explanation: The correct answer is Yes, extension indexers support ref receivers and ref returns, enabling zero-copy in-place mutations.

• Option A is correct: C# 15 extension indexers fully integrate with C#'s high-performance reference mechanics. You can mark the receiver with ref (allowing operations on mutable structs) and return ref T, allowing callers to write block[0] = 42; with zero intermediate copies.
• Option B & C are incorrect: Neither ref returns nor ref receivers are prohibited in extension blocks.
• Option D is incorrect: ref receivers are specifically designed for value types (structs).

Q14: Member Precedence (Instance vs. Extension Indexer)

A library class MatrixData defines an existing instance indexer:

public class MatrixData
{
    public double this[int index] => 100.0;
}

A consumer imports a namespace declaring an extension indexer on MatrixData with the identical signature:

public static class MatrixExtensions
{
    extension(MatrixData data)
    {
        public double this[int index] => 200.0;
    }
}

What value is returned when executing new MatrixData()[0]?

Check Answer
Explanation: The correct answer is 100.0 (The instance indexer always takes precedence over the extension indexer).

• Option A is correct: In accordance with C#'s core member lookup rules, instance members always take precedence over extension members with identical signatures. The extension indexer is completely shadowed.
• Option B is incorrect: Extensions never override or shadow existing accessible instance members.
• Option C & D are incorrect: Ambiguity errors only occur between two conflicting extension members within the same resolution scope, never between an instance member and an extension member.

Q15: Labeled Break in Deeply Nested Loops

Review the following nested loop searching for a specific value in a large grid:

searchLoop:
for (int row = 0; row < maxRows; row++)
{
    for (int col = 0; col < maxCols; col++)
    {
        if (grid[row, col] == target)
        {
            RecordCoordinates(row, col);
            break searchLoop;
        }
    }
}
Console.WriteLine("Search finished.");

What happens when the break searchLoop; statement executes?

Check Answer
Explanation: The correct answer is It immediately breaks out of both the inner and outer loops, transferring execution directly to Console.WriteLine.

• Why A is correct: C# 15 introduces labeled break and continue statements. By prefixing the outer loop with a label (searchLoop:), executing break searchLoop; cleanly exits all nested enclosing loops up to and including the targeted loop without requiring temporary boolean flags or unstructured goto.
• Why B is incorrect: A standard unlabeled break; would only exit the inner loop.
• Why C is incorrect: Labeled break exits the loop; it does not restart it.
• Why D is incorrect: C# 15 officially supports labels on for, foreach, while, and do..while blocks.

Q16: Labeled Continue Flow Mechanics

Examine the following batch data processing routine:

customerLoop:
foreach (var customer in accounts)
{
    foreach (var transaction in customer.Transactions)
    {
        if (transaction.IsFlagged)
        {
            AlertCompliance(customer, transaction);
            continue customerLoop;
        }
        ApplyTransaction(transaction);
    }
    AuditAccount(customer);
}

If a transaction evaluates to IsFlagged == true, what is the exact execution flow?

Check Answer
Explanation: The correct answer is It skips all remaining transactions for the current customer and skips AuditAccount, advancing immediately to the next customer in accounts.

• Why A is correct: The statement continue customerLoop; targets the outer loop. It bypasses any remaining code inside the inner loop as well as any trailing statements in the outer loop (such as AuditAccount), immediately advancing the outer iterator to the next customer.
• Why B is incorrect: B describes the behavior of an unlabeled continue; targeting only the inner loop.
• Why C is incorrect: C describes break customerLoop;.
• Why D is incorrect: continue transfers control to the loop condition/iterator step, skipping remaining statements in the block.

Q17: Lexical Boundary Limitations on Loop Labels

An engineer writes the following code attempting to break an outer loop from inside a LINQ lambda expression:

processLoop:
for (int i = 0; i < items.Count; i++)
{
    items[i].Processors.ForEach(proc =>
    {
        if (proc.HasFatalError)
        {
            break processLoop; // Attempt to break outer loop from lambda
        }
    });
}

What happens when building this code?

Check Answer
Explanation: The correct answer is Compilation error because labeled break and continue cannot cross lambda or local function boundaries.

• Option A is correct: In C# 15, labeled control flow statements adhere strictly to lexical method boundaries. A labeled break or continue cannot cross lambda expressions, anonymous methods, or local functions.
• Option B is incorrect: Labels are not variables; they cannot be lifted into compiler-generated display classes (closures).
• Option C & D are incorrect: This constraint is caught immediately by the Roslyn compiler as a compile-time syntax error.

Q18: Granular Memory Safety (Pointer Declarations vs. Dereferencing)

Under C# 15's updated memory safety rules (with the preview feature enabled), which of the following operations is now permitted outside of an unsafe context?

// Code running in a standard, non-unsafe method:
int number = 42;

// Operation 1: Declaring a pointer and taking an address
int* pNum = &number;

// Operation 2: Dereferencing a pointer to read memory
int readVal = *pNum;
Check Answer
Explanation: The correct answer is Operation 1 only (declaring pointer types and address-of operations no longer require an unsafe context).

• Option A is correct: C# 15 decouples pointer *declarations* and address-of operations (&) from actual *memory access*. Declaring int* p = &x; is not inherently dangerous. The hazardous operation is pointer indirection/dereferencing (*p), which still strictly requires an unsafe context.
• Option B & C are incorrect: Dereferencing unmanaged pointers can cause memory corruption or segmentation faults and is never allowed in a safe context.
• Option D is incorrect: D describes the legacy pre-C# 15 model.

Q19: Expression-Scoped Unsafe Syntax

An engineer is designing a low-level memory struct and needs to initialize a field with native pointer arithmetic in a single expression:

public class NativeBuffer
{
    private readonly int _elementSize = unsafe(sizeof(nint)) * 64;
}

What capability does the unsafe(...) syntax represent in C# 15?

Check Answer
Explanation: The correct answer is Expression-scoped unsafe: It restricts the unsafe context to an isolated expression without requiring block statements.

• Option A is correct: C# 15 introduces expression-scoped unsafe(...). Previously, using unsafe operations required decorating an entire method or wrapping code in multi-line unsafe { ... } blocks. Expression-scoped unsafe allows fine-grained, surgical unsafe evaluations inside field initializers, primary constructor arguments, and LINQ queries.
• Option B & C are incorrect: It is purely a compile-time lexical scoping mechanism with no runtime exception interception or GC pausing overhead.
• Option D is incorrect: Expression-scoped unsafe is a headline memory refinement in C# 15.

Q20: .NET 11 Lifecycle & C# 15 Preview Tooling Configuration

An engineering lead is preparing their organization's tech roadmap for .NET 11 and C# 15. The team needs to configure their .csproj file to test C# 15 language features during the preview cycle, while understanding Microsoft's official support lifecycle.

Which of the following statements is 100% accurate?

Check Answer
Explanation: The correct answer is Set <TargetFramework>net11.0</TargetFramework> and <LangVersion>preview</LangVersion>; .NET 11 is an STS release with 24 months of support.

• Option A is correct: Microsoft's cadence releases even-numbered versions as Long Term Support (LTS, e.g. .NET 10 for 3 years) and odd-numbered versions as Standard Term Support (STS, e.g. .NET 11 for 24 months). To enable C# 15 features during the preview/RC phase, set <LangVersion>preview</LangVersion> under target framework net11.0.
• Option B is incorrect: .NET 11 is an STS release, not an LTS release.
• Option C is incorrect: Major C# 15 language innovations (like runtime async and union type layouts) require the updated .NET 11 runtime engine.
• Option D is incorrect: Modern .NET STS releases receive 24 months of free patches and updates, not 12.
Get My Final Score

C# 15 & .NET 11 Architecture Cheat Sheet: Features & Rules

Before deploying C# 15 into enterprise production services, bookmark this core comparison reference summarizing syntax boundaries, compiler enforcement, and best use cases:

Feature Syntax Signature Compiler Enforcement & Mechanism Primary Production Use Case
Discriminated Unions public union Result(Ok, Err); Generates IUnion struct with implicit conversions. Enforces exhaustive switch matching (CS8509). Result patterns, API responses, discrete domain state modeling.
Closed Hierarchies public abstract closed class Node; Restricts direct subtype derivation to declaring assembly. Enforces exhaustive switch expressions. Domain-Driven Design (DDD) aggregate state models, finite class trees.
Collection with(...) [with(capacity: 256), a, b] Passes constructor/builder arguments directly during collection expression initialization. Minimizing internal array reallocations and reducing GC pressure in hot paths.
Extension Indexers extension(Type t) { this[k] => ... } Adds indexer brackets to external types via extension blocks. Instance indexers shadow extensions. Enhancing third-party SDKs, custom memory buffers, and domain DSLs.
Labeled Break/Continue loop: for(...) { break loop; } Allows targeted jump out of deeply nested loops. Cannot cross lambda or local function boundaries. Matrix algorithms, search grids, multi-layer batch processing loops.
Granular Memory Safety int* p = &x; unsafe(*p); Decouples pointer declaration (safe) from memory dereference (unsafe). Introduces expression unsafe(...). High-performance systems programming, interop, and native zero-copy memory buffers.
.NET 11 Lifecycle <LangVersion>preview</LangVersion> Standard Term Support (STS) release with 24 months of support. Requires net11.0 target framework. Modernizing cloud microservices and adopting cutting-edge language innovations.

Key Takeaways for C# 15 Architecture

• Union Types vs. Open Hierarchies: Prefer union when modeling a finite set of states where no inheritance is desired. Use closed class hierarchies when your subtypes require shared state, base virtual methods, or object-oriented polymorphism within your assembly.

• Exhaustive Pattern Matching: Both union types and closed class hierarchies enforce compile-time exhaustiveness in switch expressions. Avoid adding fallback _ => discard arms unless strictly necessary, so the compiler alerts you whenever a new subtype is added.

• Allocation Awareness with Struct Unions: Storing value-type structs in default C# 15 unions triggers heap boxing to object?. Always benchmark mission-critical high-frequency pipelines before wrapping structs in unions.

• Clean Loop Exits: Replace clumsy boolean flags (such as bool isFound = false;) with labeled break [label]; and continue [label]; to keep nested search algorithms readable and maintainable.

Frequently Asked Questions (C# 15 & .NET 11 Practice Exam FAQ)

Common questions regarding C# 15 features, compiler rules, and preparation:

What is the difference between C# 15 union types and OneOf NuGet?

The OneOf NuGet package provides union behavior via generic wrappers (e.g. OneOf<T0, T1>) relying on runtime type checks and delegate matching. C# 15 native unions are first-class language constructs: the Roslyn compiler synthesizes implicit conversion operators, enforces exhaustive pattern matching at compile time, and eliminates third-party dependencies.

Can I use C# 15 features in projects targeting .NET 10 or .NET 8?

Some purely syntactic features (like labeled break/continue or collection with arguments) can theoretically be compiled against older runtimes if required compiler polyfills exist. However, core features like native union types and closed hierarchies rely on runtime metadata and .NET 11 runtime engine support. Target net11.0 for full compatibility.

Why did my collection expression with() statement throw a duplicate key exception?

Collection expressions targeting Dictionary types execute the collection Add method. If you pass StringComparer.OrdinalIgnoreCase via with(...) and insert keys that differ only in casing, the dictionary detects colliding hashes and throws an ArgumentException, exactly as if calling Add directly.

How do I enable the updated C# 15 memory safety rules in .csproj?

To opt into the modernized memory safety model in .NET 11, add <AllowUnsafeBlocks>true</AllowUnsafeBlocks> and <Features>$(Features);updated-memory-safety-rules</Features> under your PropertyGroup with LangVersion set to preview.

Next Step in Your .NET 11 Mastery

Congratulations on completing the 20-question practice test! Whether you aced all 20 questions or identified a few compiler nuances to brush up on, mastering these features puts you ahead of the curve as .NET 11 approaches its official General Availability in November 2026.

To deepen your practical skills with complete code walkthroughs, visit our core architectural guide:

About the author

MOHAMMED KADI
Software Engineer. Passionate about IT certifications, automation, and building scalable tech solutions.

Post a Comment

Welcome to Iwalen.com! If you have any questions or need assistance with any of our resources, feel free to ask. Please keep the discussion professional and avoid posting external links. All comments are moderated to ensure a high-quality community experience.