Speed<T> gets FromMeterPerSecond, FromKilometerPerHour, FromMilePerHour, FromFootPerSecond, FromKnot — one factory per entry in the dimension's availableUnits. Velocity3D<T> gets none. The only way to build one is:
Velocity3D<double> v = new() { X = 7660.0, Y = 0.0, Z = 0.0 }; // m/s, and you had better remember that
The same is true of Position3D, Displacement3D, Force3D and the other 34 vector types. The library's premise is that the unit lives in the type instead of in the caller's head, and the vector forms opt out of it. In a domain that works natively in kilometres — orbital mechanics, which is what surfaced this — every construction site carries a hand-written ×1000, which is precisely the class of error the library exists to prevent.
Design
Emit, per vector form, one factory per unit in the dimension's availableUnits, taking one T per component:
/// <summary>Creates a Position3D from components in kilometers.</summary>
public static Position3D<T> FromKilometer(T x, T y, T z) => new()
{
X = x * Units.ConversionConstants.Values<T>.KilometerToMeters,
Y = y * Units.ConversionConstants.Values<T>.KilometerToMeters,
Z = z * Units.ConversionConstants.Values<T>.KilometerToMeters,
};
Reading the same Values<T> holder the scalar factories already use, so a decimal or PreciseNumber vector converts at its own precision rather than through double — the 5.2.0 behaviour, extended to the vectors it skipped.
Naming follows the existing mechanical rule: the unit's singular-lemma name from units.json, verbatim. FromKilometer(x, y) for V2, FromKilometer(x, y, z, w) for V4.
Four things to settle in review
1. No Vector0Guards. Vector components are signed by construction. The non-negativity rule belongs to the V0 form and must not leak into the vectors — a Position3D with a negative X is ordinary.
2. Offset units must be refused, not mishandled. A conversion with a non-zero ToBaseOffset (the temperature scales) is meaningless applied componentwise to a displacement. No dimension with offset units declares a vector form today, so this is defensive — but the generator should emit nothing and report a new SEM0xx if the combination ever appears, rather than emitting something quietly wrong. That matches how SEM004 handles the analogous typo case.
3. The reader is the open design question. Speed<T>.In(unit) returns T. The vector equivalent cannot return the vector type, because the result is no longer in base units and would be a lie in the type system. Options:
public (T X, T Y, T Z) In(IUnit unit); // proposed
public T XIn(IUnit unit); // per-component alternative
The tuple reads better at the call site; the per-component form composes better in generic code. Worth deciding before the factories land, since they should ship together.
4. Does this want Create-style raw constructors too? The object-initializer path stays available either way, so probably not — but if the factories become the documented route, the initializer's meaning ("these are base units") should be spelled out in the XML docs rather than assumed.
Scope
QuantitiesGenerator, regenerating 37 vector classes under Semantics.Quantities/Generated/. No new type names, so scripts/Generate-AliasProps.ps1 should produce identical output — run it and confirm rather than assume, because verify-generated will fail the PR otherwise.
Tests: extend StorageConversionTests<T> with the vector factories over double and decimal, exact where the factor terminates and to a relative tolerance where it does not, matching how the scalar factories are covered.
Speed<T>getsFromMeterPerSecond,FromKilometerPerHour,FromMilePerHour,FromFootPerSecond,FromKnot— one factory per entry in the dimension'savailableUnits.Velocity3D<T>gets none. The only way to build one is:The same is true of
Position3D,Displacement3D,Force3Dand the other 34 vector types. The library's premise is that the unit lives in the type instead of in the caller's head, and the vector forms opt out of it. In a domain that works natively in kilometres — orbital mechanics, which is what surfaced this — every construction site carries a hand-written ×1000, which is precisely the class of error the library exists to prevent.Design
Emit, per vector form, one factory per unit in the dimension's
availableUnits, taking oneTper component:Reading the same
Values<T>holder the scalar factories already use, so adecimalorPreciseNumbervector converts at its own precision rather than throughdouble— the 5.2.0 behaviour, extended to the vectors it skipped.Naming follows the existing mechanical rule: the unit's singular-lemma
namefromunits.json, verbatim.FromKilometer(x, y)for V2,FromKilometer(x, y, z, w)for V4.Four things to settle in review
1. No
Vector0Guards. Vector components are signed by construction. The non-negativity rule belongs to the V0 form and must not leak into the vectors — aPosition3Dwith a negative X is ordinary.2. Offset units must be refused, not mishandled. A conversion with a non-zero
ToBaseOffset(the temperature scales) is meaningless applied componentwise to a displacement. No dimension with offset units declares a vector form today, so this is defensive — but the generator should emit nothing and report a newSEM0xxif the combination ever appears, rather than emitting something quietly wrong. That matches how SEM004 handles the analogous typo case.3. The reader is the open design question.
Speed<T>.In(unit)returnsT. The vector equivalent cannot return the vector type, because the result is no longer in base units and would be a lie in the type system. Options:The tuple reads better at the call site; the per-component form composes better in generic code. Worth deciding before the factories land, since they should ship together.
4. Does this want
Create-style raw constructors too? The object-initializer path stays available either way, so probably not — but if the factories become the documented route, the initializer's meaning ("these are base units") should be spelled out in the XML docs rather than assumed.Scope
QuantitiesGenerator, regenerating 37 vector classes underSemantics.Quantities/Generated/. No new type names, soscripts/Generate-AliasProps.ps1should produce identical output — run it and confirm rather than assume, becauseverify-generatedwill fail the PR otherwise.Tests: extend
StorageConversionTests<T>with the vector factories overdoubleanddecimal, exact where the factor terminates and to a relative tolerance where it does not, matching how the scalar factories are covered.