Skip to content

Generate units as UnitOf<TQuantity>, such as LengthUnits.Meter - #1741

Draft
tmilnthorp wants to merge 9 commits into
angularsen:masterfrom
tmilnthorp:tmilntho/builtin-unitof
Draft

tmilnthorp wants to merge 9 commits into
angularsen:masterfrom
tmilnthorp:tmilntho/builtin-unitof

Conversation

@tmilnthorp

Copy link
Copy Markdown
Collaborator

Step 1 of the proposal in #1740: generate every unit as a UnitOf<TQuantity>, such as LengthUnits.Meter, next to the unit enums. Opened as a draft until the direction in #1740 is agreed.

Length length = Length.FromMeters(3);
length.As(LengthUnits.Foot);            // same as length.Feet
Length.Info.From(2, LengthUnits.Foot);  // 2 ft

var furlong = new UnitOf<Length>("Furlong", "Furlongs", 201.168);
length.As(furlong);
  • One static class per quantity (LengthUnits), with a static readonly UnitOf<Length> field per unit holding its names, base units and exact conversions. UnitOf itself knows nothing about enums or UnitKey.
  • Default mappings built from these: new (LengthUnit.Foot, LengthUnits.Foot), so each unit's data is generated in one place.
  • As/From look built-in units up in the quantity's QuantityInfo, so they convert exactly like their enum values, configured conversions included, and From keeps the quantity in that unit. Custom units still use their own conversions.
  • UnitOf<TQuantity> derives from a new non-generic UnitOf. It holds the data, so the code isn't compiled again for each quantity. It's also the type a future IQuantity.Unit would return.

Purely additive. It builds on #1737 and #1738, which should be merged first.

net10.0, Release master this PR
UnitsNet.dll 1,885,696 B 1,750,528 B
First ToString(), cold process 381 ms 360 ms
All quantity infos, cold process 355 ms, 1,564 KB allocated 345 ms, 1,894 KB allocated

Against #1737 alone, the generated units cost about 59 KB, plus about 13 ms and 436 KB of allocations when every quantity is created.

Part of #1740.

🤖 Generated with Claude Code

https://claude.ai/code/session_012XKhDsyHDc5BHrmibxScqG

tmilnthorp and others added 9 commits October 6, 2026 11:21
Each yield-return method compiles to a hidden iterator class. Returning an
array removes 129 of those classes (-194 KB in the net10.0 DLL) and lets
QuantityInfo size its unit array up front. The return type is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012XKhDsyHDc5BHrmibxScqG
UnitOf<TQuantity> describes a unit of a quantity by its names, base units and
conversions, without a unit enum value. Quantities can be converted to it with
As(UnitOf<TQuantity>), and created from a value in it with
QuantityInfo.From(value, UnitOf<TQuantity>), which returns the quantity in its
base unit. Since the unit is tied to its quantity, using it with another
quantity doesn't compile.

This makes it possible to use units that UnitsNet doesn't define, without a
unit enum value or any setup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012XKhDsyHDc5BHrmibxScqG
Base units are made of unit enum values, which a unit that UnitsNet doesn't
define has none of, and nothing reads them yet. UnitOf still implements
IUnitDefinition, with BaseUnits.Undefined as an explicit implementation.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012XKhDsyHDc5BHrmibxScqG
The constructor took the conversion from the base unit, so a furlong needed the
fraction 1000/201168. Taking the value of one unit in the base unit instead
lets it be written as 201.168, as units are usually described.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012XKhDsyHDc5BHrmibxScqG
A unit that UnitsNet doesn't define can still be made of the base units it does,
such as feet per day of speed. New constructor overloads take its base units,
which UnitOf now exposes; the others leave them undefined.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012XKhDsyHDc5BHrmibxScqG
Each quantity gets a static class of its units, such as LengthUnits.Meter, as
UnitOf instances with their names, base units and conversions. The default unit
mappings are created from them, so the data is generated once. Quantities can be
converted to and from them with As and QuantityInfo.From like custom units, and
a built-in unit converts through its UnitInfo, respecting the configuration.

UnitOf<TQuantity> now derives from a non-generic UnitOf, which holds its data,
so its code isn't compiled again for each quantity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012XKhDsyHDc5BHrmibxScqG
@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98%. Comparing base (3bf1ef4) to head (6eff3d9).

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #1741    +/-   ##
=======================================
- Coverage      98%     98%    -1%     
=======================================
  Files         515     645   +130     
  Lines       24092   24137    +45     
=======================================
+ Hits        23692   23736    +44     
- Misses        400     401     +1     
Flag Coverage Δ
net48 97% <100%> (+<1%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant