Interview Handbook by Sayed Elmahdy العربية

Topic 06 · C# & .NET

C# 14, .NET 10 & performance

What's new in C# 14 and .NET 10, old-vs-new comparisons, Span, source generators, BenchmarkDotNet.

18 questionsJunior · 5Mid · 7Senior · 610 quiz

Concepts

What you will learn: Learn how modern C# features can make code clearer and when a performance improvement is worth its complexity. We will separate compiler features from runtime features and measure before changing or deployment, because newer syntax does not guarantee a faster program.

Before you start: You should be able to read a basic C# class, method and collection; revisit topic 02 if needed. A benchmark repeatedly measures a defined case, while profiling shows where an application spends its time. Do not rely on guesses alone.

Select a term with a dotted underline for its meaning, then continue reading from the same place.

#C# 10–14 and .NET 6–10 timeline

Separate language syntax from runtime and library features. C# 14 ships with the 10 ; setting LangVersion alone cannot add missing runtime .

ReleaseLanguagePlatform
C# 10 / .NET 6Global usings, file-scoped namespaces, record structsMinimal APIs, DateOnly/TimeOnly, Parallel.ForEachAsync, WaitAsync
C# 11 / .NET 7Required members, raw strings, list patterns, generic math supportRate limiting and output caching
C# 12 / .NET 8Primary constructors and collection expressionsKeyed , TimeProvider, frozen collections, IExceptionHandler, dynamic by default, Minimal API Native
C# 13 / .NET 9Params collections, Lock support, \e escape; field was previewHybridCache, OpenAPI generation, Guid.CreateVersion7, Task.WhenEach, CountBy/AggregateBy/Index, MapStaticAssets
C# 14 / .NET 10Extension members, field, null-conditional assignment, unbound-generic nameof, conversions, modifiers, partial constructors/events, compound assignment operatorsFile-based apps; validation, OpenAPI 3.1, , passkeys, System.Text.Json Patch; named filters, LeftJoin/RightJoin, improved complex types, JSON and vectors

.NET 10 is , released November 2025 and supported through November 2028. .NET 8 and .NET 9 end support in November 2026; support became 24 months with .NET 9. Examples target net10.0 and C# 14.

#Records, required, init and primary constructors

A needs explicit data without boilerplate; a service needs a dependency without a repetitive constructor. These tools reduce ceremony but do not replace validation or ownership design.

public record Price(decimal Amount, string Currency);           // (1)
public sealed class CreateProduct
{
    public required string Name { get; init; }                  // (2)
    public required decimal Price { get; init; }
}
public sealed class CouponService(TimeProvider clock)           // (3)
{
    public bool IsValid(DateTimeOffset expiry) => clock.GetUtcNow() < expiry;
}

// Statements below belong in a caller method.
var price = new Price(100m, "EGP");
var taxed = price with { Amount = 114m };                        // (4)
List<int> original = [10, 20];
int[] ids = [.. original, 30];                                  // (5)
🧾 Code walkthrough
  1. (1) A positional record class generates value equality and init properties.
  2. (2) required forces source callers to initialize a member; init restricts assignment to initialization. The value can still be invalid.
  3. (3) A primary constructor parameter is available inside the class but is neither an automatic property nor readonly.
  4. (4) with makes a shallow copy; mutable referenced objects may remain shared.
  5. (5) Collection expressions need a target type; spreading copies elements rather than transferring ownership.

Use a regular class when entity identity defines equality. required does not mean non-empty, and init on a List does not prevent Add. Validate input at runtime at boundaries.

#Pattern matching: type, shape and value

Nested conditionals and casts obscure business rules. Patterns express conditions on data shape with an explicit case order; the first matching arm wins.

public record Parcel(decimal Total, string Country, decimal Weight);

static decimal Shipping(Parcel? order) => order switch
{
    null => throw new ArgumentNullException(nameof(order)),       // (1)
    { Total: >= 1000m } => 0m,                                  // (2)
    { Country: "EG", Weight: > 0m and <= 5m } => 50m,
    { Country: "SA" or "AE" } => 150m,
    _ => 300m                                                   // (3)
};

