Skip to content

[release/11.0] Make the Nullable<T>.Value exception message actionable - #132379

Merged
jeffhandley merged 2 commits into
release/11.0from
backport/pr-132367-to-release/11.0
Aug 21, 2026
Merged

jeffhandley merged 2 commits into
release/11.0from
backport/pr-132367-to-release/11.0

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

Backport of #132367 to release/11.0 (for .NET 11 RC2)

/cc @jeffhandley

Customer Impact

  • Customer reported
  • Found internally

Reported in #126560. Nullable<T>.Value throws InvalidOperationException with "Nullable object must have a value." That describes an obligation on the object's state rather than the invalid action, never names the member that was accessed, and doesn't mention HasValue. The reporter's point is that it reads as "you must assign a value", when the fix is normally to check HasValue before reading Value.

New text: "Cannot read the Value property of a Nullable object that has no value. Check HasValue before reading Value."

Diagnostics only; no functional impact, but addressing a customer-reported pitfall.

Regression

  • Yes
  • No

The original wording predates .NET Core.

Testing

Covered by CI on #132367. No test asserts this message text; verified by searching the repo, including the NativeAOT FrameworkStrings string-pinning test, which pins other resources. No tests were added: the change is a resource value with no behavior to exercise beyond the string itself. Locally confirmed that the built System.Private.CoreLib contains the new string and no longer contains the old one.

Risk

Low. The functional change is a single resource value in src/libraries/System.Private.CoreLib/src/Resources/Strings.resx. The resource name is unchanged, so there is no code change, no API change, and no behavior change other than the message text.

This backport also carries the cleanup that came out of review on #132367: 15 unreferenced strings removed from the NativeAOT System.Private.Reflection.Execution Strings.resx (including a dead copy of InvalidOperation_NoValue) and the unused HashHelpers.cs compile item. All were verified unreferenced and have no runtime effect, but if a minimal backport diff is preferred, those commits can be dropped and only the exception message change taken.

Fixes #126560

## Problem

`InvalidOperation_NoValue` — the message thrown by `Nullable<T>.Value`
when there's no value — reads:

> Nullable object must have a value.

As #126560 points out, this states an obligation on the *object's state*
rather than naming the *invalid action*. It reads as "you must assign a
value", when the actual fix is almost always the opposite: don't read
`.Value` without checking `.HasValue` first. It also never mentions
which member was accessed.

## Change

```diff
-Nullable object must have a value.
+Cannot read the Value property of a Nullable object that has no value. Check HasValue before reading Value.
```

This follows the wording style already used in the repo for comparable
"you called this on something that has no value" failures:

- `SqlMisc_NullValueMessage` (`System.Data.Common`) — *"Data is Null.
This method or property cannot be called on Null values."*
- `ReflectionModel_ExportNotReadable`
(`System.ComponentModel.Composition`) — *"Cannot get the value of
property '{0}', because the member is not readable. The property must
have an accessible getter."*
- `NoResultOnFailed` (`System.Text.RegularExpressions`) — *"Result
cannot be called on a failed Match."*

The issue also floated "Cannot read the Value property of a null
Nullable object." I avoided "null Nullable object" because `Nullable<T>`
is a struct and is never `null` — "that has no value" keeps the same
cadence without the imprecision. The second sentence names the check to
perform, per *"DO ensure that exception messages are clear and
actionable"*
(`docs/coding-guidelines/framework-design-guidelines-digest.md`).

The resource **name** is unchanged.

## Scope

`InvalidOperation_NoValue` is not shared with any unrelated call site —
all definitions and references are `Nullable<T>.Value` semantics:

| | Path |
|---|---|
| def |
`src/libraries/System.Private.CoreLib/src/Resources/Strings.resx` |
| def |
`src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Resources/Strings.resx`
|
| use |
`ThrowHelper.ThrowInvalidOperationException_InvalidOperation_NoValue` —
sole caller is the `Nullable<T>.Value` getter |
| use | `CustomMethodMapper.Nullable.cs` — NativeAOT reflection-invoke
shim for `Nullable<T>.Value` |

Both `.resx` definitions are updated. Mono shares the libraries CoreLib
`.resx`; there is no third copy. No test asserts the text, and the
NativeAOT `FrameworkStrings` string-pinning smoke test pins other
resources.

Per `docs/coding-guidelines/breaking-change-rules.md`, changing the text
of an error message is not a breaking change ("users should not rely on
these text messages, and they change anyways based on culture").

## Validation

- Built `System.Private.CoreLib` (`build.cmd clr.corelib -c Release`) —
clean.
- Confirmed against the produced `System.Private.CoreLib.dll` that the
new string is embedded and the old one is gone, so
`SR.InvalidOperation_NoValue` still resolves.
- The NativeAOT `System.Private.Reflection.Execution` `.resx` was not
compiled locally — that project currently fails on a pre-existing
missing generated `AsmOffsets.cs` requiring a full NativeAOT native
build. The edit there is identical in shape and doesn't change the
resource name. CI covers it.

> [!NOTE]
> This pull request description was generated by GitHub Copilot.

---------

Co-authored-by: Copilot App <[email protected]>
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: jkotas <[email protected]>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

@jeffhandley jeffhandley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@artl93 I recommend we take this in .NET 11 RC2, addressing customer feedback about a misleading exception message.

@jeffhandley

Copy link
Copy Markdown
Member

/ba-g Failures are in the unrelated System.Net.HttpListener.Tests

@artl93 artl93 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appear to be unrelated changes.

Co-authored-by: Copilot App <[email protected]>

@artl93 artl93 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved.

@jeffhandley jeffhandley added Servicing-approved Approved for servicing release and removed Servicing-consider Issue for next servicing release review labels Aug 21, 2026
@jeffhandley

jeffhandley commented Aug 21, 2026 •

Copy link
Copy Markdown
Member

/ba-g Failures are System.Net.Tests.InvalidClientRequestTests.GetContext_InvalidRequest_DoesNotGetContext times out (#132336) plus a few unrelated DeadLetter results.

@jeffhandley
jeffhandley merged commit 2d27bab into release/11.0 Aug 21, 2026
131 of 139 checks passed
@jeffhandley
jeffhandley deleted the backport/pr-132367-to-release/11.0 branch August 21, 2026 17:09
@dotnet-milestone-bot dotnet-milestone-bot Bot modified the milestones: 11.0.0, 11.0-rc2 Aug 26, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Reflection Servicing-approved Approved for servicing release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants