Skip to content

[pull] master from angularsen:master - #47

Open
pull[bot] wants to merge 98 commits into
AIKICo:masterfrom
angularsen:master
Open

[pull] master from angularsen:master#47
pull[bot] wants to merge 98 commits into
AIKICo:masterfrom
angularsen:master

Conversation

@pull

@pull pull Bot commented Jun 11, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

TS-Andritz and others added 3 commits June 11, 2026 08:07
Adding two units used in our domain for small pressure values in two
different systems of measurement (metric and US customary).
Add the grams force unit
@pull pull Bot locked and limited conversation to collaborators Jun 11, 2026
@pull pull Bot added the ⤵️ pull label Jun 11, 2026
angularsen and others added 15 commits June 11, 2026 10:58
Closes #1647

nanoFramework support has moved to its own repository,
[nanoframework/nanoFramework.UnitsNet](https://github.com/nanoframework/nanoFramework.UnitsNet),
which now publishes its own NuGet packages. This removes the in-repo
nanoFramework stack.

## Changes
- **Projects**: removed the entire `UnitsNet.NanoFramework/` directory
(projects + generated code).
- **Code generation**: removed `CodeGen/Generators/NanoFrameworkGen/`,
`NanoFrameworkGenerator.cs`, `NanoFrameworkVersions.cs`, and the
`skipNanoFramework`/`updateNanoFrameworkDependencies` wiring in
`Program.cs`.
- **Build scripts**: removed nanoFramework build/pack/version logic from
`Build/build.ps1`, `build-functions.psm1`, `init.ps1`,
`set-version-UnitsNet.ps1`, and deleted `build-pack-nano-nugets.psm1`.
`init.ps1` no longer downloads NuGet.exe or the VS nanoFramework
extension.
- **CI**: dropped the nanoFramework setup steps and
`-IncludeNanoFramework` flag from GitHub Actions (`ci.yml`, `pr.yml`)
and Azure Pipelines.
- **Misc**: deleted redundant `build-all-targets.bat` (now identical to
`build.bat`), `upgrade-nanoframework.sh`, `Docs/nanoframework.md`;
cleaned up `README.md`, `AGENTS.md`, `Docs/README.md`, `cSpell.json`,
and the ReSharper dictionary.

External-port references (the ports table and dependent-projects list in
README) are intentionally kept, since the package still exists in its
new home.

## Verification
- `dotnet build CodeGen` → 0 warnings, 0 errors
- `dotnet run --project CodeGen` → regenerates cleanly, no nanoFramework
output
- `dotnet build UnitsNet.slnx -c Release` → 0 errors (only pre-existing
obsolete-API benchmark warnings)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary

Fixes the Azure Pipelines build failure introduced by #1657:

\`\`\`
error CS0266: Cannot implicitly convert type 'UnitsNet.UnitInfo<TUnit>'
to
  'UnitsNet.UnitInfo<TQuantity, TUnit>'
\`\`\`

at
[UnitsNet/Extensions/QuantityExtensions.cs:47](UnitsNet/Extensions/QuantityExtensions.cs:47):

\`\`\`csharp
return quantity.QuantityInfo[quantity.Unit];
\`\`\`

## Why this happens

The receiver is statically `IQuantity<TQuantity, TUnit>`, whose
`QuantityInfo` getter is shadowed (`new`) to return the more specific
`QuantityInfo<TQuantity, TUnit>`. On the hosted Azure Pipelines image's
Roslyn build, member lookup resolves to the inherited
`IQuantity<TUnit>.QuantityInfo` (`QuantityInfo<TUnit>`) instead, so the
indexer returns `UnitInfo<TUnit>` and the conversion to
`UnitInfo<TQuantity, TUnit>` fails. Local builds on newer SDKs happened
to resolve the shadowed member and compiled cleanly, which is why
#1657's CI never tripped on the maintainer's machine.

## Fix

The runtime value is always the more derived type, so cast through
`QuantityInfo<TQuantity, TUnit>` explicitly to surface the typed
indexer. No behavior change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… interfaces (#1675)

Per discussion on #1657, this introduces a static abstract `Info` member
on the IQuantityOfType<TQuantity> and IQuantity<TSelf,TUnitType>
interfaces under #if NET, marks the existing instance QuantityInfo
property as [Obsolete] on .NET 5+, and adds GetQuantityInfo() extension
methods on QuantityExtensions so callers have a single discoverable API
that works on every TFM.

Why
- The instance QuantityInfo property invariably returns a per-type
static value. Exposing it as an instance member implies it can vary per
instance, which it cannot, and incurs interface dispatch (boxing on
structs) for every call.
- The static abstract member lets generic algorithms reach the info with
`TSelf.Info` directly, no boxing, no virtual call.
- The extension method pair (`GetQuantityInfo()` /
`GetQuantityInfo<TUnit>()`) is the discoverable replacement for callers
that only have an `IQuantity` reference. It looks the quantity up via
`UnitsNetSetup.Default.Quantities`.
- Keeping the instance property obsolete (warning) instead of removing
it preserves source compatibility for existing callers and the
netstandard2.0 contract. We can promote to error / remove once
netstandard2.0 is dropped.

Implementation notes
- Generated quantities already expose `public static QuantityInfo<TSelf,
TUnitType> Info { get; }`, which directly satisfies the typed static
abstract. The non-generic `IQuantityOfType<TSelf>.Info` is satisfied by
a default static implementation in IQuantity<TSelf,TUnitType>: `static
QuantityInfo IQuantityOfType<TSelf>.Info => TSelf.Info;`. No codegen
change required.
- The IQuantity bridge `QuantityInfo IQuantity.QuantityInfo =>
QuantityInfo;` chain inside the interfaces uses #pragma to suppress the
obsolete warning on the bridge itself.
- Internal callers in UnitsNet were migrated to either `TSelf.Info` /
`TSelf.From` (where the generic constraint allows) or
`quantity.GetQuantityInfo()` (where it doesn't). Callers that must keep
working for custom quantities not registered in `UnitsNetSetup.Default`
(JsonNet serialization, debugger proxy, QuantityTypeConverter) keep
using the instance member with a `#pragma warning disable CS0618` and a
comment explaining why.
- HowMuch test custom quantity changed `public static readonly` field to
`public static QuantityInfo Info { get; }` property to satisfy the
static abstract.
- Tests added for round-trip equivalence between `Mass.Info`,
`mass.GetQuantityInfo()`, `TQuantity.Info` (via static abstract on
IQuantityOfType<T>), and `TSelf.Info` (via static abstract on
IQuantity<TSelf,TUnit>).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fork pull request workflows cannot request the OIDC token required by the
Claude action. Skip these automatic reviews to avoid failed checks and limit
exposure to untrusted PR input. Maintainers can still request a review by
commenting `@claude`.
Standard for paper grammage/base weight in North America is lbs/MSF
(pound per 1000 square feet) instead of g/m².
Conversion factor is standardized by TAPPI (free sample of TIP 0800-01
can be found
[here](https://www.normsplash.com/Samples/TAPPI/145775521/TAPPI-TIP-0800-01-2012-en.pdf)).

I also added lbs/ft² for completeness.

---------

Co-authored-by: Andreas Fischer <andreasfische@bhs-intralogistics.com>
Co-authored-by: Andreas Gullberg Larsen <andreas.larsen84@gmail.com>
Adds additional BTU-based units for power density, thermal conductivity,
and heat transfer coefficient.

## Changes

- Added `BtuPerSecondCubicInch` and `BtuPerSecondCubicFoot` to
`PowerDensity`.
- Added `BtuPerSecondInchFahrenheit` to `ThermalConductivity`.
- Added `BtuPerSecondSquareInchDegreeFahrenheit` to
`HeatTransferCoefficient`.
- Regenerated UnitsNet source, resources, number extensions, and
generated tests.
- Fixed the per-second heat transfer coefficient abbreviations to avoid
invalid hour-based aliases.

## Validation

- Ran focused tests for `HeatTransferCoefficientTests`,
`PowerDensityTests`, and `ThermalConductivityTests` on `net9.0`: 1600
passed.

---------

Co-authored-by: Hayley Easter <hayley.easter@ansys.com>
Co-authored-by: Andreas Gullberg Larsen <andreas.larsen84@gmail.com>
Adds square millimeter variants for `HeatFlux`, including metric
prefixes generated from `WattPerSquareMillimeter`.

## Changes

- Added `WattPerSquareMillimeter` to `HeatFlux`.
- Added generated metric-prefixed square millimeter units, such as
`MilliwattPerSquareMillimeter`, `MicrowattPerSquareMillimeter`, and
`KilowattPerSquareMillimeter`.
- Regenerated UnitsNet source, resources, number extensions, and
generated tests.
- Merged current `master` and resolved stale nanoFramework
generated-file conflicts by keeping the upstream removal of
nanoFramework support.

## Validation

- Ran focused `HeatFluxTests` on `net9.0`: 463 passed.
- Ran `git diff --check`: passed.

Co-authored-by: June Meservy <meservy@nanotest.eu>
Co-authored-by: Andreas Gullberg Larsen <andreas.larsen84@gmail.com>
## Summary

Adds `UnitInfo` convenience overloads to `UnitAbbreviationsCache` for
the same method groups that already accept `UnitKey`:

- `GetDefaultAbbreviation(UnitInfo, ...)`
- `GetUnitAbbreviations(UnitInfo, ...)`
- `MapUnitToAbbreviation(UnitInfo, ...)`
- `MapUnitToDefaultAbbreviation(UnitInfo, ...)`

This avoids caller-side `unitInfo.UnitKey` extraction when the caller
already has a `UnitInfo`, while keeping the existing `UnitKey`, typed
enum, and `(Type, int)` overloads.

## Maintainer Updates

- Added explicit `ArgumentNullException(nameof(unitInfo))` handling for
the new public overloads.
- Kept the overloads scoped to the cache's configured quantities by
resolving the supplied `UnitInfo.UnitKey` through the cache's
`QuantityInfoLookup` before reading or mapping abbreviations.
- Added tests for null `UnitInfo` inputs.
- Added tests confirming a cache constructed with a limited quantity set
rejects `UnitInfo` values outside that set.

## Validation

- Ran `dotnet test UnitsNet.Tests --filter
"FullyQualifiedName~UnitAbbreviationsCacheTests" --no-restore
--verbosity minimal`.
- Result: 36 tests passed per framework across net8, net9, and net10.

---------

Co-authored-by: Andreas Gullberg Larsen <andreas.larsen84@gmail.com>
## Motivation

Issue #1663 reports that the generated abbreviations for MolarFlow
per-hour units are shifted by one kilo prefix:

- `MolarFlowUnit.MolePerHour` formats/parses as `kmol/h`
- `MolarFlowUnit.KilomolePerHour` formats/parses as `kkmol/h`

The root cause is that `MolePerHour` has `Prefixes: [ "Kilo" ]`, but its
base abbreviation in `MolarFlow.json` was already written as `kmol/h`.
The prefix generator then prepended another `k` for the generated
`KilomolePerHour` unit.

## Changes

- Change `MolePerHour` abbreviation from `kmol/h` to `mol/h`.
- Regenerate MolarFlow resources and generated tests.
- Generated abbreviations now match the other molar-flow units:
  - `MolePerHour`: `mol/h`
  - `KilomolePerHour`: `kmol/h`

## Validation

- `generate-code.bat`
- `dotnet test UnitsNet.Tests --filter
"FullyQualifiedName~MolarFlowTests"`

Fixes #1663.
## Why

`UnitsNetBaseJsonConverter` instantiates registered custom quantity
types with `Activator.CreateInstance()`. If that reflection call fails,
or if it returns `null`, the converter should not return `null` from its
non-null `IQuantity` conversion path or surface an unclear reflection
exception.

## What

- Throw a `UnitsNetException` when a registered custom quantity cannot
be instantiated.
- Include the quantity type, unit type, unit value, and a stable error
code in `Exception.Data`.
- Use `UnitsNetException.ErrorCodeDataKey` as the shared
`Exception.Data` key for error codes.
- Preserve the original reflection/constructor failure as the inner
exception.
- Add regression tests for failed registered quantity instantiation and
constructor failures.

## Validation

- `dotnet test UnitsNet.Serialization.JsonNet.Tests -f net8.0 --filter
"FullyQualifiedName~UnitsNetBaseJsonConverterTest"`
- `dotnet test UnitsNet.Serialization.JsonNet.Tests -f net9.0 --filter
"FullyQualifiedName~UnitsNetBaseJsonConverterTest"`
- `dotnet test UnitsNet.Serialization.JsonNet.Tests -f net10.0 --filter
"FullyQualifiedName~UnitsNetBaseJsonConverterTest"`

---------

Co-authored-by: June Meservy <meservy@nanotest.eu>
Co-authored-by: Andreas Gullberg Larsen <andreas.larsen84@gmail.com>
JustASquid and others added 9 commits July 20, 2026 21:02
## Motivation

Some generated unit relations that are useful in structural engineering
were missing. Adding them makes it possible to express common derived
relationships directly with UnitsNet quantities instead of manually
converting through raw numeric values.

## Changes

- Add relation definitions for structural-engineering workflows,
including density/length to area density, area/area to area moment of
inertia, specific weight/volume to force, and acceleration/area density
to pressure.
- Regenerate the affected relation operators and tests.

## Validation

- GitHub PR Build: passing.
- Azure UnitsNet PR: passing.
- Local maintainer check: merged with current `master`, reran code
generation with no extra diff, and ran the affected relation test
classes on net9 successfully.
- The only red check is Claude code review, which should not block this
PR.
## Motivation

The current quantity interface hierarchy duplicates generic-math
contracts and couples arithmetic
capabilities to interfaces that also carry the unit enum type.

This results in verbose generic constraints such as:

```csharp
where TQuantity : ILinearQuantity<TQuantity>, IQuantity<TQuantity, TUnit>
```

Linear and logarithmic quantities also declare many of the same
arithmetic operator interfaces
independently, making the hierarchy harder to understand and extend.

This PR separates the arithmetic capability from unit typing and
composes the more specific quantity
interfaces from those reusable contracts.

## Changes

- Introduce `IArithmeticQuantity<TSelf>` as the unit-independent
arithmetic capability.
- Make `IArithmeticQuantity<TSelf, TUnit>` combine:
  - `IQuantity<TSelf, TUnit>`
  - `IArithmeticQuantity<TSelf>`
- Introduce `ILinearQuantity<TSelf, TUnit>` as the strongly typed linear
quantity contract.
- Reuse `IArithmeticQuantity<TSelf>` from both linear and logarithmic
quantities.
- Move affine addition/subtraction contracts to the unit-independent
  `IAffineQuantity<TSelf, TOffset>` interface.
- Generate linear quantities as `ILinearQuantity<TSelf, TUnit>`.
- Simplify `Sum()` and `Average()` constraints to:

```csharp
where TQuantity : ILinearQuantity<TQuantity, TUnit>
```

- Regenerate affected quantity declarations.

## Result

Generic APIs can express the intended quantity capability with one
constraint:

```csharp
static TQuantity Sum<TQuantity, TUnit>(
    IEnumerable<TQuantity> quantities,
    TUnit unit)
    where TQuantity : ILinearQuantity<TQuantity, TUnit>
    where TUnit : struct, Enum
{
    return quantities.Sum(unit);
}
```

The hierarchy also gives linear and logarithmic quantities a common
arithmetic capability without
requiring a unit enum when the generic algorithm does not use units.

This is intended as an interface-organization change, not a behavior
change. Existing arithmetic
interfaces remain transitively implemented by the generated quantity
types.

## Compatibility considerations

This changes the declared interface hierarchy of generated quantity
structs, so the public API and
binary compatibility implications should be reviewed carefully even
though assignability through
the existing base interfaces is preserved.

---------

Co-authored-by: Andreas Gullberg Larsen <andreas.larsen84@gmail.com>
> [!NOTE]
> While v6 is still prerelease, we may instead move the complete
`ToUnit` API—including the base
> member and typed overloads—to extension methods. Moving them together
avoids member shadowing and
> further reduces the burden on custom quantities.

## Motivation

`IQuantity<TSelf, TUnitType>` preserves the concrete quantity type for
static factories and generic
quantity metadata, but inherited `ToUnit(TUnitType)` currently returns
the weaker
`IQuantity<TUnitType>` contract. Generic callers therefore lose `TSelf`
and must cast or reconstruct
the concrete quantity after a unit conversion.

For example, this cannot currently return `TQuantity` without a cast:

```csharp
static TQuantity ConvertToUnit<TQuantity, TUnit>(TQuantity quantity, TUnit unit)
    where TQuantity : IQuantity<TQuantity, TUnit>
    where TUnit : struct, Enum
{
    return quantity.ToUnit(unit);
}
```

## Changes

- Add a default, self-typed `ToUnit(TUnitType)` member to
`IQuantity<TSelf, TUnitType>` on modern .NET
  targets.
- Implement it through the existing `TSelf.From()` and `As()` contracts.
- Delegate the inherited `IQuantity<TUnitType>.ToUnit()` implementation
to the strongly typed member.
- Add a generic test demonstrating that conversion returns `Length`
directly without boxing or an
  explicit cast.

The default implementation means existing third-party quantity
implementations do not need to add a
new member. The `netstandard2.0` contract is unchanged, following the
interface's existing
modern-target pattern for static abstract members.

## Validation

- `dotnet build UnitsNet/UnitsNet.csproj --no-restore --verbosity
minimal`
  - Builds `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0`.
- `dotnet test UnitsNet.Tests/UnitsNet.Tests.csproj --no-build
--verbosity minimal`
  - 42,703 passed and 20 existing skips on each of .NET 8, 9, and 10.
Add new quantity for area per unit length (m²/m), commonly used in
structural engineering for distributed reinforcement. Includes metric
(cm²/m, mm²/m) and imperial (in²/ft, in²/in, ft²/ft) units with verified
conversion factors.

In structural engineering, reinforcement is commonly specified as an
area distributed over a length — for example, mm²/m or in²/ft. This is
the standard way to express required steel reinforcement in concrete
structures.

My specific application is designing cylindrical prestressed concrete
tanks (per AWWA D110 / ACI 372), where reinforcement requirements are
calculated as area of steel per unit height or per unit circumference
(e.g., in²/ft of tank wall).

---------

Co-authored-by: Andreas Gullberg Larsen <andreas.larsen84@gmail.com>
Motivation:
.NET Framework cannot use generic Enum.GetValues<T>(), preventing CLR4 test compilation.

Changes:
Use EnumHelper in generated and custom enum tests.
Motivation:
Exception messages differ between CLR implementations.

Changes:
Assert ArgumentNullException.ParamName instead of message text.
Motivation:
The build scripts assumed Windows paths and executable names.

Changes:
Use platform-neutral paths and tools, and build net48 benchmarks only on Windows.
Motivation:
The Math.Pow fallback returns NaN for negative values even when an odd root is real.

Changes:
Move the fallback to reusable MathHelper code, use it on netstandard, and test it directly.
Motivation:
Modern static interface members are unavailable on CLR4, and the custom fixture missed its netstandard contract.

Changes:
Guard modern-only tests and implement the required HowMuch instance metadata property.
angularsen and others added 30 commits July 31, 2026 00:43
## Motivation

The version bump scripts only updated a subset of the packages they
release. In particular, `UnitsNet.NumberExtensions.CS14` and
`UnitsNet.Serialization.SystemTextJson` could be left on the previous
version. UnitsNet.Modular also had no equivalent bump command for its
MinVer tag stream.

## Changes

- Update the UnitsNet script to version UnitsNet and both
NumberExtensions packages.
- Update the JSON script to version both serialization packages and
preserve unrelated working changes.
- Add a MinVer-aware UnitsNet.Modular script for minor, patch, and
prerelease suffix tags.
- Document the Modular release command and update the wrapper
descriptions.

## Validation

- Parsed the PowerShell scripts in Windows PowerShell and PowerShell 7.
- Checked the shell wrappers with Git Bash.
- Ran both explicit-version scripts in an isolated clone and verified
all five project versions, commits, and annotated tags.
- Verified the Modular tag flow and MinVer version resolution in an
isolated clone.
- Ran `git diff --check`.
## Motivation
Fixes #1654.

Several quantity combinations are already representable in UnitsNet, but
were missing generated quantity-derivation operators. This means
consumers had to drop down to numeric values or write the relation
manually even though both sides are typed quantities.

## Changes
- Add the missing relations from #1654 to `UnitRelations.json` and
regenerate the quantity code.
- Add relation tests for multiplication and inverse division where those
operators are generated.
- Use `NoInferredDivision` for `VolumePerLength * Length` and
`VolumeFlowPerArea * Area`, since inferred `Volume / Length` and
`VolumeFlow / Area` divisions would be ambiguous with existing
relations.

## Validation
- `generate-code.bat`
- `dotnet build UnitsNet.Tests\UnitsNet.Tests.csproj --no-restore`
- `dotnet test UnitsNet.Tests\UnitsNet.Tests.csproj --no-build --filter
"FullyQualifiedName~ElectricPotentialChangeRateTests|FullyQualifiedName~ForceTests|FullyQualifiedName~RotationalAccelerationTests|FullyQualifiedName~IrradianceTests|FullyQualifiedName~ElectricCurrentDensityTests|FullyQualifiedName~ElectricSurfaceChargeDensityTests|FullyQualifiedName~ElectricChargeDensityTests|FullyQualifiedName~HeatTransferCoefficientTests|FullyQualifiedName~ThermalResistanceTests|FullyQualifiedName~ThermalInsulanceTests|FullyQualifiedName~LinearPowerDensityTests|FullyQualifiedName~PowerDensityTests|FullyQualifiedName~VolumePerLengthTests|FullyQualifiedName~VolumeFlowPerAreaTests|FullyQualifiedName~RatioChangeRateTests|FullyQualifiedName~CompressibilityTests|FullyQualifiedName~VolumetricHeatCapacityTests|FullyQualifiedName~LuminousIntensityTests|FullyQualifiedName~MagneticFieldTests|FullyQualifiedName~ElectricCapacitanceTests|FullyQualifiedName~ElectricFieldTests|FullyQualifiedName~ElectricPotentialTests|FullyQualifiedName~FluidResistanceTests|FullyQualifiedName~PressureTests|FullyQualifiedName~AbsorbedDoseOfIonizingRadiationTests|FullyQualifiedName~ElectricApparentPowerTests|FullyQualifiedName~ElectricReactivePowerTests"`
## Motivation

PR #1544 left the samples with a development-only package override for
normal builds, while only the `Official` configuration used the
published NuGet package. That override also pointed to the accidental
`pre103` version.

The samples should always exercise the package users install.

## Changes

- Reference `UnitsNet 6.0.0-pre021` centrally for all samples.
- Remove the configuration-specific package overrides.
- Update the custom quantity sample to implement `ILinearQuantity`,
matching the current v6 aggregation APIs.

## Validation

- Restored the sample solution with an empty-cache package resolution.
- Built `Samples/Samples.slnx` in Release successfully.
- Verified that no development `VersionOverride` or conditional UnitsNet
package references remain.
- Ran `git diff --check`.
## Motivation

The build scripts should keep the actual test project list easy to see
and avoid project discovery or MSBuild evaluation on every build. The
net48 compatibility workflow runs the same main test projects on .NET
Framework, so it should not duplicate the project names or import the
full build-functions module just to get the list.

## Changes

- move the hard-coded main test project list to
`Build/test-projects.psm1`
- expose the list through `Get-TestProjectPaths`
- call `Get-TestProjectPaths` explicitly from
`Build/build-functions.psm1` and the net48 compatibility workflow
- merge the branch onto latest `master`

## Validation

- `powershell -NoProfile -ExecutionPolicy Bypass -Command 'Import-Module
./Build/test-projects.psm1 -Force; @(Get-TestProjectPaths) |
ForEach-Object { $_ }'`
- `pwsh -NoProfile -Command 'Import-Module ./Build/test-projects.psm1
-Force; @(Get-TestProjectPaths) | ForEach-Object { $_ }'`
- `powershell -NoProfile -ExecutionPolicy Bypass -Command 'Import-Module
./Build/build-functions.psm1 -Force'`
- `pwsh -NoProfile -Command 'Import-Module ./Build/build-functions.psm1
-Force'`
- `git diff --check -- Build/test-projects.psm1
Build/build-functions.psm1 .github/workflows/net48-compatibility.yml`
## Motivation

Generated quantities duplicated descriptive metadata across the quantity
type, value instances, and registry descriptors. The prototype also
introduced a separate `UnitsNet.Core` project as a possible contract
boundary with legacy UnitsNet, but the two models currently have
deliberately different contracts and Core has no independent consumer or
versioning boundary.

This PR makes static `Quantity.Info` the canonical metadata object and
keeps the clean quantity contracts and runtime in the consumer-facing
`UnitsNet.Modular` package. It supersedes #1713 without introducing a
second package that would immediately need to be removed.

## Changes

- make each generated quantity's static `Info` object the canonical home
for identity, base-unit metadata, unit metadata, and base dimensions
- standardize on `QuantityInfo.BaseUnit`, `QuantityInfo.Units`, and
`UnitInfo.Value`
- reuse the same `Info` instance through the generated discovery
registry instead of constructing a parallel descriptor graph
- define slim self-typed quantity and arithmetic capability contracts
with reusable conversion and aggregation behavior
- move the contracts, immutable metadata, conversion helpers, and
quantity math into the `UnitsNet.Modular` project and namespace
- remove the standalone `UnitsNet.Core` project, package, dependency,
and two-package release plumbing
- preserve strong naming and the stable `6.0.0.0` assembly version on
the unified runtime
- hide source-compatibility aliases and generator-infrastructure APIs
from IntelliSense
- update generated code, samples, migration guidance, architecture
documentation, CI, and local package automation for the single-package
model

## Consumer impact

Consumers install only `UnitsNet.Modular`. The package contains the
runtime, contracts, metadata types, and bundled source generator. This
intentionally does not preserve the unpublished alpha `UnitsNet.Core.*`
or earlier Modular metadata surface. Ordinary strongly typed
construction, conversion, parsing, formatting, arithmetic, and unit APIs
remain source compatible where the selected catalog contains the
required quantities and units.

The main `UnitsNet` project and its existing interfaces remain
unchanged. Extracting a shared contracts package is deferred until a
separate legacy/modular investigation demonstrates a genuinely useful
common boundary.

## Validation

- `dotnet restore UnitsNet.Modular/UnitsNet.Modular.slnx
--force-evaluate
-p:UnitsNetModularSampleUpdateLocalPackagesOnBuild=true`
- `dotnet build UnitsNet.Modular/UnitsNet.Modular.slnx --no-restore -m:1
-p:UnitsNetModularSampleUpdateLocalPackagesOnBuild=false`
- `dotnet test UnitsNet.Modular/UnitsNet.Modular.slnx --no-build -m:1
-p:UnitsNetModularSampleUpdateLocalPackagesOnBuild=false`
- 43 compatibility tests passed
- 31 generator tests passed
- 37 Modular runtime/generated quantity tests passed
- the Codespaces playground and all sample projects build
- packed `UnitsNet.Modular` successfully for .NET 8, 9, and 10 and
verified that its nuspec has no `UnitsNet.Core` dependency
## Motivation

Make the first UnitsNet.Modular experience discoverable from
IntelliSense, compiler diagnostics, documentation, and a package-based
sample that behaves like a real consumer.

## Changes

- expand the authoring API XML documentation with complete module,
unit-set, custom-spec, and profile examples
- link every `UNM` diagnostic to the relevant README section and verify
that coverage in a generator test
- replace the quick start with two copy-pasteable files, explain
generated ownership and relationships, and add troubleshooting guidance
plus a scenario-oriented sample index
- add an isolated NuGet getting-started sample that exactly matches the
documented quick start
- run both package-facing consumer scenarios in the separate
UnitsNet.Modular workflow

## Validation

- `dotnet test UnitsNet.Modular/UnitsNet.Modular.slnx --no-restore -m:1
-p:UnitsNetModularSampleUpdateLocalPackagesOnBuild=false` (115 tests
passed; runtime targets net8.0, net9.0, and net10.0)
- `pwsh
UnitsNet.Modular/Samples/UnitsNet.Modular.GettingStarted.Sample/run.ps1`
- verified generated XML documentation contains the examples and links
- verified all diagnostic anchors exist and all relative Markdown links
resolve
## Motivation

UnitsNet.Modular forwards quantity format strings such as `F1` to the
stored numeric value, but its public APIs do not currently identify
those parameters as numeric format strings to IDE tooling.

## Changes

- annotate generated quantity `ToString(string)` overloads with
`StringSyntaxAttribute.NumericFormat`
- annotate the type-erased `IQuantityDescriptor.Format` format parameter
- verify the annotations are present in emitted consumer-visible
metadata

This improves validation and completion for direct format-string
arguments where supported by the IDE. Interpolation clauses such as
`$"{speed:F1}"` continue to work, but their completion behavior remains
controlled by the IDE's interpolated-string support.

## Validation

- `dotnet test UnitsNet.Modular/UnitsNet.Modular.slnx --no-restore -m:1
-p:UnitsNetModularSampleUpdateLocalPackagesOnBuild=false`
- 115 tests passed
- Modular runtime built for .NET 8, .NET 9, and .NET 10
## Motivation

#1313 reported that custom conversion functions could not override
built-in same-quantity conversions in v5. In v6, the supported model is
to customize the quantity unit definitions before the converter is
built, instead of adding runtime override precedence for built-in
conversions.

## Changes

- Document how to override built-in unit conversion factors through
`UnitsNetSetup.ConfigureDefaults()` or an isolated custom
`UnitConverter`.
- Add regression coverage for the original
`PressureUnit.InchOfWaterColumn` scenario, verifying that
`UnitConverter`, `As(..., converter)`, and `ToUnit(..., converter)` all
use the configured conversion factor.

## Validation

- `dotnet test UnitsNet.Tests\UnitsNet.Tests.csproj --filter
"FullyQualifiedName~UnitConverterTest.ConvertValue_WithCustomBuiltInUnitDefinition_UsesConfiguredConversion|FullyQualifiedName~UnitConverterTest.GetConversionFunction_WithCustomUnitConversion|FullyQualifiedName~UnitConverterTest.TryGetConversionFunction_WithCustomUnitConversion"`
## Motivation

CodeGen currently relies on default file encodings from
`File.WriteAllText`, `File.CreateText`, `StreamWriter`, and related read
helpers. Those defaults can differ by runtime/API history and make
generated output encoding/BOM behavior less obvious than it should be.

We want generated output to be stable across platforms and avoid review
noise from UTF-8 BOM differences.

## Changes

- Add a small `CodeGenFile` helper that reads text as UTF-8 and detects
an existing BOM, but writes generated/codegen-normalized files as
explicit UTF-8 without BOM.
- Use the helper for generated C# files, generated resource text files,
normalized relation JSON, unit enum allocation JSON, quantity JSON
reads, and codegen edit helpers.

## Validation

- `dotnet build CodeGen\CodeGen.csproj`
- `generate-code.bat`
- Scanned generated/codegen-normalized outputs and found no UTF-8 BOM.
- Running code generation after the change produced no generated-file
diffs.
## Summary

- Set the repository-wide EditorConfig charset to UTF-8 without BOM.
- Remove the existing UTF-8 BOM from all 536 tracked files that
contained one.
- Keep file contents and line endings otherwise unchanged.

## Motivation

UTF-8 has no byte-order ambiguity, so its BOM only serves as an encoding
signature. Unicode permits but does not require it, and W3C recommends
avoiding it unless a compatibility requirement exists because it can
interfere with tools or formats that expect content at byte zero.

This also aligns authored files with the explicit UTF-8-without-BOM
behavior recently adopted by CodeGen and prevents future encoding-only
review noise.

References:

- https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-2/
- https://www.w3.org/International/questions/qa-byte-order-mark.en.php

## Impact

This is an encoding-only normalization. No source, project, resource,
documentation, or unit-definition content changes.

## Validation

- Verified all 537 changed-file diffs: 536 are BOM-only removals and one
is the EditorConfig policy change.
- Scanned all tracked files and found zero remaining UTF-8 BOMs.
- `generate-code.bat`
- `dotnet restore UnitsNet.slnx`
- `dotnet build UnitsNet.slnx --no-restore` — succeeded with 18 existing
obsolete-API warnings and 0 errors.
- `git diff --check`
## Summary

- reduce the UnitsNet.Modular sample portfolio to six purpose-focused
scenarios with concise names
- replace `ConsumerOwned` with a documented, solution-grouped
`SharedUnitsLibrarySample`
- let every sample switch between `ProjectReferences`, `LocalPackages`,
and `PublishedPackages` through the IDE solution-platform selector
- move the runnable compatibility pair into test fixtures and update
documentation, CI, VS Code, and devcontainer paths

## Motivation

The previous samples mixed dependency sources into separate projects,
used long or ambiguous names, and contained overlapping scenarios. This
made it difficult to tell why each sample existed or to compare the same
sample against source, locally packed, and published dependencies.

The new solution platforms keep `Debug` and `Release` as build
configurations while treating the dependency source as the
IDE-selectable platform. Local package preparation runs once at solution
level to avoid parallel pack races.

## Impact

In Rider or Visual Studio, contributors can select combinations such as
`Debug | LocalPackages` for the complete sample portfolio. Direct sample
builds continue to default to project references. Outputs and restore
state are isolated by dependency platform.

## Validation

- `dotnet build UnitsNet.Modular.slnx --configuration Release
-p:Platform=ProjectReferences`
- `dotnet build UnitsNet.Modular.slnx --configuration Release
-p:Platform=LocalPackages`
- `dotnet build UnitsNet.Modular.slnx --configuration Release
-p:Platform=PublishedPackages`
- `dotnet test UnitsNet.Modular.slnx --configuration Release
-p:Platform=ProjectReferences` — 116 tests passed
- local-package custom and shared-units samples executed successfully
- published-package getting-started sample executed successfully
- `git diff --check`
## Motivation

UnitsNet v6 has already removed the proprietary `U`, `V`, and `Q`
quantity format strings. This completes that cleanup by removing the
remaining `A` and `S` formats, so quantity format parameters
consistently accept .NET numeric format strings and can be described
accurately with `StringSyntaxAttribute.NumericFormat`.

The historical rationale supports the cleanup:

- `A` only returns unit abbreviations, which are already available
explicitly through generated `GetAbbreviation()` APIs and the
configurable `UnitAbbreviationsCache`.
- #797 intended to remove `S`, and commit 721287d removed its
documentation and tests, but the formatter implementation remained in
v5. This PR completes that cleanup. The discussion in #1450 also noted
that `S` can hide precision and that callers should choose an explicit
numeric format.

## Changes

- Remove `A`/`An` and `S`/`Sn` formatting from `QuantityFormatter` and
`QuantityValue`.
- Throw focused `FormatException` messages for those formerly recognized
formats with migration guidance.
- Keep standard and custom .NET numeric formats, such as `G3`, `F2`,
`N2`, `E2`, and `0.##`.
- Annotate generated quantity formatting, `QuantityFormatter`,
`QuantityValue`, quantity extensions, and type-converter formatting with
`StringSyntaxAttribute.NumericFormat`.
- Add an internal `StringSyntaxAttribute` compatibility definition for
the `netstandard2.0` target.
- Regenerate quantities and tests, and update the string-formatting
guide and v6 upgrade guide.

## Migration

| Removed | Replacement |
|---|---|
| `A`, `A0`, `A1`, ... | `Length.GetAbbreviation(unit)` or
`UnitAbbreviationsCache.GetUnitAbbreviations(unit)` |
| `S`, `S2`, ... | An explicit standard/custom numeric format such as
`G3`, `F2`, `N2`, `E2`, or `0.##` |
| `U` | `quantity.Unit` |
| `V` | `quantity.Value` |
| `Q` | Static metadata such as `Length.Info.Name` |

Currency (`C`) and percent (`P`) formats remain intentionally
unsupported for physical quantities.

Refs #1200.

## Validation

- `generate-code.bat`
- `dotnet build UnitsNet/UnitsNet.csproj --no-restore`
(`netstandard2.0`, `net8.0`, `net9.0`, `net10.0`)
- `dotnet test UnitsNet.Tests --no-restore -f net10.0` (52,506 passed,
16 skipped)
- `dotnet test UnitsNet.Tests --no-restore -f net48` (45,321 passed, 16
skipped)
## Summary 
Add three `VolumeFlow` units commonly used in machining and
material-removal workflows:

1. Cubic inches per second (`in³/s`) 
2. Cubic inches per minute (`in³/min`) 
3. Cubic millimeters per minute (`mm³/min`) 

The generated API, enum values, localization resources, numeric
extensions, and conversion tests have been updated.
## Motivation
In subtractive manufacturing (like milling and lathework) and in
additive manufacturing (like 3D printing), material rates are written in
these alternate formats. A web search for "material removal rate" or
"extrusion rate" will confirm the ubiquity of these unit types in
manufacturing. Rates for mm^3/min will be used to describe things like
the performance of a drill bit (or similar cutting tool).

## Testing 
- Ran `build.bat` successfully.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.