static string Describe(object? value) => value switch
{
    Price(> 0m, var currency) => currency,                       // (4)
    int[] [var first, .., var last] => $"{first}..{last}",        // (5)
    not null => "Other value",
    _ => "Missing"
};

static bool CanShip(string status, bool paid) =>
    (status, paid) is ("Ready", true);                           // (6)
🧾 Code walkthrough
  1. (1) Null has an explicit policy; property patterns do not match null.
  2. (2) Property, relational and logical patterns combine weight bounds with and and country alternatives with or.
  3. (3) The discard covers remaining inputs. Exhaustiveness warnings help but do not prove business-rule completeness.
  4. (4) A positional pattern uses the record's Deconstruct together with a type test.
  5. (5) The list pattern requires at least two elements; .. accepts any middle length.
  6. (6) The tuple pattern combines a decision based on two values.

Use explicit methods or strategies for large evolving rules or . Avoid side effects in pattern-accessed getters, and validate weight separately from selecting a shipping fee.

#C# 14 extension properties and static extension members

Classic extension methods could not expose property syntax. Extension blocks group members for a type without modifying that type or adding state to it.

public static class ProductExtensions
{
    // Existing syntax still works.
    public static bool IsValidSku(this string sku) => sku.Length > 0;

    extension(string sku)                                  // (1)
    {
        public bool HasSkuText => !string.IsNullOrWhiteSpace(sku);
        public string NormalizeSku() => sku.Trim().ToUpperInvariant();
    }

    extension(string)                                      // (2)
    {
        public static string DefaultSku => "UNASSIGNED";
    }
}

// In a caller method with the extensions' namespace imported:
bool valid = " mug-01 ".HasSkuText;                         // (3)
string normalized = " mug-01 ".NormalizeSku();
string fallback = string.DefaultSku;                       // (4)
🧾 Code walkthrough
  1. (1) The named receiver supplies the instance-like property and method.
  2. (2) A receiver type without a name supplies a static extension property.
  3. (3) Property-looking syntax is resolved at compile time and needs the extension namespace in scope.
  4. (4) Static extensions are invoked on the extended type.

This is neither inheritance nor virtual dispatch and adds no instance fields. Real instance members take precedence. Reserve properties for clear, inexpensive calculations rather than hidden network calls. Extension members reference.

#C# 14 field, null-conditional assignment and nameof

Property validation used to need a manual backing field, and assigning through an optional object needed a null check. C# 14 shortens both without adding concurrency or complete validation guarantees.

public sealed class Product
{
    public string Sku
    {
        get;
        set => field = !string.IsNullOrWhiteSpace(value)    // (1)
            ? value.Trim().ToUpperInvariant()
            : throw new ArgumentException("SKU is required");
    } = "UNASSIGNED";
}

public sealed class Order { public string Status { get; set; } = "New"; }

// In a caller method:
Order? order = null;
order?.Status = CreateStatus();                            // (2)
string typeName = nameof(List<>);                          // (3) "List"

static string CreateStatus() => "Ready";
🧾 Code walkthrough
  1. (1) field names a compiler-generated backing field. It is stable in C# 14 and was preview in C# 13. Initializers bypass the setter, so choose a valid initial value.
  2. (2) A null order skips both assignment and CreateStatus. This syntax does not permit ++ or -- and provides no locking.
  3. (3) nameof accepts an unbound generic without choosing type arguments. It produces a short name, not a unique type identifier.

Manual backing fields, explicit null checks and nameof(List<int>) remain valid. Disambiguate an existing member named field with this.field or @field. Do not silently skip an assignment when a missing order should be an error.

#C# 14 lambda modifiers, partial members and compound assignment

Some features primarily serve libraries and generators. Understand their purpose and limits rather than rewriting application code for novelty.

public delegate bool TryRead(string text, out int result);
// In a caller method:
// TryRead parse = (text, out result) => int.TryParse(text, out result); // (1)

