You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Proposal: generate custom quantities against the UnitsNet package #1745
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 .resx file 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:
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 .restext files embedded in the application's assembly, passed to QuantityInfo as a ResourceManager. The HowMuch sample already does this.
Registration: a generated extension, such as UnitsNetSetup.ConfigureDefaults(b => b.WithMyQuantities()), wrapping WithAdditionalQuantities.
Relations with built-in quantities: the package ships the built-in JSON so CodeGen can generate operators like HowMuch * Length = Mass on 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.
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.
Problem
Adding a quantity of your own on top of the
UnitsNetpackage is possible, but it's a lot of hand-written code. TheHowMuchsample (Samples/UnitsNetSetup.Configuration/ConfigureWithCustomQuantities.cs) is about 250 lines:ILinearQuantity<,>;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
UnitsNetrather than working with it: the two can't be referenced together (UNM016). An application that usesUnitsNettoday can't add one quantity this way.Finding: generated code is close to working outside UnitsNet
I copied generated quantity code (
TurbidityandLength, renamed) into a separate project that references the builtUnitsNet.dll, and compiled it. The only errors come from 7 internal APIs that generated code calls:new QuantityValue(numerator, denominator)QuantityValue.FromTerms, or make the constructor publicQuantityValue.PowerOfTenQuantityValue.FromPowerOfTenComparison.GetHashCode(Type, QuantityValue)GetHashCodeHashCode.CombineUnitParser.Parse/TryParseoverloads taking aQuantityInfoParseUnitQuantityInfo.GetDefaultUnit(UnitSystem)UnitSystemExceptionHelper.CreateArgumentExceptionCompareTo(object)UnitsNetSetup.CreateQuantityInfoEverything else that generated code uses, including
QuantityInfo,UnitDefinition,UnitParser.DefaultandQuantityFormatter, is already public.Proposal
Let applications generate their own quantities with the CodeGen that generates the built-in ones, as part of their build:
The JSON uses the same schema as
Common/UnitDefinitions. On build, CodeGen generates only those quantities intoobj/, 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:
.restextfiles embedded in the application's assembly, passed toQuantityInfoas aResourceManager. TheHowMuchsample already does this.UnitsNetSetup.ConfigureDefaults(b => b.WithMyQuantities()), wrappingWithAdditionalQuantities.HowMuch * Length = Masson the custom type. Operators between two built-in quantities can't be added this way.Common/UnitEnumValues.g.json, so values stay stable as units are added.Compared with UnitsNet.Modular
UnitsNetpackageQuantityValue,UnitConverterdoublevaluesQuestion: is Modular the planned direction for v7? If so, it may be better to let Modular's generator target the
UnitsNetpackage, rather than adding a second generator path. I'd like your view before going further.Possible steps
Each is additive and independently useful:
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
https://claude.ai/code/session_012XKhDsyHDc5BHrmibxScqG