Skip to content

Commit 4a56539

Browse files
BillWagneradegeo
andauthored
Everyday C#: Null safety tutorials (#53542)
* Complete Phase 1. 1. `fundamentals/null-safety/nullable-reference-types.md` — apply `template-concept.md`. Body from [docs/csharp/nullable-references.md](docs/csharp/nullable-references.md), with conceptual prose lifted from the existing tutorial. 4-tier audience tip, `ms.topic: concept-article`, `ai-usage: ai-assisted`. All code via `:::code:::` snippet refs. 2. `fundamentals/null-safety/resolve-warnings.md` — concept article. Pull "Understand contexts and warnings" / "Address warnings" / "Enable type annotations" / "Attributes extend type annotations" sections from [docs/csharp/nullable-migration-strategies.md](docs/csharp/nullable-migration-strategies.md). Frame as **5 resolution techniques** with worked examples (NOT a per-CS86xx catalog). Cross-link to [docs/csharp/language-reference/compiler-messages/nullable-warnings.md](docs/csharp/language-reference/compiler-messages/nullable-warnings.md) for per-warning lookup. 3. `fundamentals/null-safety/migration-strategies.md` — concept article. Pull "Plan your migration" / "Next steps" / intro from [docs/csharp/nullable-migration-strategies.md](docs/csharp/nullable-migration-strategies.md). Cover the four default-context strategies and recommended phased order. * Phase 2: tutorial 4. `fundamentals/tutorials/nullable-reference-types.md` — apply `template-tutorial.md`. Source: [docs/csharp/tutorials/nullable-reference-types.md](docs/csharp/tutorials/nullable-reference-types.md). Restructure to template (checklist, Prerequisites, numbered task H2s, "Get the code", "Next step"). Trim concept-duplicating prose; replace with cross-links. * Phase 3: Snippets . New snippet projects under `docs/csharp/fundamentals/null-safety/snippets/{nullable-reference-types,resolve-warnings,migration-strategies}/`. Each minimal `.csproj`, `<Nullable>enable</Nullable>`, latest TFM. Build + execute per docs `copilot-instructions.md`. 6. Move `docs/csharp/tutorials/snippets/NullableIntroduction/` → `docs/csharp/fundamentals/tutorials/snippets/NullableIntroduction/`. Update tutorial `:::code:::` source paths. * Phase 4: TOC, redirects, deletes [docs/csharp/toc.yml](docs/csharp/toc.yml): remove lines 205–206 (legacy tutorial) and 293–296 (legacy concepts); add 4 new entries inside the `Null safety` node from PR 8. 8. [.openpublishing.redirection.csharp.json](.openpublishing.redirection.csharp.json): add 3 new redirects (alphabetical insertion); repoint existing redirects on lines 3126, 5389, 5447 (currently target `/dotnet/csharp/nullable-migration-strategies`) to new fundamentals URL to avoid chains. Run `sort-redirects` skill. 9. Delete [docs/csharp/nullable-references.md](docs/csharp/nullable-references.md), [docs/csharp/nullable-migration-strategies.md](docs/csharp/nullable-migration-strategies.md), [docs/csharp/tutorials/nullable-reference-types.md](docs/csharp/tutorials/nullable-reference-types.md). * Phase 5: Update links ***When reviewing commit-by-commit, this can be skimmed*** 10. Repoint inbound links across `docs/`. Critical: [docs/csharp/language-reference/compiler-messages/nullable-warnings.md](docs/csharp/language-reference/compiler-messages/nullable-warnings.md) has multiple links to moved files. * Major content review Review the drafts, make several changes in style, tone and substance. * Fix redirect chain for whats-new/tutorials/nullable-reference-types * Move nullable-migration-strategies to advanced-topics/update-applications Relocate the migration article out of fundamentals/null-safety into a new Advanced topics > Update existing apps subsection. Rename to nullable-migration-strategies.md, repath snippets, update inbound links from sibling articles and language-reference, repoint redirects, and add a redirect from the vacated path. * Update the TOC * Remove 98 duplicate redirect entries flagged by build * Fix duplicate H1 and missing #nullable-context bookmark Rename language-reference compiler-messages H1 'Resolve nullable warnings' to 'Nullable reference type warnings' to avoid collision with the new fundamentals article. Rename the H2 'Enable nullable reference types' to 'Nullable context' so existing inbound bookmarks #nullable-context resolve. * One more set of build warnings * one more set of build warnings * Apply suggestions from code review Co-authored-by: Andy (Steve) De George <67293991+adegeo@users.noreply.github.com> * Respond to additional feedback. * fix warnings Restore the details on nullable contexts. * Apply changes from new prompts The latest update to the prompts provided new themes for actions in this section. Run those to see how much it picks up. In addition, move a couple sections from fundamentals into the language reference. They were too deep for fundamentals, but were necessary in the reference section. * fix warnings Fix build warnings. * Another round of edits. * lint * Review in light of new prompt I ran the new prompts, and it caught a couple more things. * Apply suggestions from code review Co-authored-by: Andy (Steve) De George <67293991+adegeo@users.noreply.github.com> * Respond to feedback Update based on the final round of feedback. --------- Co-authored-by: Andy (Steve) De George <67293991+adegeo@users.noreply.github.com>
1 parent 387e0e4 commit 4a56539

