Skip to content

Commit b9c933b

Browse files
committed
Major content review
Review the drafts, make several changes in style, tone and substance.
1 parent bd6f8a3 commit b9c933b

9 files changed

Lines changed: 95 additions & 88 deletions

File tree

docs/csharp/fundamentals/null-safety/migration-strategies.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ ai-usage: ai-assisted
1313
>
1414
> **Maintaining an existing codebase?** Read [Nullable reference types](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.
1515
16-
When you turn on nullable reference types in a project that wasn't built for the feature, 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.
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.
1717

1818
The end state is the same in every case—the project sets `<Nullable>enable</Nullable>` and contains no `#nullable` preprocessor directives.
1919

2020
## Choose a default context
2121

22-
The nullable context has two independent flags: *annotations* (whether `?` declares a nullable reference type) and *warnings* (whether the compiler emits diagnostics). You set them together as a single `<Nullable>` value:
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:
2323

2424
| Default value | Annotations | Warnings | Best for |
2525
| --------------------- | :---------: | :------: | --------------------------------------------------------------------- |
@@ -30,8 +30,8 @@ The nullable context has two independent flags: *annotations* (whether `?` decla
3030

3131
Pick the strategy that makes the next file you create do the right thing automatically:
3232

33-
- **Disable as the default.** Add `#nullable enable` at the top of each file as you migrate it. Existing files stay nullable-oblivious until you touch them. This is the lowest-friction option 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 is the better choice when development is active.
33+
- **Disable as the default.** 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.
3535
- **Warnings as the default.** 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.
3636
- **Annotations as the default.** 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.
3737

@@ -43,7 +43,7 @@ Your project file controls the global default. The `#nullable` preprocessor dire
4343

4444
## Migrate file by file
4545

46-
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 picked:
46+
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:
4747

4848
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.
4949
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.
@@ -55,16 +55,29 @@ If your codebase already has `<Nullable>enable</Nullable>` and you're driving th
5555

5656
## Migrate in two phases
5757

58-
A two-phase migration separates the two kinds of work that nullable reference types involve:
58+
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.
59+
60+
### Warnings first, then annotations
61+
62+
Lead with warnings when fixing latent <xref:System.NullReferenceException?displayProperty=nameWithType> bugs is the priority:
5963

6064
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.
6165
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.
6266

63-
The advantage of separating the phases is that each diff is smaller and reviewable. Phase 1 changes only behavior; phase 2 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.
67+
### Annotations first, then warnings
68+
69+
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.
70+
71+
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.
72+
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](resolve-warnings.md).
73+
74+
### Choosing between the orderings
75+
76+
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.
6477

6578
## Generated code is excluded
6679

67-
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 is true:
80+
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:
6881

6982
- An `.editorconfig` rule sets `generated_code = true` for the file.
7083
- The first comment in the file contains `<auto-generated>` or `<auto-generated/>`.

0 commit comments

Comments
 (0)