public partial class OrderNotice
{
    public partial OrderNotice(string id);                  // (2) definition
    public partial event EventHandler? Sent;
}
public partial class OrderNotice
{
    private readonly string _id;
    private EventHandler? _sent;
    public partial OrderNotice(string id) { _id = id; }     // implementation
    public partial event EventHandler? Sent                // (3)
    {
        add => _sent += value;
        remove => _sent -= value;
    }
    public void Send() => _sent?.Invoke(this, EventArgs.Empty);
}

public sealed class InvoiceTotal
{
    public decimal Amount { get; private set; }
    public void operator +=(decimal amount)                 // (4)
    {
        ArgumentOutOfRangeException.ThrowIfNegative(amount);
        Amount += amount;
    }
}
// Caller: var total = new InvoiceTotal(); total += 25m;
🧾 Code walkthrough
  1. (1) The supplies types, so an out parameter can omit its explicit type. The feature also covers ref, in, scoped and ref readonly; params still requires an explicit type.
  2. (2) A partial constructor has matching defining and implementing declarations, often split across handwritten and generated files.
  3. (3) The partial event implementation must supply add and remove accessors.
  4. (4) A user-defined compound assignment is a public instance operator returning void and taking one operand, mutating the existing object.

Previously += relied on operator + followed by assignment. The new form may mutate an object shared through aliases; document that behavior and avoid it for an value object. Generators are optional when both partial declarations are handwritten. Partial members and compound assignment reference.

#Old versus new: choices with reasons

Use the table to explain trade-offs rather than declaring old code wrong by age. Migrate when requirements, compatibility and cost justify it.

TraditionalModern choiceReason and limit
Startup.csMinimal hosting in Program.csFrom 6; the same and principles with less ceremony
Swashbuckle document and Microsoft.AspNetCore.OpenApi plus Scalar UIBuilt-in generation from .NET 9; choose a separate UI; Swashbuckle remains valid
Newtonsoft.JsonSystem.Text.Json source generationBuild-time metadata; verify converters and contract differences before migrating
IMemoryCacheHybridCacheFrom .NET 9; local cache, optional distributed backend and protection; IMemoryCache still fits simpler needs
Polly v7 policiesPolly v8 resilience pipelines and Microsoft.Extensions.Http.ResilienceComposable strategies; review timeouts, retries and
lock(object)lock(System.Threading.Lock)From C# 13 / .NET 9; synchronous only, and object locks remain valid
DateTime.NowTimeProvider.GetUtcNow()Testable time from .NET 8; convert time zones explicitly for display
ArrayListList<T>Type safety without value elements
WebClientHttpClient and IHttpClientFactoryAsync and handler pooling; a long-lived client with PooledConnectionLifetime is also valid
MediatR / AutoMapperCurrent versions under their terms, or explicit handlers, manual mapping and MapperlyCommercial licensing changes in 2025; review version-specific terms and eligibility
FluentAssertions v8AwesomeAssertions or Shouldly, according to terms and needsv8 is commercial; check team policy. xUnit v3 is current
// Web project; packages: Microsoft.AspNetCore.OpenApi, Scalar.AspNetCore.
using Scalar.AspNetCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();                          // (1)
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();                                  // (2)
    app.MapScalarApiReference();                        // (3)
}
app.MapGet("/products/{id:int}", (int id) => new { Id = id });
app.Run();
🧾 Code walkthrough
  1. (1) Registers document generation; 10 defaults to OpenAPI 3.1.
  2. (2) Exposes the document, not a UI.
  3. (3) The Scalar package supplies a separate UI, enabled here only in Development.

ASP.NET Core OpenAPI and Scalar integration.

#Span, Memory, stackalloc and ArrayPool

A worked example
  1. A byte array holds a message, and you only need its first four bytes. Allocating another array and copying them may be unnecessary.
  2. A is a view over existing memory; slicing the view does not copy the data. Writing through a writable span changes that same source memory.
  3. ReadOnlySpan prevents writes through that view, not all changes to the source. Respect memory lifetimes and Span restrictions; retaining a view across await may require an appropriate Memory representation.

A span is like a ruler marking part of a page: it views existing characters rather than photocopying them. The view is not a new buffer; the underlying memory must remain valid while it is used.

