Repository navigation
Proposal: units as objects in v6 (UnitOf<TQuantity> instead of unit enums) #1740
Copy link
Copy link
Open
Description
Why
Units are enums today, which makes them closed:
- A custom unit is a magic number,
(LengthUnit)1000. It has no name:ToString()gives "1000" and JSON gets 1000. Nothing reserves a range, so it can collide with a future built-in value. - Adding a unit to an existing quantity is undocumented and awkward (How to add a unit to an existing quantity? #1246, Make Custom Unit Extension Work Like Built-In Units #1562).
UnitKeyexists largely to work around the boxing that enums cause.
#1738 introduced UnitOf<TQuantity>, a unit described by its names and conversions and tied to its quantity. This proposal makes UnitOf<TQuantity> the unit type in v6.
Where it ends up
- Built-in units are
UnitOfobjects,LengthUnits.Meter, replacingLengthUnit.Meter. - Quantities store their unit as a
UnitOf:Length.Unitis aUnitOf<Length>. - Custom units are first-class: registered once, then converted, parsed, formatted and stored like built-in units, with real names.
UnitKeygoes away, because the unit object itself is the identity.- Serialization uses unit names, and old numeric values stay readable for one major version.
Why LengthUnits.Meter, not Length.Meter: 80 units in 24 quantities have identical singular and plural names, so a static Frequency.Hertz would collide with the existing value property frequency.Hertz.
Steps
Each is a small PR. Only the last one is breaking.
- Generate every unit as a
UnitOf(additive), e.g.LengthUnits.Meter, next to the enums. The default unit mappings are built from these, so each unit's data is generated in one place. Draft: Generate units as UnitOf<TQuantity>, such as LengthUnits.Meter #1741. - Unit-agnostic tests (no product change): tests pass units by name instead of enum constants in
[InlineData],switchand int casts. - Register custom units with a setup (additive): parsing and formatting by abbreviation.
- The switch (breaking): quantities store
UnitOf, the enums are marked obsolete, andUnitKeyis removed.
Generated quantities then also implementIQuantityOfType.Create/Infoexplicitly: in a prototype, class-based units combined with those static default interface implementations made the runtime's type loader need more than 2 MB of stack to load one quantity.
Upgrade path
If this direction is accepted, I'd add a v5 bridge:
LengthUnits.Meterand friends shipped next to the enums in a v5 minor;- an analyzer whose code fix rewrites
LengthUnit.XtoLengthUnits.X.
Code migrated on v5 would then compile unchanged on v6. #1739 (UnitOf for v5) is the first piece of that bridge.
Feedback wanted
- The direction: units as objects, replacing the enums in v6.
- The name:
LengthUnits.Meter. - Timing: should step 4 land in v6 (v6 Release #1200), or wait for v7?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
tmilnthorp commentedon Oct 7, 2026
Related: #1745 proposes generating custom quantities against the
UnitsNetpackage, using the same CodeGen as the built-in quantities.The two proposals reinforce each other. With units as objects, generated custom quantities wouldn't need a unit enum. That removes the problem of coordinating enum values between independently built packages, and serialization can use unit names.
🤖 Generated with Claude Code
https://claude.ai/code/session_012XKhDsyHDc5BHrmibxScqG