Nullable Reference Types
Purpose: make nullability visible in the type system. Example: string? can be null; string should not be. Gotcha: it is static analysis, not a runtime guarantee.
A field reference for modern .NET development: SDK workflow, target frameworks, C# 14 language features, ASP.NET Core, EF Core, MAUI, Aspire, packaging, diagnostics, deployment, and architecture decisions.
Use this when creating, debugging, testing, packaging, and shipping a normal .NET application.
| Need | Command or file | Example | Gotcha |
|---|---|---|---|
| Create app | dotnet new | dotnet new webapi -n Billing.Api | Templates change over SDK releases; pin SDK with global.json for teams. |
| Restore packages | dotnet restore | dotnet restore Billing.slnx --locked-mode | Use locked mode in CI once lock files are committed. |
| Build | dotnet build | dotnet build -c Release -warnaserror | dotnet run builds implicitly unless --no-build is set. |
| Test | dotnet test | dotnet test --collect:"XPlat Code Coverage" | Unit tests and integration tests need different fixture lifetimes. |
| Publish | dotnet publish | dotnet publish -c Release -r linux-x64 --self-contained false | Runtime-dependent is smaller; self-contained carries the runtime. |
| Inspect SDKs | dotnet --list-sdks | dotnet --info | Installed SDK and target framework are different decisions. |
| Manage packages | dotnet package | dotnet add package Microsoft.EntityFrameworkCore.SqlServer | Central package management prevents version drift across projects. |
| Audit packages | dotnet list package | dotnet list package --outdated --include-transitive | Transitive upgrades can change runtime behavior even when direct references stay fixed. |
| EF migration | dotnet ef | dotnet ef migrations add AddInvoices | Install matching EF tools and keep migrations reviewed as code. |
| Format | dotnet format | dotnet format --verify-no-changes | Run in CI only after agreeing on analyzer severities. |
| Secrets | dotnet user-secrets | dotnet user-secrets set ConnectionStrings:Billing "...dev..." | User secrets are local development storage, not production secret management. |
| Workloads | dotnet workload | dotnet workload install maui | Workloads are SDK-scoped; CI images must install the same workloads developers use. |
dotnet new sln -n Commerce
dotnet new webapi -n Commerce.Api
dotnet new classlib -n Commerce.Domain
dotnet new xunit -n Commerce.Tests
dotnet sln Commerce.sln add Commerce.Api Commerce.Domain Commerce.Tests
dotnet add Commerce.Api reference Commerce.Domain
dotnet add Commerce.Tests reference Commerce.Domain
dotnet build -warnaserror
dotnet test
Facts below were checked against Microsoft Learn on 2026-08-02. Treat release and support dates as volatile.
The .NET platform is a build system, runtime, BCL, package ecosystem, and deployment model. Confusing these layers causes most environment bugs.
SDK: developer toolchain with CLI, MSBuild, Roslyn compilers, templates, and workload support. Runtime: what executes compiled apps. Example: a build agent needs SDK 10.x; a production container may need only the ASP.NET Core runtime.
dotnet --list-sdks and dotnet --list-runtimes to diagnose mismatches.global.json to pin SDK feature band for a repo.The Base Class Library is the common API surface: System, collections, streams, JSON, HTTP, LINQ, threading, diagnostics, globalization, and cryptography. C# compiles to Common Intermediate Language plus metadata inside assemblies; the runtime loads assemblies and JIT-compiles methods as they execute.
.dll or .exe deployment and versioning units with a manifest, referenced assemblies, resources, and type metadata.TargetFramework says what API surface the project compiles against. Example: net10.0 for current cross-platform apps, net10.0-windows when using Windows-specific APIs.
TargetFrameworks multi-targets a library, for example net8.0;net10.0.netX.0 targets for new libraries; use .NET Standard mainly for old compatibility requirements.<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
Nullable, ImplicitUsings, LangVersion, RuntimeIdentifier, and publish properties change build output.PackageReference, ProjectReference, Compile, Content, and None control dependencies and included files.Directory.Build.props, package versions in Directory.Packages.props, and SDK selection in global.json.TreatWarningsAsErrors is useful, but generated code or analyzer packages may need targeted suppressions instead of blanket disabling.Directory.Packages.props to prevent drift.RestorePackagesWithLockFile and CI --locked-mode for deterministic restores.nuget.config explicit for public and private feeds; use package source mapping when multiple feeds can serve the same ID.C# productivity comes from static types, async flow, pattern matching, LINQ, nullable analysis, and low-allocation APIs when needed.
Purpose: make nullability visible in the type system. Example: string? can be null; string should not be. Gotcha: it is static analysis, not a runtime guarantee.
Purpose: express non-blocking I/O while keeping straight-line control flow. Example: await http.GetFromJsonAsync<Invoice[]>("/invoices", ct). Gotcha: do not block with .Result or .Wait() in request paths.
Task represents an operation; Task<T> represents an operation with a result; ValueTask<T> is for measured allocation-sensitive APIs.await; use normal try/catch around awaited calls.Purpose: query objects, XML, and providers with composable operators. Example: orders.Where(o => o.Total > 100).Select(o => o.Id). Gotcha: LINQ-to-Objects and EF SQL translation have different costs.
Where, Select, GroupBy, OrderBy, Any, and FirstOrDefault.ToList() or ToArray() at a deliberate boundary.Purpose: branch on type, shape, values, and collection structure without fragile casts. Example: order is { Total: > 500, Status: OrderStatus.Pending }. Gotcha: clever nested patterns can become less readable than explicit guards.
message is PaymentReceived received checks and casts in one expression.customer is { Address.Country: "US" } expresses object-shape checks.score is >= 90 and <= 100 keeps range rules close to the branch.tokens is ["deploy", var service, ..] is useful for command parsers.Purpose: concise value-oriented models with generated equality, deconstruction, formatting, and non-destructive mutation. Example: public sealed record Money(decimal Amount, string Currency);. Gotcha: record equality is value-based; do not use mutable navigation-heavy EF entities as records by default.
with expressions create modified copies, for example invoice with { Status = Paid }.Purpose: slice contiguous memory without allocations. Example: parse a protocol header from ReadOnlySpan<byte>. Gotcha: Span<T> is stack-only and cannot be stored on the heap.
As of July 2026: C# 14 includes extension members, null-conditional assignment, unbound generic nameof, more span conversions, lambda parameter modifiers, field-backed properties, partial events/constructors, and user-defined compound assignments. Gotcha: language version still depends on SDK/tooling support.
Every C# keyword grouped by job, with a one-line purpose each. C# has 77 reserved keywords (identifiers you can never reuse) plus a longer set of contextual keywords that only carry special meaning in a specific place and stay usable as ordinary identifiers elsewhere. Contextual keywords are marked ᶜ. Verified against the Microsoft Learn C# keywords reference (page dated 2026-06-05) for .NET 10 / C# 14.
| Keyword | Purpose |
|---|---|
bool | Boolean value, true or false (alias for System.Boolean). |
byte / sbyte | 8-bit unsigned (0–255) / signed (−128–127) integer. |
short / ushort | 16-bit signed / unsigned integer. |
int / uint | 32-bit signed / unsigned integer; int is the default integral literal type. |
long / ulong | 64-bit signed / unsigned integer. |
float / double | 32-bit / 64-bit IEEE-754 floating point; double is the default real literal. |
decimal | 128-bit base-10 decimal; use for money to avoid binary rounding error. |
char | Single 16-bit UTF-16 code unit. |
string | Immutable sequence of characters (alias for System.String). |
object | Root of every type (alias for System.Object). |
void | Method returns no value. |
nint / nuintᶜ | Native-sized signed / unsigned integer (32- or 64-bit per platform). |
varᶜ | Implicitly-typed local; the compiler infers the static type from the initializer. |
dynamicᶜ | Bypasses static type checking; resolved at runtime. Avoid unless interop demands it. |
| Keyword | Purpose |
|---|---|
class | Reference type with identity; heap-allocated, reference semantics. |
struct | Value type; copied by value, ideal for small immutable data. |
recordᶜ | Type with compiler-generated value equality and with copying; record struct for value semantics. |
interface | Contract of members a type promises to implement. |
enum | Named set of integral constants. |
delegate | Type-safe reference to a method; basis of events and callbacks. |
namespace | Groups types into a named scope. |
using | As a directive: imports a namespace or defines an alias. |
event | Member exposing a delegate for publish/subscribe, restricting callers to +=/-=. |
operator | Declares an operator overload or conversion. |
this | Current instance; also declares an indexer or (C# 14) an extension receiver. |
base | Accesses the base class's members or constructor. |
extensionᶜ | C# 14 extension block declaring extension members (properties, operators, statics), not just methods. |
| Keyword | Purpose |
|---|---|
public | Visible everywhere. |
private | Visible only inside the declaring type (default for class members). |
protected | Visible to the type and its derived types. |
internal | Visible within the same assembly (default for top-level types). |
static | Member belongs to the type, not an instance. |
abstract | Incomplete member/type that must be overridden/derived. |
sealed | Prevents further inheritance or overriding. |
virtual / override | Marks a member overridable / provides the derived implementation. |
new | As a modifier: hides an inherited member. As an operator: allocates an instance. |
const | Compile-time constant, baked into callers. |
readonly | Field assignable only in declaration or constructor. |
requiredᶜ | Caller must set this member during object initialization (C# 11+). |
volatile | Field read/written without compiler/CPU reordering optimizations. |
extern | Method implemented externally, usually via P/Invoke. |
unsafe | Enables pointer types and pointer arithmetic in a scope. |
fixed | Pins a variable so the GC cannot move it; declares fixed-size buffers. |
partialᶜ | Splits a type or member across files; C# 14 adds partial constructors/events. |
fileᶜ | Type visible only within the source file (C# 11+); used by source generators. |
| Keyword | Purpose |
|---|---|
if / else | Conditional branch. |
switch | Multi-way branch; also the switch expression form for pattern matching. |
case / default | A switch arm / the fallthrough arm (also the default-value expression). |
for | Counter-based loop. |
foreach / in | Iterates any IEnumerable; in separates variable and source. |
while / do | Top-tested / bottom-tested loop. |
break / continue | Exit the loop / skip to the next iteration. |
goto | Jumps to a label or switch case; rarely justified outside state machines. |
return | Exits a method, optionally with a value. |
whenᶜ | Guard clause on a catch or a pattern-matching arm. |
| Keyword | Purpose |
|---|---|
try / catch / finally | Guard a block / handle an exception / run cleanup unconditionally. |
throw | Raises an exception; bare throw; in a catch preserves the stack trace. |
checked / unchecked | Enable / suppress overflow checking for integral arithmetic. |
lock | Acquires a mutual-exclusion lock around a block (uses System.Threading.Lock in .NET 9+). |
using | As a statement/declaration: disposes an IDisposable at scope end. |
| Keyword | Purpose |
|---|---|
ref | Pass/return by reference; also ref struct and ref locals. |
out | Pass by reference for output; the callee must assign it before returning. |
in | Pass a readonly reference (avoids copying large structs). |
params | Variable-length argument list; C# 13 allows params spans and collections. |
scopedᶜ | Restricts a ref's lifetime to the current scope (ref-safety, C# 11+). |
stackalloc | Allocates a block on the stack, typically as a Span<T>. |
sizeof | Size in bytes of an unmanaged type. |
notnullᶜ | Generic constraint: the type argument must be a non-nullable type. |
unmanagedᶜ | Generic constraint: type argument is an unmanaged (blittable) type. |
managed / unmanagedᶜ | Calling convention on a function pointer (delegate*). |
allowsᶜ | C# 13 anti-constraint, e.g. allows ref struct, permitting ref-struct type arguments. |
| Keyword | Purpose |
|---|---|
is | Type/pattern test; obj is Customer c tests and casts in one step. |
as | Reference conversion returning null on failure (no exception). |
typeof | Gets the System.Type for a type at compile time. |
nameofᶜ | Compile-time string of a symbol's name; refactor-safe for logs and ArgumentNullException. |
explicit / implicit | Declares a cast that requires / does not require a cast operator. |
true / false | Boolean literals (and overloadable operators). |
null | Absence of a reference or of a nullable value. |
| Keyword | Purpose |
|---|---|
getᶜ / setᶜ | Property/indexer read / write accessor. |
initᶜ | Accessor settable only during object initialization (C# 9+). |
valueᶜ | Implicit parameter holding the assigned value inside a set/init/add/remove. |
fieldᶜ | C# 14: the compiler-generated backing field inside an accessor, so you skip declaring one. |
addᶜ / removeᶜ | Custom event subscription / unsubscription accessors. |
asyncᶜ / awaitᶜ | Marks an async method / suspends until a Task completes without blocking the thread. |
yieldᶜ | yield return/yield break produce an iterator lazily. |
withᶜ | Non-destructive copy of a record/struct with some members changed. |
| Keyword | Purpose |
|---|---|
fromᶜ | Starts a query and introduces the range variable. |
whereᶜ | Query filter clause; also (reserved sense) a generic type constraint. |
selectᶜ | Projects each element into the result shape. |
groupᶜ / byᶜ / intoᶜ | Groups results by a key and continues the query. |
orderbyᶜ / ascendingᶜ / descendingᶜ | Sorts the sequence and sets direction. |
joinᶜ / onᶜ / equalsᶜ | Correlates two sequences on matching keys. |
letᶜ | Introduces a computed range variable mid-query. |
andᶜ / orᶜ / notᶜ | Logical pattern combinators, e.g. is >= 1 and <= 9. |
globalᶜ / aliasᶜ | global:: namespace qualifier; global using and extern aliases. |
argsᶜ | The implicit command-line arguments parameter in a top-level program. |
Pick the runtime model around deployment, UI needs, latency, scaling, and team skill. Avoid using one framework for every problem.
| Model | Use when | Example | When not to use |
|---|---|---|---|
| ASP.NET Core minimal API | Small HTTP APIs, microservices, internal tools | app.MapGet("/health", () => Results.Ok()) | Complex MVC view workflows with lots of server-rendered UI conventions |
| ASP.NET Core MVC/Razor Pages | Server-rendered business apps and admin workflows | Razor Page for invoice approval | Pure JSON backend with no server-side views |
| Blazor | C# web UI with component model | Dashboard sharing validation models with backend | Teams already standardized on React/Vue and needing broad JS ecosystem packages |
| .NET MAUI | Cross-platform mobile/desktop from one C# codebase | Line-of-business tablet app | Pixel-perfect native consumer UI per platform |
| WPF / WinForms | Windows desktop, existing enterprise code, rich desktop controls | Manufacturing workstation app | Mac/Linux first-party desktop target |
| .NET Aspire | Local orchestration, service discovery, telemetry defaults for distributed apps | API + worker + Redis + Postgres app host | Simple single-process app with no distributed dependencies |
| Worker service | Background jobs, queue consumers, schedulers | Hosted service processing payments queue | Request/response HTTP API only |
| NativeAOT | Fast startup, small deployment, restricted runtime feature set acceptable | CLI tool or serverless function | Heavy reflection, runtime code generation, dynamic plugin loading |
Most production .NET work sits here: HTTP boundaries, persistence, validation, identity, telemetry, and operational resilience.
The pipeline is ordered middleware plus endpoints. Example order: exception handling, HTTPS redirection, static files, routing, authentication, authorization, endpoint mapping. Gotcha: authorization before authentication does not work as intended.
ILogger<T> with structured templates, log levels, scopes, and correlation IDs rather than string-concatenated messages.var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();
builder.Services.AddOpenApi();
builder.Services.AddHealthChecks();
builder.Services.AddHttpClient("catalog", c => c.BaseAddress = new Uri("https://catalog.internal/"));
var app = builder.Build();
app.UseExceptionHandler();
app.MapOpenApi();
app.MapHealthChecks("/healthz");
app.MapGet("/orders/{id:int}", async (int id, OrdersDb db, CancellationToken ct) =>
await db.Orders.FindAsync([id], ct) is { } order
? Results.Ok(order)
: Results.NotFound());
app.Run();
dotnet ef migrations add AddInvoiceStatus.AsNoTracking() for read-only queries.Select, avoid N+1 queries, use pagination, prefer compiled queries only after measuring, and inspect generated SQL for hot paths.SaveChanges in a transaction for supported providers; coordinate explicit transactions only when multiple operations must commit together.WebApplicationFactory for ASP.NET Core endpoints and test containers or dedicated databases for realistic persistence behavior.Modern .NET gives good tools, but you still need explicit telemetry, measured optimization, and repeatable deployment.
Use structured logs, metrics, traces, health checks, and correlation IDs. Example: OpenTelemetry exporting traces to your collector. Gotcha: logs without request IDs are painful during incidents.
Measure before optimizing. Example: use dotnet-counters for allocation rate and GC pressure, then profile hot paths. Gotcha: premature Span<T> rewrites can obscure code without improving throughput.
Use official runtime images, non-root users where possible, and small publish output. Example: build in SDK image, run in ASP.NET runtime image. Gotcha: self-contained containers are larger and patch differently.
Purpose: choose output based on host control, startup, size, and runtime patching. Example: dotnet publish -c Release -r linux-x64 -p:PublishTrimmed=true. Gotcha: trimming and AOT can break reflection-heavy libraries unless tested from published output.
linux-x64 or win-x64.Timeouts, retries, circuit breakers, bulkheads, and idempotency protect distributed systems. Example: retry transient SQL or HTTP 503 with jitter. Gotcha: retrying non-idempotent writes can duplicate side effects.
Useful CLI tools include dotnet-trace, dotnet-counters, dotnet-dump, and test loggers. Gotcha: collect diagnostics from a matching environment before guessing.
dotnet-counters monitor --process-id 1234 System.Runtime watches GC, exceptions, thread pool, and allocation pressure.dotnet-trace collect --process-id 1234 captures runtime events for later analysis.dotnet-dump collect --process-id 1234 captures memory state for leaks, hangs, and crash investigation.dotnet test --logger trx --blame-hang helps diagnose test failures that only appear in CI.Purpose: make restore, build, test, package, and deploy repeatable outside a developer IDE. Example: CI runs dotnet restore --locked-mode, dotnet build --no-restore -warnaserror, then dotnet test --no-build. Gotcha: an IDE-only workflow hides missing SDKs, workloads, and environment settings.
bin/Debug output.Keep domain logic independent from transport, persistence, and UI. Example: controllers call application services; EF implementations live behind repository/query abstractions where useful. Gotcha: over-abstracting CRUD can add noise.
These cause real production bugs in otherwise ordinary .NET systems.
Calling .Result or .Wait() on async operations can starve thread pools or deadlock older synchronization contexts. Await all the way through.
A singleton context or context captured by background work causes stale tracking, threading issues, and connection pressure. Use scoped lifetimes and factories where needed.
Persistence models leak internal shape, navigation cycles, and future schema changes. Use DTOs for external contracts.
HTTP disconnects, job shutdown, and timeouts should flow through tokens. Pass CancellationToken into EF, HTTP, and long-running work.
Developers, CI, containers, and production can silently use different SDK/runtime versions. Pin SDKs and log runtime info at startup.
AOT trims unused code and restricts dynamic patterns. Test publish output early when targeting AOT or trimming.
Repeated clients can exhaust sockets and lose handler pooling. Use IHttpClientFactory, typed clients, or a deliberately managed long-lived client.
The in-memory provider does not enforce relational constraints or SQL translation. Use SQLite or the real provider for query and migration behavior.
User secrets are for local development only, and production secrets belong in managed stores. Scrub tokens, passwords, and PII from logs.
Debug builds can pass while trimmed, single-file, self-contained, or NativeAOT output fails. Smoke-test the exact artifact you deploy.
Use official Microsoft docs first for volatile platform facts and API behavior.
No matching sections. Clear filters or search a broader term.