static int SumCsv(ReadOnlySpan<char> csv)
{
    int total = 0;
    foreach (Range range in csv.Split(','))                 // (1) .NET 9+
        total += int.Parse(csv[range]);
    return total;
}

static bool StartsWithValue<T>(ReadOnlySpan<T> values, T value)
    where T : IEquatable<T> =>
    !values.IsEmpty && values[0].Equals(value);

// In a caller method:
int[] ids = [10, 20];
bool starts = StartsWithValue(ids, 10);                      // (2) C# 14
Span<int> scratch = stackalloc int[32];                      // (3)
scratch.Clear();

static async Task CopyAsync(Stream source, Stream destination,
    CancellationToken ct)
{
    byte[] rented = ArrayPool<byte>.Shared.Rent(16 * 1024); // (4)
    try
    {
        Memory<byte> memory = rented.AsMemory(0, 16 * 1024);
        int read;
        while ((read = await source.ReadAsync(memory, ct)) != 0)
            await destination.WriteAsync(memory[..read], ct); // (5)
    }
    finally { ArrayPool<byte>.Shared.Return(rented, clearArray: true); }
}
🧾 Code walkthrough
  1. (1) Span Split yields ranges; slicing creates no substring objects. Parsing can throw on malformed input; use TryParse for expected invalid data.
  2. (2) C# 14 recognizes span conversions during generic inference and overload resolution. It did not introduce Span itself or make all conversions legal.
  3. (3) Keep stack buffers small and bounded; initialize before reading. Avoid large stackalloc buffers or repeated inside long loops.
  4. (4) ArrayPool comes from System.Buffers; rentals may be larger than requested and contain old data.
  5. (5) Memory can cross await. Write only bytes read and return the array after the last consumer. Clearing costs work but prevents passing buffer contents to the next renter.

Span is a ref struct and cannot be a class field or remain live across await. From C# 13 it can be used in async method regions that do not cross suspension. Memory does not automatically own its lifetime, and a consumer must not retain a pooled buffer after Return. C# 14 span conversions.

#Measure first: BenchmarkDotNet and profiling

A slow endpoint may be waiting for rather than needing . Start with to locate the stage, profile the hot path, then microbenchmark the relevant implementations.

// Console project; add the BenchmarkDotNet package.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<CsvBenchmarks>();                      // (1)

[MemoryDiagnoser]
public class CsvBenchmarks
{
    private const string Csv = "12,7,30,44,5,9,100";

    [Benchmark(Baseline = true)]
    public int Split() => Csv.Split(',').Sum(int.Parse);    // (2)

    [Benchmark]
    public int Span()
    {
        int total = 0;
        ReadOnlySpan<char> input = Csv;
        foreach (Range range in input.Split(','))
            total += int.Parse(input[range]);              // (3)
        return total;
    }
}
dotnet run -c Release
🧾 Code walkthrough
  1. (1) The runner performs measurements with warm-up; run without a debugger in the target environment.
  2. (2) The baseline creates split strings and returns a used result.
  3. (3) The alternative avoids temporary strings; MemoryDiagnoser reports . Verify equal results first.

Test varied sizes and malformed input, and examine mean, variability and allocated bytes. A microbenchmark cannot establish p99 improvement under load. Use dotnet-counters for and thread-pool behavior, dotnet-trace for analysis and load testing for application impact. BenchmarkDotNet documentation.

#Source generators and ValueTask: remove measured costs

Runtime is useful, but repeated metadata discovery can sometimes move to the build. Source generators add C# during compilation. ValueTask addresses a different cost: Task in operations that frequently finish synchronously.

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

public record ProductDto(int Id, string Name);

[JsonSerializable(typeof(ProductDto))]                    // (1)
public partial class ProductJson : JsonSerializerContext { }

public static partial class ProductPatterns
{
    [GeneratedRegex("^[A-Z]{3}-[0-9]{4}$")]
    public static partial Regex Sku();                   // (2)
}

