Skip to content

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

Open
github-actions[bot] wants to merge 1 commit into
release/11.0from
backport/pr-132367-to-release/11.0
Open

[release/11.0] Make the Nullable<T>.Value exception message actionable#132379
github-actions[bot] wants to merge 1 commit 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 <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Reflection Servicing-consider Issue for next servicing release review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant