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 .
| Release | Language | Platform |
|---|---|---|
| C# 10 / .NET 6 | Global usings, file-scoped namespaces, record structs | Minimal APIs, DateOnly/TimeOnly, Parallel.ForEachAsync, WaitAsync |
| C# 11 / .NET 7 | Required members, raw strings, list patterns, generic math support | Rate limiting and output caching |
| C# 12 / .NET 8 | Primary constructors and collection expressions | Keyed , TimeProvider, frozen collections, IExceptionHandler, dynamic by default, Minimal API Native |
| C# 13 / .NET 9 | Params collections, Lock support, \e escape; field was preview | HybridCache, OpenAPI generation, Guid.CreateVersion7, Task.WhenEach, CountBy/AggregateBy/Index, MapStaticAssets |
| C# 14 / .NET 10 | Extension members, field, null-conditional assignment, unbound-generic nameof, conversions, modifiers, partial constructors/events, compound assignment operators | File-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)
- (1) A positional record class generates value equality and init properties.
- (2) required forces source callers to initialize a member; init restricts assignment to initialization. The value can still be invalid.
- (3) A primary constructor parameter is available inside the class but is neither an automatic property nor readonly.
- (4) with makes a shallow copy; mutable referenced objects may remain shared.
- (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)
- (1) Null has an explicit policy; property patterns do not match null.
- (2) Property, relational and logical patterns combine weight bounds with and and country alternatives with or.
- (3) The discard covers remaining inputs. Exhaustiveness warnings help but do not prove business-rule completeness.
- (4) A positional pattern uses the record's Deconstruct together with a type test.
- (5) The list pattern requires at least two elements; .. accepts any middle length.
- (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)
- (1) The named receiver supplies the instance-like property and method.
- (2) A receiver type without a name supplies a static extension property.
- (3) Property-looking syntax is resolved at compile time and needs the extension namespace in scope.
- (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";
- (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) A null order skips both assignment and CreateStatus. This syntax does not permit ++ or -- and provides no locking.
- (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;
- (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) A partial constructor has matching defining and implementing declarations, often split across handwritten and generated files.
- (3) The partial event implementation must supply add and remove accessors.
- (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.
| Traditional | Modern choice | Reason and limit |
|---|---|---|
| Startup.cs | Minimal hosting in Program.cs | From 6; the same and principles with less ceremony |
| Swashbuckle document and | Microsoft.AspNetCore.OpenApi plus Scalar UI | Built-in generation from .NET 9; choose a separate UI; Swashbuckle remains valid |
| Newtonsoft.Json | System.Text.Json source generation | Build-time metadata; verify converters and contract differences before migrating |
| IMemoryCache | HybridCache | From .NET 9; local cache, optional distributed backend and protection; IMemoryCache still fits simpler needs |
| Polly v7 policies | Polly v8 resilience pipelines and Microsoft.Extensions.Http.Resilience | Composable strategies; review timeouts, retries and |
| lock(object) | lock(System.Threading.Lock) | From C# 13 / .NET 9; synchronous only, and object locks remain valid |
| DateTime.Now | TimeProvider.GetUtcNow() | Testable time from .NET 8; convert time zones explicitly for display |
| ArrayList | List<T> | Type safety without value elements |
| WebClient | HttpClient and IHttpClientFactory | Async and handler pooling; a long-lived client with PooledConnectionLifetime is also valid |
| MediatR / AutoMapper | Current versions under their terms, or explicit handlers, manual mapping and Mapperly | Commercial licensing changes in 2025; review version-specific terms and eligibility |
| FluentAssertions v8 | AwesomeAssertions or Shouldly, according to terms and needs | v8 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();
- (1) Registers document generation; 10 defaults to OpenAPI 3.1.
- (2) Exposes the document, not a UI.
- (3) The Scalar package supplies a separate UI, enabled here only in Development.
#Span, Memory, stackalloc and ArrayPool
- A byte array holds a message, and you only need its first four bytes. Allocating another array and copying them may be unnecessary.
- 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.
- 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); }
}
- (1) Span Split yields ranges; slicing creates no substring objects. Parsing can throw on malformed input; use TryParse for expected invalid data.
- (2) C# 14 recognizes span conversions during generic inference and overload resolution. It did not introduce Span itself or make all conversions legal.
- (3) Keep stack buffers small and bounded; initialize before reading. Avoid large stackalloc buffers or repeated inside long loops.
- (4) ArrayPool comes from System.Buffers; rentals may be larger than requested and contain old data.
- (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
- (1) The runner performs measurements with warm-up; run without a debugger in the target environment.
- (2) The baseline creates split strings and returns a used result.
- (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;
}
}
- (1) The context generates metadata for the declared type, not every type in the application.
- (2) GeneratedRegex supplies an implementation during build rather than ordinary runtime discovery.
- (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 benefit | Cost |
|---|---|
| Lower cold-start time and memory | Longer builds; actual measurement is required |
| No installed shared-runtime dependency | Republish to update linked runtime services |
| Code and metadata known earlier | No .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();
- (1) #:package declares a versioned NuGet dependency without a handwritten csproj. This file is separate from the AppHost example.
- (2) The Aspire AppHost declares a Redis resource; container resources need a container runtime.
- (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
No results
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.
Which statement about required is correct?
What happens to the right-hand expression in a null-conditional assignment on null?
What does nameof(List<>) produce in C# 14?
What is the signature shape of a C# 14 user-defined += operator?
Which feature makes array-to-span generic inference more natural in C# 14?
What must a partial event's implementing declaration contain?
When should a rented array be returned?
What does AddOpenApi provide without an extra UI package?
What is the first useful step for an endpoint with high p99 latency?
Which is a core Aspire AppHost responsibility?