// In a caller method:
// string json = JsonSerializer.Serialize(
//     new ProductDto(1, "Mug"), ProductJson.Default.ProductDto);

public sealed class StockReader
{
    public ValueTask<int> ReadAsync(int? cached, CancellationToken ct) =>
        cached is int count                              // (3)
            ? ValueTask.FromResult(count)
            : new ValueTask<int>(LoadAsync(ct));

    private static async Task<int> LoadAsync(CancellationToken ct)
    {
        await Task.Delay(10, ct); // stand-in for an asynchronous data source
        return 0;
    }
}
🧾 Code walkthrough
  1. (1) The context generates metadata for the declared type, not every type in the application.
  2. (2) GeneratedRegex supplies an implementation during build rather than ordinary runtime discovery.
  3. (3) A cache hit returns a value without creating a Task. Misses may still allocate; this illustrates the cost rather than a complete thread-safe cache.

Default to Task for async . Consume a ValueTask once; if repeated awaits or composition are needed, convert it to a Task once and retain that Task. A generator does not make every dependency -safe. Other examples include LoggerMessage and Mapperly; inspect generated code and diagnostics.

#Native AOT deployment trade-offs

For a or short-lived service that repeatedly pays startup cost, Native moves compilation to publishing. It does not automatically fix a database bottleneck.

dotnet publish -c Release -r linux-x64 -p:PublishAot=true

The command targets an and architecture and needs a suitable native toolchain. The executable has no but still links and runtime services. Test the published artifact and resolve trimming and AOT warnings rather than suppressing them indiscriminately.

Potential benefitCost
Lower cold-start time and memoryLonger builds; actual measurement is required
No installed shared-runtime dependencyRepublish to update linked runtime services
Code and metadata known earlierNo .Emit; reflection can work with preserved metadata
Supported Minimal path controllers are not a supported Native AOT choice; audit libraries and providers

reduces startup work while retaining JIT; JIT can exploit dynamic at runtime. See topic 03: runtime. AOT is not guaranteed smaller or faster for every workload.

#File-based apps and Aspire: different jobs

A small invoice conversion tool may not need a solution. An with Redis and a database needs coordinated dependencies locally. File-based apps simplify the former; Aspire coordinates the latter.

// app.cs; .NET 10 SDK. Package restore requires network access or a cache.
#:package Humanizer@2.14.1                                  // (1)
using Humanizer;
Console.WriteLine(TimeSpan.FromMinutes(90).Humanize());
dotnet run app.cs
# Use --file explicitly if the directory also contains a project.
dotnet run --file app.cs
// AppHost project with Aspire.Hosting.Redis and a reference to Store.Api.
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");                      // (2)
builder.AddProject<Projects.Store_Api>("api")
    .WithReference(cache)                                  // (3)
    .WaitFor(cache);
builder.Build().Run();
🧾 Code walkthrough
  1. (1) #:package declares a versioned NuGet dependency without a handwritten csproj. This file is separate from the AppHost example.
  2. (2) The Aspire AppHost declares a Redis resource; container resources need a container runtime.
  3. (3) WithReference passes connection information and WaitFor waits for readiness. Projects.Store_Api is generated from the project reference; the service still needs an appropriate Redis client registration.

When added to services, ServiceDefaults provides telemetry, and resilience defaults; the dashboard collects logs, and metrics. Aspire, formerly Aspire, supports local orchestration; production publishing needs a chosen target and separate configuration. Convert a growing tool into an ordinary project when modules and tests warrant it. File-based apps and Aspire overview.

Interview questions

Check your reasoning

Why does this choice fit? When would it fail? What changes under higher load or a dependency failure?

6.1What do records provide, and are they deeply immutable?Junior
Model answer

I use records when value-based equality fits the data, such as a price or response . They generate equality, useful formatting and support for with expressions. A positional record class has init-only properties, but records are not automatically deeply . A List inside a record can still change, and with makes a shallow copy that can share that List. I choose immutable contained values when the contract requires a stable value object.

6.2Do required and init replace runtime input validation?Junior
Model answer

