Package Versions and Ranges
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
Every PackageReference Version attribute is a range, even when it looks like a single version, and NuGet’s
version strings follow Semantic Versioning with a few differences. This page tries each notation against a feed that
holds Ledgerline.Money 1.0.0, 1.5.0, 2.0.0 and 2.1.0-beta.1, using NuGet 7.9.0 in .NET SDK 10.0.402. Choosing
versions for packages you publish is on
Versioning and Prerelease Packages.
Version basics
A NuGet version is Major.Minor.Patch[.Revision][-Prerelease][+Metadata]:
-
Prerelease labels (
-beta.1,-rc.2) sort before the release with the same numbers:2.1.0-beta.1<2.1.0. Labels are compared part by part, numeric parts numerically (beta.2<beta.10) and text parts alphabetically and case-insensitively. -
Metadata after
+(+e33226ff) is ignored when comparing and is not part of the identity on nuget.org. -
A package using SemVer 2.0.0 features — dotted prerelease labels or metadata — is only visible to NuGet 4.3+ clients.
Where NuGet’s NuGetVersion differs from SemVer 2.0.0: it allows a fourth part (1.0.0.1), it requires only the major
part (1, 1.0, 1.0.0 and 1.0.0.0 are equal), and it compares prerelease labels without case.
Normalization
NuGet normalizes versions when it stores and compares them: missing parts become 0, leading zeros are removed, a zero
fourth part is dropped, and metadata is removed. 1.5, 1.5.0, 01.5.00 and 1.5.0.0 are the same version — the
reference Version="01.5.00" restored Ledgerline.Money/1.5.0. Feeds and the global-packages folder use the normalized
string in paths (ledgerline.money/1.5.0/).
Source: Normalized version numbers.
Range notation
| Notation | Meaning | Restored here |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
at most 2.0 / below 2.0 (no lower bound) |
|
|
invalid |
restore failed |
Two rules explain the results: NuGet picks the lowest version that satisfies the range
(Dependency Resolution), and a plain
version is a minimum, not "this version". [1.0,1.5] therefore restored 1.0.0, not 1.5.0, and so did
[1.0,2.0]: an inclusive upper bound admits 2.0.0 but the lowest match still wins. (1.0,2.0) excludes both ends,
which leaves only 1.5.0 on this feed.
The exclusive lower bound produced a warning, for (1.0,) and again for (1.0,2.0), where NuGet prints the range
with both bounds:
~/ledgerline/samples/Deps/Deps.csproj : warning NU1604: Project dependency Ledgerline.Money (> 1.0.0) does not contain an inclusive lower bound. Include a lower bound in the dependency version to ensure consistent restore results.
~/ranges/Deps/Deps.csproj : warning NU1604: Project dependency Ledgerline.Money (> 1.0.0 && < 2.0.0) does not contain an inclusive lower bound. Include a lower bound in the dependency version to ensure consistent restore results.
(The second line comes from a scratch project, ~/ranges/Deps, whose nuget.config lists only a folder feed with the
same four Ledgerline.Money versions; the (1.0,2.0), [1.0,2.0] and prerelease examples below were run there.)
The invalid (1.0) gave no NuGet diagnostic at all — only MSBuild’s generic message that the restore task failed
silently:
~/.dotnet/sdk/10.0.402/NuGet.targets(198,5): error MSB4181: The "RestoreTask" task returned false but did not log an error. [~/ledgerline/samples/Deps/Deps.csproj]
A minimum that no feed has is NU1102, which lists what each source offers:
~/ledgerline/samples/Deps/Deps.csproj : error NU1102: Unable to find package Ledgerline.Money with version (>= 3.0.0)
~/ledgerline/samples/Deps/Deps.csproj : error NU1102: - Found 4 version(s) in ledgerline-local [ Nearest version: 2.1.0-beta.1 ]
~/ledgerline/samples/Deps/Deps.csproj : error NU1102: - Versions from ledgerline-bagetter were not considered
~/ledgerline/samples/Deps/Deps.csproj : error NU1102: - Versions from nuget.org were not considered
("not considered" because the repository’s package source mapping sends Ledgerline.* only to ledgerline-local;
Package Sources and Source Mapping.)
A minimum whose exact version is missing but a higher one exists resolves with NU1603:
~/ledgerline/samples/Deps/Deps.csproj : warning NU1603: Deps depends on Ledgerline.Money (>= 2.1.0-beta.0) but Ledgerline.Money 2.1.0-beta.0 was not found. Ledgerline.Money 2.1.0-beta.1 was resolved instead.
Prereleases in a range
A range admits prereleases only when something in the graph asks for one — in practice, when a bound of the range is
itself a prerelease. The feed’s only version above 2.0.0 is 2.1.0-beta.1:
<PackageReference Include="Ledgerline.Money" Version="[2.0.1,3.0)" />
Determining projects to restore...
~/ranges/Deps/Deps.csproj : error NU1103: Unable to find a stable package Ledgerline.Money with version (>= 2.0.1 && < 3.0.0)
~/ranges/Deps/Deps.csproj : error NU1103: - Found 4 version(s) in ledgerline-local [ Nearest version: 2.1.0-beta.1 ]
Failed to restore ~/ranges/Deps/Deps.csproj (in 176 ms).
2.1.0-beta.1 lies inside [2.0.1,3.0) but is not stable, so NuGet reports NU1103 rather than NU1102. Giving the
lower bound a prerelease label opts in:
<PackageReference Include="Ledgerline.Money" Version="[2.1.0-beta.1,3.0)" />
<!-- or, to accept any prerelease at or above 2.0.1: -->
<PackageReference Include="Ledgerline.Money" Version="[2.0.1-0,3.0)" />
Determining projects to restore...
Restored ~/ranges/Deps/Deps.csproj (in 275 ms).
Determining projects to restore...
~/ranges/Deps/Deps.csproj : warning NU1603: Deps depends on Ledgerline.Money (>= 2.0.1-0 && < 3.0.0) but Ledgerline.Money 2.0.1-0 was not found. Ledgerline.Money 2.1.0-beta.1 was resolved instead.
Restored ~/ranges/Deps/Deps.csproj (in 196 ms).
Both restored Ledgerline.Money/2.1.0-beta.1 (each was restored separately with dotnet restore --force). -0 is the
lowest possible prerelease label, so 2.0.1-0 means "2.0.1 or any prerelease of it or later"; because no package
2.0.1-0 exists, the restore adds NU1603. packages.config projects never mix stable and prerelease versions this way:
a stable range there excludes prereleases from the whole graph.
How the tools write versions
The tools that add a reference write different things into the project:
| Tool | What it writes | Ranges |
|---|---|---|
|
The latest stable version as a plain |
— |
|
The string exactly as given, not normalized: |
Accepted: |
Visual Studio Package Manager UI (not run here) |
One version picked from the Version list (prereleases appear only with Include prerelease); the documentation does not show the attribute it writes. |
No range is entered in the UI. In |
nuget.exe (not run here) |
|
Only in |
dotnet package add Ledgerline.Money
dotnet package add Ledgerline.Money --version "[1.0,2.0)"
info : Adding PackageReference for package 'Ledgerline.Money' into project '~/ranges/Deps/Deps.csproj'.
info : PackageReference for package 'Ledgerline.Money' version '2.0.0' added to file '~/ranges/Deps/Deps.csproj'.
info : Adding PackageReference for package 'Ledgerline.Money' into project '~/ranges/Deps/Deps.csproj'.
info : PackageReference for package 'Ledgerline.Money' version '[1.0,2.0)' added to file '~/ranges/Deps/Deps.csproj'.
(The reference was removed between the two commands; the lines shown are the ones that report what was written.)
Floating versions
A * makes the range float to the highest matching version (the opposite of the normal lowest-applicable rule):
| Version | Floats to | Restored here |
|---|---|---|
|
highest 1.x release |
|
|
highest release |
|
|
highest version, including prereleases |
|
|
highest 2.1.0 prerelease (or 2.1.0) |
|
Floating versions make a restore depend on when it runs: the same commit restores differently after a new package
is published. They suit tools and experiments, not shipped builds; with lock files they are recorded at the resolved
version (Lock Files and Locked Mode),
and with Central Package Management they need CentralPackageFloatingVersionsEnabled.
Source: PackageReference: Floating versions.
Best practice
-
Applications and libraries alike: a plain minimum (
Version="1.5.0") and an update process. Exact versions and upper bounds in a library make it impossible for consumers to combine it with other packages that need a newer dependency (NU1107/NU1608 on Dependency Resolution). -
Upper bounds only for a known incompatibility, documented next to the reference.
-
Lock files, not exact versions, for reproducible application restores.
Source: Package versioning: Best practice.
Common mistakes
-
Reading
Version="1.5.0"as "exactly 1.5.0" — it is a minimum. -
Expecting a stable range to pick up a prerelease —
[2.0.1,3.0)fails with NU1103; put a prerelease label on a bound. -
(1.0)and other invalid ranges — restore fails with no NuGet message. -
Upper bounds in library dependencies — consumers cannot resolve.
-
Floating versions in release builds — the restore changes over time.