Repository navigation
Proposal: generate custom quantities against the UnitsNet package #1745
Description
Problem
Adding a quantity of your own on top of the UnitsNet package is possible, but it's a lot of hand-written code. The HowMuch sample (Samples/UnitsNetSetup.Configuration/ConfigureWithCustomQuantities.cs) is about 250 lines:
- a unit enum and a struct implementing
ILinearQuantity<,>; - hand-written equality, comparison, parsing and formatting;
- a
QuantityInfo, plus a.resxfile for abbreviations.
It still lacks the generated API that built-in quantities have, such as HowMuch.FromSomes(1), howMuch.Somes, and operators with built-in quantities. The docs call this approach "highly experimental and incomplete".
UnitsNet.Modular generates custom quantities from JSON, with the same API as built-in ones. But it replaces UnitsNet rather than working with it: the two can't be referenced together (UNM016). An application that uses UnitsNet today can't add one quantity this way.
Finding: generated code is close to working outside UnitsNet
I copied generated quantity code (Turbidity and Length, renamed) into a separate project that references the built UnitsNet.dll, and compiled it. The only errors come from 7 internal APIs that generated code calls:
| Internal API | Used for | Public alternative |
|---|---|---|
new QuantityValue(numerator, denominator) |
Exact conversion factors | QuantityValue.FromTerms, or make the constructor public |
QuantityValue.PowerOfTen |
Large factors | QuantityValue.FromPowerOfTen |
Comparison.GetHashCode(Type, QuantityValue) |
GetHashCode |
Make public, or generate HashCode.Combine |
UnitParser.Parse/TryParse overloads taking a QuantityInfo |
ParseUnit |
Make public |
QuantityInfo.GetDefaultUnit(UnitSystem) |
Constructor taking a UnitSystem |
Make public |
ExceptionHelper.CreateArgumentException |
CompareTo(object) |
Generate the exception inline |
UnitsNetSetup.CreateQuantityInfo |
Configuring built-in quantities | Not needed; an external quantity registers through setup instead |
Everything else that generated code uses, including QuantityInfo, UnitDefinition, UnitParser.Default and QuantityFormatter, is already public.
Proposal
Let applications generate their own quantities with the CodeGen that generates the built-in ones, as part of their build:
<!-- MyApp.csproj, which references UnitsNet -->
<ItemGroup>
<PackageReference Include="UnitsNet.CodeGen" PrivateAssets="all" />
<UnitsNetDefinition Include="Units/*.json" />
</ItemGroup>The JSON uses the same schema as Common/UnitDefinitions. On build, CodeGen generates only those quantities into obj/, in the same shape as the built-in ones: From<Unit> methods, unit properties, Parse/ToString, and relations. They compile into the application's own assembly.
This builds on #1742, which runs CodeGen from MSBuild during the repo's own build. The package would contain CodeGen and an MSBuild targets file that runs it on the application's definitions. What's needed:
- CodeGen options: input and output paths, a target namespace, and an "external" mode that generates only the given definitions using public APIs.
- Abbreviations: generated
.restextfiles embedded in the application's assembly, passed toQuantityInfoas aResourceManager. TheHowMuchsample already does this. - Registration: a generated extension, such as
UnitsNetSetup.ConfigureDefaults(b => b.WithMyQuantities()), wrappingWithAdditionalQuantities. - Relations with built-in quantities: the package ships the built-in JSON so CodeGen can generate operators like
HowMuch * Length = Masson the custom type. Operators between two built-in quantities can't be added this way. - Unit enum values: each application keeps its own enum value file next to its JSON, like
Common/UnitEnumValues.g.json, so values stay stable as units are added.
Compared with UnitsNet.Modular
| This proposal | UnitsNet.Modular | |
|---|---|---|
Works with the UnitsNet package |
Yes | No, it replaces it |
Runtime setup, QuantityValue, UnitConverter |
All existing features | Compile-time only, double values |
| Selecting a subset of built-in quantities | No | Yes |
| Integration | MSBuild step in the application's build | Source generator |
| Type identity | Generated types belong to the generating assembly | Same |
Question: is Modular the planned direction for v7? If so, it may be better to let Modular's generator target the UnitsNet package, rather than adding a second generator path. I'd like your view before going further.
Possible steps
Each is additive and independently useful:
- Make the internal helpers above public, or generate public alternatives. This also helps hand-written custom quantities today.
- Add CodeGen options for paths, namespace and external mode.
- Package CodeGen with the MSBuild targets.
- Later, units as objects. If Proposal: units as objects in v6 (UnitOf<TQuantity> instead of unit enums) #1740 lands, generated custom quantities use
UnitOf<TQuantity>units instead of an enum, so there are no enum values to coordinate between packages.
Related: #1742 (CodeGen runs in the build), #1740 (units as objects), #902 (source generators), #1181 (one package per quantity), #1728 (feedback on a custom quantity built with Modular), UnitsNet.Modular.
🤖 Generated with Claude Code
Activity