No. required makes ordinary source callers initialize a member, while init restricts when a property can be assigned. Neither establishes business validity or deep . A required string can still be empty, and an init-only List can still be modified. I use these features to improve construction contracts, then validate external requests at runtime and enforce domain where state changes occur. Compiler assistance and runtime validation solve different parts of the problem.

6.3What does a primary constructor generate for a normal class?Junior
Model answer

A primary constructor lets me declare constructor parameters on the class declaration and use them within the type. For a normal class, those parameters do not automatically become public properties, unlike positional record parameters. They are not readonly either. I use the syntax for concise dependency injection when it improves clarity. If I need an explicit readonly field or constructor validation with a clearer lifetime contract, a conventional constructor remains a good choice.

6.4What is pattern matching useful for?Junior
Model answer

I use pattern matching to express decisions based on a value's type, shape and properties without repeated casts or deeply nested conditionals. Property and relational patterns work well for shipping rules, logical patterns combine alternatives, and list patterns describe sequence shapes. Switch expressions evaluate arms in order. I keep rules readable and handle null explicitly, but I still test business boundaries because a syntactically exhaustive switch can contain incorrect or overly broad rules.

6.5What do collection expressions and spread do?Junior
Model answer

Collection expressions give a compact syntax for constructing supported collection types, such as an int array or a List. A spread element includes the elements of another sequence. The expression needs an appropriate target type, so I do not assume an untyped var declaration will infer a List. It creates a collection structure, not a deep clone of referenced objects, and enumerating a spread source can have a cost if that source is deferred.

6.6What are the important C# 14 changes?Mid
Model answer

I group them by purpose. Extension blocks add properties and static extension members. The field keyword reduces property boilerplate, null-conditional assignment simplifies optional updates, and nameof accepts unbound generics. conversions integrate better with inference and overload resolution. can use parameter modifiers without explicit types, while partial constructors and events help generated code. User-defined compound assignment can mutate a type in place. I adopt each feature when its semantics make the code clearer, rather than converting everything.

6.7How do extension properties differ from adding real instance state?Mid
Model answer

An extension property is declared outside the extended type and resolved at compile time when its namespace is in scope. It does not add storage, change the type layout or participate in virtual dispatch. I use it for small derived values that naturally read as properties, such as checking whether a SKU has text. Real instance members take precedence, and I avoid hiding expensive behind property syntax because callers expect simple access.

6.8What does order?.Status = CreateStatus() do if order is null?Mid
Model answer

It skips both the assignment and evaluation of CreateStatus. That short-circuit behavior is useful when absence is an accepted case. I would use an explicit guard instead if a missing order violates the operation's contract, because silently skipping work could hide a bug. The syntax provides no thread synchronization or semantics. I also distinguish it from increment and decrement, which are not supported through null-conditional assignment in this form.

6.9Why would you use TimeProvider rather than DateTime.Now?Mid
Model answer

I inject TimeProvider when business behavior depends on time, such as coupon expiry. Production uses the system provider, while tests can control the clock without sleeping or changing the machine time. GetUtcNow gives an explicit instant suitable for comparisons; I convert to the user's time zone for display. This improves determinism rather than making clock reads inherently faster. I also separate elapsed-time measurement from wall-clock time when timing an operation.

6.10What changed with OpenAPI generation, and where does Scalar fit?Mid
Model answer

provides built-in OpenAPI document generation from 9 through Microsoft.AspNetCore.OpenApi, and ASP.NET Core 10 defaults to OpenAPI 3.1. That document is separate from an interactive . Scalar is one UI integration I can add as a separate package. Swashbuckle remains a valid choice even though it left the default templates. I check transformers, compatibility and access policy before migrating an existing documentation setup.

6.11How would you approach optimizing a slow endpoint?Mid
Model answer

I define a measurable target and reproduce the issue under representative load. show whether time goes into , external calls, queueing or application . I inspect the relevant query plans or profile the hot code, then make one justified change. BenchmarkDotNet helps compare small implementations, but I verify the application result with , tail and measurements. I would not rewrite code with or switch to before identifying the bottleneck.

6.12How should licensing changes affect library selection?Mid
Model answer

