/csharp-nullable-reference-types
Guidelines for introducing and using nullable reference types (NRT) and System.Diagnostics.CodeAnalysis nullable attributes in C# / .NET codebases. Covers the nullability model, flow analysis, the null-forgiving operator, API design rules, the full attribute catalog (AllowNull,
$ npx -y skills add aaronontheweb/dotnet-skills --skill csharp-nullable-reference-types --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/csharp-nullable-reference-types
Context preview
The summary Claude sees to decide when to auto-load this skill.
Guidelines for introducing and using nullable reference types (NRT) and System.Diagnostics.CodeAnalysis nullable attributes in C# / .NET codebases. Covers the nullability model, flow analysis, the null-forgiving operator, API design rules, the full attribute catalog (AllowNull,
SKILL.md
csharp-nullable-reference-types.SKILL.mdname: csharp-nullable-reference-types
description: Guidelines for introducing and using nullable reference types (NRT) and System.Diagnostics.CodeAnalysis nullable attributes in C# / .NET codebases. Covers the nullability model, flow analysis, the null-forgiving operator, API design rules, the full attribute catalog (AllowNull, DisallowNull, MaybeNull, NotNull, NotNullWhen, MaybeNullWhen, NotNullIfNotNull, MemberNotNull, MemberNotNullWhen, DoesNotReturn, DoesNotReturnIf), the C# 14 field keyword, incremental migration of legacy codebases, and a code-generation checklist.
version: 1.0.0
tags:
- csharp
- nullable
- nrt
- code-quality
- api-design
C# Nullable Reference Types
When to Use
- Introducing nullable reference types (NRT) into a codebase that has not yet adopted them
- Writing or refactoring C# code that uses `T?` / nullable annotations
- Annotating APIs with `System.Diagnostics.CodeAnalysis` nullable attributes
- Designing public/internal APIs where nullability contracts matter
- Wrapping unannotated or legacy APIs so downstream callers still benefit from NRT
- Reviewing code for correct null-state analysis, guard helpers, and the `field` keyword
Core Goals
- Prevent `NullReferenceException` at runtime by making null intent explicit in signatures.
- Express contracts the type system cannot represent directly using the official nullable attributes.
- Adopt NRT incrementally in legacy codebases without a big-bang rewrite.
Core Nullability Model
Non-nullable vs nullable
- `string` — non-nullable reference. The compiler assumes instances are never `null`; assigning `null` or a maybe-null value produces a warning.
- `string?` — nullable reference. The variable may be `null`; the compiler requires a null check before dereference.
string name = "Alice";
name = null; // Warning: assigning null to non-nullable.
string? nickname = null;
Console.WriteLine(nickname.Length); // Warning: possible null dereference.
Null-state analysis (flow)
The compiler tracks whether a reference is *definitely non-null* or *maybe null*. Null checks and assignments update this state.
string? message = GetMessageOrNull();
if (message != null)
{
// message is definitely non-null in this block.
Console.WriteLine(message.Length);
}
// Outside the if, message is maybe null again.Introduce explicit null checks (`if (x != null)`, `is not null`, pattern matching) before dereferencing nullable values. Narrow nullability early and keep the non-null state alive. **Null-conditional assignment (C# 14)** lets you write `customer?.Order = CreateOrder();` — the right side is evaluated only when the receiver is non-null.
Null-forgiving operator (`!`)
`x!` tells the compiler "treat `x` as non-null here." It affects analysis only, not runtime behavior.
- Use `!` only when a real invariant guarantees non-null and the compiler cannot see it.
- Do **not** use `!` as a general fix for warnings. Prefer refactoring control flow, adding attributes, or proper member initialization.
_customer = LoadCustomerFromOrm()!; // ORM guarantees this is not null in valid state.
Reorganize code before suppressing warnings
A successful guard clause or pattern match already creates a null-safe region in the current scope. Before adding `!`, make nullable values cross a checked boundary once and keep the remaining code non-nullable:
- narrow early with a guard clause or pattern match;
- copy nullable fields or properties to a local before checking, so repeated reads cannot change underneath the analysis;
- when a method has complex control flow, optionally move the non-null path into a local function or private method with non-nullable parameters;
- keep nullable handling at the boundary instead of spreading `T?`, repeated checks, or `!` through the implementation.
public void Process(Order? order)
{
if (order?.Customer is not { } customer)
{
return;
}
// The pattern match already proved that customer is non-null here.
Console.WriteLine(customer.Name);
}Do not extract a function solely to satisfy nullable analysis. Use an explicit non-nullable function boundary when it also simplifies a large or branching implementation. Use `!` only when a real external invariant cannot be represented through control flow, signatures, or nullable-analysis attributes.
Project Configuration
Enable NRT for new code:
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
For legacy codebases, enable incrementally with file-level directives (`#nullable enable`, `#nullable disable`, `#nullable enable warnings`, `#nullable enable annotations`). Treat `CS86xx` nullable warnings as important; consider `TreatWarningsAsErrors` or treating nullable warnings as errors in new projects.
See [nrt-migration-playbook-reference.md](nrt-migration-playbook-reference.md) for the full incremental-adoption strategy, `#nullable` directive reference, legacy interop, and known static-analysis limitations (arrays, `default(struct)`).
API Design Rules (Signatures)
These rules apply to public and internal APIs and to models.
**Parameters** — if `null` is not allowed, use a non-nullable type and add a runtime guard for public APIs:
public void SendEmail(string recipient)
{
ArgumentNullException.ThrowIfNull(recipient);
// Implementation
}If `null` is allowed and meaningful, use `T?`, document how `null` is interpreted, and implement correct `null` behavior.
**Return types** — `Customer` when the method never returns `null`; `Customer?` when it can legitimately return `null` (callers must check, and the compiler enforces it).
public Customer GetRequiredCustomer(Guid id); // Throws on failure.
public Customer? TryGetCustomer(Guid id); // Returns null on failure.
**Properties and fields** — follow the same rules as parameters and return types. Non-nul
Read more
name: csharp-nullable-reference-types description: Guidelines for introducing and using nullable reference types (NRT) and System.Diagnostics.CodeAnalysis nullable attributes in C# / .NET codebases. Covers the nullability model, flow analysis, the null-forgiving operator, API design rules, the full attribute catalog (AllowNull, DisallowNull, MaybeNull, NotNull, NotNullWhen, MaybeNullWhen, NotNullIfNotNull, MemberNotNull, MemberNotNullWhen, DoesNotReturn, DoesNotReturnIf), the C# 14 field keyword, incremental migration of legacy codebases, and a code-generation checklist. version: 1.0.0 tags: - csharp - nullable - nrt - code-quality - api-design
C# Nullable Reference Types
When to Use
- Introducing nullable reference types (NRT) into a codebase that has not yet adopted them
- Writing or refactoring C# code that uses `T?` / nullable annotations
- Annotating APIs with `System.Diagnostics.CodeAnalysis` nullable attributes
- Designing public/internal APIs where nullability contracts matter
- Wrapping unannotated or legacy APIs so downstream callers still benefit from NRT
- Reviewing code for correct null-state analysis, guard helpers, and the `field` keyword
Core Goals
- Prevent `NullReferenceException` at runtime by making null intent explicit in signatures.
- Express contracts the type system cannot represent directly using the official nullable attributes.
- Adopt NRT incrementally in legacy codebases without a big-bang rewrite.
Core Nullability Model
Non-nullable vs nullable
- `string` — non-nullable reference. The compiler assumes instances are never `null`; assigning `null` or a maybe-null value produces a warning.
- `string?` — nullable reference. The variable may be `null`; the compiler requires a null check before dereference.
string name = "Alice"; name = null; // Warning: assigning null to non-nullable. string? nickname = null; Console.WriteLine(nickname.Length); // Warning: possible null dereference.
Null-state analysis (flow)
The compiler tracks whether a reference is *definitely non-null* or *maybe null*. Null checks and assignments update this state.
string? message = GetMessageOrNull();
if (message != null)
{
// message is definitely non-null in this block.
Console.WriteLine(message.Length);
}
// Outside the if, message is maybe null again.Introduce explicit null checks (`if (x != null)`, `is not null`, pattern matching) before dereferencing nullable values. Narrow nullability early and keep the non-null state alive. **Null-conditional assignment (C# 14)** lets you write `customer?.Order = CreateOrder();` — the right side is evaluated only when the receiver is non-null.
Null-forgiving operator (`!`)
`x!` tells the compiler "treat `x` as non-null here." It affects analysis only, not runtime behavior.
- Use `!` only when a real invariant guarantees non-null and the compiler cannot see it.
- Do **not** use `!` as a general fix for warnings. Prefer refactoring control flow, adding attributes, or proper member initialization.
_customer = LoadCustomerFromOrm()!; // ORM guarantees this is not null in valid state.
Reorganize code before suppressing warnings
A successful guard clause or pattern match already creates a null-safe region in the current scope. Before adding `!`, make nullable values cross a checked boundary once and keep the remaining code non-nullable:
- narrow early with a guard clause or pattern match;
- copy nullable fields or properties to a local before checking, so repeated reads cannot change underneath the analysis;
- when a method has complex control flow, optionally move the non-null path into a local function or private method with non-nullable parameters;
- keep nullable handling at the boundary instead of spreading `T?`, repeated checks, or `!` through the implementation.
public void Process(Order? order)
{
if (order?.Customer is not { } customer)
{
return;
}
// The pattern match already proved that customer is non-null here.
Console.WriteLine(customer.Name);
}Do not extract a function solely to satisfy nullable analysis. Use an explicit non-nullable function boundary when it also simplifies a large or branching implementation. Use `!` only when a real external invariant cannot be represented through control flow, signatures, or nullable-analysis attributes.
Project Configuration
Enable NRT for new code:
<PropertyGroup> <Nullable>enable</Nullable> </PropertyGroup>
For legacy codebases, enable incrementally with file-level directives (`#nullable enable`, `#nullable disable`, `#nullable enable warnings`, `#nullable enable annotations`). Treat `CS86xx` nullable warnings as important; consider `TreatWarningsAsErrors` or treating nullable warnings as errors in new projects.
See [nrt-migration-playbook-reference.md](nrt-migration-playbook-reference.md) for the full incremental-adoption strategy, `#nullable` directive reference, legacy interop, and known static-analysis limitations (arrays, `default(struct)`).
API Design Rules (Signatures)
These rules apply to public and internal APIs and to models.
**Parameters** — if `null` is not allowed, use a non-nullable type and add a runtime guard for public APIs:
public void SendEmail(string recipient)
{
ArgumentNullException.ThrowIfNull(recipient);
// Implementation
}If `null` is allowed and meaningful, use `T?`, document how `null` is interpreted, and implement correct `null` behavior.
**Return types** — `Customer` when the method never returns `null`; `Customer?` when it can legitimately return `null` (callers must check, and the compiler enforces it).
public Customer GetRequiredCustomer(Guid id); // Throws on failure. public Customer? TryGetCustomer(Guid id); // Returns null on failure.
**Properties and fields** — follow the same rules as parameters and return types. Non-nul
A comprehensive AI coding plugin with 30 skills and 5 specialized agents for professional .NET development. Battle-tested patterns from production systems covering C#, Akka.NET, Aspire, EF Core, testing, and performance optimization.
Other skills on dotnet-skills.
- /akka-aspire-configuration
Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.
Open skill - /akka-best-practices
Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.
Open skill - /akka-hosting-actor-patterns
Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.
Open skill - /akka-management
Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.
Open skill - /akka-testing-patterns
Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.
Open skill - /aspire-configuration
Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.
Open skill