45 files changed

Lines changed: 5614 additions & 5212 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.openpublishing.redirection.csharp.json

Lines changed: 4433 additions & 4417 deletions
Large diffs are not rendered by default.

docs/core/compatibility/sdk/6.0/csharp-template-code.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Starting in .NET 6, the project templates that ship with the .NET SDK use the la
1111
- [Global using directives](/dotnet/csharp/language-reference/language-specification/namespaces#1452-global-using-alias-directives)
1212
- [File-scoped namespaces](/dotnet/csharp/language-reference/language-specification/namespaces#143-namespace-declarations)
1313
- [Target-typed new expressions](/dotnet/csharp/language-reference/language-specification/expressions#128172-object-creation-expressions)
14-
- [Nullable reference types](../../../../csharp/nullable-references.md)
14+
- [Nullable reference types](../../../../csharp/fundamentals/null-safety/nullable-reference-types.md)
1515
- [Async Main return values](../../../../csharp/fundamentals/program-structure/main-command-line.md#main-return-values)
1616

1717
Some of the latest C# language features are not supported by previous target frameworks, so you might experience issues in the following scenarios:

docs/core/extensions/windows-service.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ After successfully adding the packages, your project file should now contain the
5353

5454
## Update project file
5555

56-
This worker project makes use of C#'s [nullable reference types](../../csharp/nullable-references.md). To enable them for the entire project, update the project file accordingly:
56+
This worker project makes use of C#'s [nullable reference types](../../csharp/fundamentals/null-safety/nullable-reference-types.md). To enable them for the entire project, update the project file accordingly:
5757

5858
:::code language="xml" source="snippets/workers/windows-service/App.WindowsService.csproj" range="1-7,12-20" highlight="5":::
5959

docs/core/whats-new/dotnet-5.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ ASP.NET Core 5.0 is based on .NET 5 but retains the name "Core" to avoid confusi
2424
- [App trimming](https://devblogs.microsoft.com/dotnet/app-trimming-in-net-5)
2525
- Windows Arm64 and Arm64 intrinsics
2626
- Tooling support for dump debugging
27-
- The runtime libraries are 80% annotated for [nullable reference types](../../csharp/nullable-references.md)
27+
- The runtime libraries are 80% annotated for [nullable reference types](../../csharp/fundamentals/null-safety/nullable-reference-types.md)
2828
- Performance improvements:
2929
- [Garbage Collection (GC)](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/#gc)
3030
- [System.Text.Json](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/#json)

docs/core/whats-new/dotnet-core-3-0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ C# 8.0 is also part of this release, which includes the [nullable reference type
2828

2929
Tutorials related to C# 8.0 language features:
3030

31-
- [Tutorial: Express your design intent more clearly with nullable and non-nullable reference types](../../csharp/tutorials/nullable-reference-types.md)
31+
- [Tutorial: Express your design intent more clearly with nullable and non-nullable reference types](../../csharp/fundamentals/tutorials/nullable-reference-types.md)
3232
- [Tutorial: Generate and consume async streams using C# 8.0 and .NET Core 3.0](../../csharp/asynchronous-programming/generate-consume-asynchronous-stream.md)
3333
- [Tutorial: Use pattern matching to build type-driven and data-driven algorithms](../../csharp/fundamentals/tutorials/pattern-matching.md)
3434

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
title: Nullable migration strategies
3+
description: Learn how to enable nullable reference types in an existing C# codebase. Choose a default context, address warnings progressively, and fully migrate.
4+
ms.date: 05/04/2026
5+
ms.topic: concept-article
6+
ms.subservice: null-safety
7+
ai-usage: ai-assisted
8+
---
9+
# Nullable migration strategies
10+
11+
> [!TIP]
12+
> **Starting a new project?** New projects created from .NET 6 or later templates already have `<Nullable>enable</Nullable>` set. You don't need a migration strategy: skip to [Resolve nullable warnings](../../fundamentals/null-safety/resolve-warnings.md).
13+
>
14+
> **Maintaining an existing codebase?** Read [Nullable reference types](../../fundamentals/null-safety/nullable-reference-types.md) first to understand contexts, annotations, and null-state. This article assumes you're familiar with those concepts and ready to plan a rollout.
15+
16+
When you turn on nullable reference types on a large project that started before nullable reference types were introduced, the compiler produces many warnings at once. Migration is about *sequencing* the work: choosing a default context, exposing warnings file by file or section by section, and converging on `<Nullable>enable</Nullable>` for the whole project. The right sequence depends on how active the codebase is and how much risk you can take in a single pass.
17+
18+
The end state is the same in every case: the project sets `<Nullable>enable</Nullable>` and contains no `#nullable` preprocessor directives.
19+
20+
## Choose a default context
21+
22+
The nullable context has two independent flags: *annotations* (whether `?` declares a nullable reference type) and *warnings* (whether the compiler emits diagnostics). Set them together as a single `<Nullable>` value:
23+
24+
| Default value | Annotations | Warnings | Best for |
25+
| ---------------------- | :---------: | :------: | --------------------------------------------------------------------- |
26+
| `disable` *(implicit)* | off | off | Stable libraries that won't take new feature work in this pass. |
27+
| `enable` | on | on | Active codebases with frequent new files. New code starts opted in. |
28+
| `warnings` | off | on | Two-phase migration: address warnings first, annotate later. |
29+
| `annotations` | on | off | Annotate the public API before fixing the internal warnings. |
30+
31+
Pick the strategy that best matches the goals for your project migration:
32+
33+
- **Disable as the default.** Set `<Nullable>disable</Nullable>` and add `#nullable enable` at the top of each file as you migrate it. Existing files stay nullable-oblivious until you touch them. This option has the lowest friction for stable libraries because new feature work is rare.
34+
- **Enable as the default.** Set `<Nullable>enable</Nullable>` and add `#nullable disable` at the top of every file you haven't migrated yet. Every new file is nullable-aware from the start, so the migration backlog can only shrink. This choice is better when development is active.
35+
- **Warnings as the default.** Set `<Nullable>warnings</Nullable>`. Choose this default for a two-phase migration: address warnings while every reference type is still treated as oblivious, then turn on annotations. The two-phase split keeps each step's diff focused.
36+
- **Annotations as the default.** Set `<Nullable>annotations</Nullable>`. Start by annotating your public API (`?` on members that allow `null`) before chasing warnings. The compiler doesn't emit warnings yet, so you can settle the API surface without distraction.
37+
38+
Your project file controls the global default. `#nullable` [preprocessor directives](../../language-reference/preprocessor-directives.md) override that default for a region of code:
39+
40+
:::code language="xml" source="snippets/nullable-migration-strategies/project-snippet.xml":::
41+
42+
Inside source files, the directive opts a region in or out of the project's nullable setting:
43+
44+
:::code language="csharp" source="snippets/nullable-migration-strategies/Program.cs" id="DirectiveOverrides":::
45+
46+
## Migrate file by file
47+
48+
The most predictable way to migrate a large project is to enable warnings or annotations file by file. The pattern is the same regardless of which default you pick:
49+
50+
1. Pick a file. Start with the deepest leaf types in your dependency graph, then move outward. Annotating a type causes new warnings in its callers, so working bottom-up minimizes rework.
51+
1. Add the `#nullable` directive that opts the file into the new behavior. Use `#nullable enable` if you want both flags. Use `#nullable enable warnings` for warning-only.
52+
1. Address the warnings in the file using the techniques in [Resolve nullable warnings](../../fundamentals/null-safety/resolve-warnings.md).
53+
1. Repeat for the next file.
54+
1. When every file in the project has its directive, remove the directives and set `<Nullable>enable</Nullable>` at the project level.
55+
56+
If your codebase already has `<Nullable>enable</Nullable>`, you're driving the *opposite* direction. Suppress warnings in unmigrated files until you're ready. Use `#nullable disable` to opt files out, then remove the suppressions one at a time.
57+
58+
## Migrate in two phases
59+
60+
A two-phase migration separates the two kinds of work that nullable reference types involve. You can sequence the phases either way, depending on which form of stability matters more to you.
61+
62+
### Warnings first, then annotations
63+
64+
Lead with warnings when fixing latent <xref:System.NullReferenceException?displayProperty=nameWithType> bugs is the priority:
65+
66+
1. **Phase 1: Address warnings.** Set the project default to `warnings`. Reference types remain nullable-oblivious, so the type system doesn't change yet. The compiler emits warnings everywhere your existing code might already throw a <xref:System.NullReferenceException?displayProperty=nameWithType>. Add null checks, restructure flow, or apply attributes until the project is warning-clean. Each fix makes the production code more resilient even before annotations exist.
67+
1. **Phase 2: Add annotations.** Switch the project default to `enable`. Reference types are now non-nullable by default, and `var` locals become nullable. New warnings reflect declarations that don't match how the variables are used. Add `?` to types that should allow `null`. Tighten APIs that should require non-null inputs.
68+
69+
### Annotations first, then warnings
70+
71+
Lead with annotations when stabilizing the public API surface is the priority. This sequence suits libraries: you can ship annotated signatures so consumers see the right contracts, then close out the internal warnings on your own schedule.
72+
73+
1. **Phase 1: Add annotations.** Set the project default to `annotations`. Reference types become non-nullable by default, but the compiler doesn't emit warnings, so the noise stays out of your way. Walk the public API and add `?` to every member that may legitimately return or accept `null`. Tighten the signatures that shouldn't. Because warnings are off, you can settle the API shape in focused commits without untangling the implementation at the same time.
74+
1. **Phase 2: Address warnings.** Switch the project default to `enable`. The annotations you added in phase 1 now feed null-state analysis, so the warnings the compiler emits are higher quality from the start: each one points at code whose behavior doesn't match the contract you already published. Resolve them with the techniques in [Resolve nullable warnings](../../fundamentals/null-safety/resolve-warnings.md).
75+
76+
### Choosing between the orderings
77+
78+
Each ordering separates the phases into smaller, more reviewable diffs. One phase changes only behavior, and the other changes only types. The disadvantage is that you visit each file twice. For mature, stable code where every change carries risk, the two passes are usually worth it. Pick *warnings first* when you most want to harden running code. Pick *annotations first* when you most want to publish a stable contract.
79+
80+
## Generated code is excluded
81+
82+
The compiler treats files marked as generated as if the nullable context were disabled, regardless of the project's setting. A file is considered generated when any of the following conditions are true:
83+
84+
- An `.editorconfig` rule sets `generated_code = true` for the file.
85+
- The first comment in the file contains `<auto-generated>` or `<auto-generated/>`.
86+
- The file name starts with `TemporaryGeneratedFile_`.
87+
- The file name ends with `.designer.cs`, `.generated.cs`, `.g.cs`, or `.g.i.cs`.
88+
89+
Generators that produce nullable-aware output can opt back in by emitting `#nullable enable` at the top of the generated file.
90+
91+
## When you're done
92+
93+
After every file participates in the project default and the `<Nullable>enable</Nullable>` element is set:
94+
95+
- Remove every `#nullable` directive in your source.
96+
- Remove `null!` and `default!` initializers that you added only to silence warnings during migration. Replace them with proper initialization, or make type a nullable reference type.
97+
- Spot-check the public API. Every member that returns or accepts `null` should be annotated with `?`. The annotations are part of your contract once the package ships.
98+
99+
You're now in the same state as new projects: nullable reference types are part of the type system, and any new warnings reflect a real mismatch between declarations and code. Use [Resolve nullable warnings](../../fundamentals/null-safety/resolve-warnings.md) to address them as they come up.
100+
101+
## Related content
102+
103+
- [Nullable reference types](../../fundamentals/null-safety/nullable-reference-types.md)
104+
- [Resolve nullable warnings](../../fundamentals/null-safety/resolve-warnings.md)
105+
- [Nullable static analysis attributes](../../language-reference/attributes/nullable-analysis.md)
106+
- [Working with nullable reference types in EF Core](/ef/core/miscellaneous/nullable-reference-types)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
namespace MigrationStrategies;
2+
3+
internal static class Examples
4+
{
5+
public static void RunAll()
6+
{
7+
Console.WriteLine(LegacyHelper.GetGreeting("ada"));
8+
Console.WriteLine(MigratedHelper.GetGreeting("ada"));
9+
}
10+
}
11+
12+
// <DirectiveOverrides>
13+
#nullable disable
14+
public static class LegacyHelper
15+
{
16+
// This file is nullable-oblivious. Reference types use the legacy rules.
17+
public static string GetGreeting(string name) =>
18+
name == null ? "hello" : $"hello {name}";
19+
}
20+
#nullable restore
21+
22+
#nullable enable
23+
public static class MigratedHelper
24+
{
25+
// This file is fully migrated. Reference types are non-nullable by default.
26+
public static string GetGreeting(string? name) =>
27+
name is null ? "hello" : $"hello {name}";
28+
}
29+
#nullable restore
30+
// </DirectiveOverrides>
31+
32+
internal static class Program
33+
{
34+
private static void Main() => Examples.RunAll();
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
</Project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<PropertyGroup>
2+
<Nullable>enable</Nullable>
3+
</PropertyGroup>

docs/csharp/fundamentals/null-safety/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ By using the `?` annotation, you declare your intent:
4444

4545
:::code language="csharp" source="snippets/null-safety-overview/Program.cs" ID="NrtIntro":::
4646

47-
All .NET projects that modern SDK templates create enable nullable reference types by default. For complete guidance on enabling and annotating, see [Nullable reference types](../../nullable-references.md).
47+
All .NET projects that modern SDK templates create enable nullable reference types by default. For complete guidance on enabling and annotating, see [Nullable reference types](nullable-reference-types.md).
4848

4949
## Null operators
5050

0 commit comments

Comments
 (0)