I review the terms of the exact version and the organization's eligibility, rather than assuming every version has identical conditions. MediatR and AutoMapper changed their commercial licensing model in 2025, and FluentAssertions v8 is commercial. Depending on requirements, explicit handlers, manual mapping or Mapperly may be alternatives, while AwesomeAssertions and Shouldly are assertion options. I include migration and maintenance costs in the decision and avoid presenting a licensing change as a technical defect.

6.13How do Span and Memory differ, especially with async code?Senior
Model answer

is a ref-struct view over contiguous memory, useful for -conscious synchronous processing. It cannot be a class field or remain live across an await. Modern C# allows using it in async method regions that do not cross suspension. Memory is the storable counterpart that can cross awaits, from which I obtain a span while processing. Neither abstraction automatically owns the underlying buffer, so I still enforce lifetime, especially when arrays come from a pool.

6.14What can go wrong with ArrayPool or stackalloc?Senior
Model answer

A pooled array may be larger than requested and contain previous data, so I track the valid length and never expose unused bytes. I return it exactly once in finally after asynchronous consumers finish, and consider clearing sensitive contents. With stackalloc I keep sizes small and bounded, initialize before reading and avoid repeated inside long loops. Both tools trade simpler allocation for stronger lifetime responsibilities; I use them only after measuring a worthwhile benefit.

6.15When is ValueTask worth its extra constraints?Senior
Model answer

I consider ValueTask for a measured hot path that completes synchronously often enough for Task to matter. It can hold a direct result, a Task or a reusable source, which is why consumers must follow single-consumption rules rather than assuming repeated awaits are safe. It also makes composition less convenient and has its own struct-copy costs. I default to Task; when repeated awaits are needed, I convert once to a Task and reuse that.

6.16Do source generators eliminate all reflection and AOT problems?Senior
Model answer

No. A source generator moves specific work into compilation, such as metadata generation, logging helpers or regular-expression implementation. It does not guarantee that every dependency avoids runtime code generation or preserves all metadata needed after trimming. I inspect generated output, build diagnostics and the published artifact's behavior. For JSON I ensure the context covers the actual serialized types and converters, then test the binary rather than treating a successful ordinary build as sufficient.

6.17What does Native AOT trade for startup improvements?Senior
Model answer

Native moves compilation to publishing and removes the runtime , which can improve cold starts and memory usage. It requires platform-specific builds and a native toolchain, limits runtime code generation and needs deliberate metadata preservation. and other runtime services still exist in the executable. I audit dependencies and measure the target workload, because a long-running JIT service can benefit from dynamic and may not be limited by startup at all.

6.18What do file-based apps and Aspire each add to a workflow?Senior
Model answer

A file-based app lets me run a small C# tool directly with the 10 and declare packages through directives, without maintaining a project file initially. Aspire addresses a different scale: its AppHost describes services and dependencies, wires connection information and coordinates local startup. ServiceDefaults and the dashboard support observability and health conventions. I still configure clients and decide how production is deployed; neither feature removes the need for application design or testing.

Test yourself

Quiz answers and completed-topic marks are saved in this browser; they do not sync across devices.

Score: 0 / 0

Which statement about required is correct?

Initialization is not validity; runtime validation remains necessary.

What happens to the right-hand expression in a null-conditional assignment on null?

The assignment itself is skipped.

What does nameof(List<>) produce in C# 14?

nameof returns a short name at compile time.

What is the signature shape of a C# 14 user-defined += operator?

It mutates the existing instance rather than returning a replacement.

Which feature makes array-to-span generic inference more natural in C# 14?

already existed; the change integrates it with language rules.

What must a partial event's implementing declaration contain?

Defining and implementing declarations have different roles.

When should a rented array be returned?

Early return lets another renter modify the same buffer.

What does AddOpenApi provide without an extra UI package?

Document generation and an interactive are separate concerns.

What is the first useful step for an endpoint with high p99 latency?

Optimization should follow evidence.

Which is a core Aspire AppHost responsibility?

AppHost coordinates resources; the production target is a separate decision.