Skip to content

Simplify interfaces#1662

Open
Muximize wants to merge 3 commits into
angularsen:masterfrom
Muximize:simplify-interfaces
Open

Simplify interfaces#1662
Muximize wants to merge 3 commits into
angularsen:masterfrom
Muximize:simplify-interfaces

Conversation

@Muximize

@Muximize Muximize commented May 4, 2026

Copy link
Copy Markdown
Contributor

In my use of UnitsNet, I find the recently added IQuantityOfType<TQuantity> very useful. I wanted to do a comparison between some, but could not without adding IComparisonOperators<TQuantity, TQuantity, bool> to the constraints of my method. I then realized this can be added to the base interface instead of the quantity itself.

While doing this I found a way to simplify some other interfaces in the inheritance chain, like reusing IArithmeticQuantity for both ILinearQuantity and ILogarithmicQuantity and removing one level of inheritance.

@lipchev Are these improvements in your opinion?

@angularsen

Copy link
Copy Markdown
Owner

@claude Review idea in PR description and current code change

@claude

claude Bot commented Jun 11, 2026

Copy link
Copy Markdown

Claude finished @angularsen's task in 4m 56s —— View job


PR Review: Simplify interfaces

  • Gather context: read interface files and diff
  • Understand the design idea in PR description
  • Review code changes for correctness and implications
  • Post review feedback

Design Idea

The PR has two goals:

  1. Add IComparisonOperators<TQuantity, TQuantity, bool> to IQuantityOfType<TQuantity> so callers can use comparison operators on generic quantities without extra constraints
  2. Flatten the two-type-parameter interface pattern (IArithmeticQuantity<TSelf, TUnit>, ILogarithmicQuantity<TSelf, TUnit>, IAffineQuantity<TSelf, TUnit, TOffset>) into simpler single-type-param interfaces composed with IQuantity<TSelf, TUnit> directly

Both goals are reasonable. The first is clearly useful. The second reduces redundancy in the generated code significantly.


What Changed

Hierarchy restructure (non-generated files):

Before After
IArithmeticQuantity.cs held both ILinearQuantity<TSelf> and IArithmeticQuantity<TSelf, TUnitType> IArithmeticQuantity.cs holds only IArithmeticQuantity<TSelf>; ILinearQuantity<TSelf> is in its own file
ILogarithmicQuantity<TSelf, TUnitType> existed Removed; only ILogarithmicQuantity<TSelf> : IQuantityOfType<TSelf>, IArithmeticQuantity<TSelf> remains
IAffineQuantity<TSelf, TUnitType, TOffset> existed Removed; only IAffineQuantity<TSelf, TOffset> : IQuantityOfType<TSelf> remains
IQuantityOfType<out TQuantity> — covariant, no comparison interfaces IQuantityOfType<TQuantity> — invariant, adds IComparable<TQuantity>, IEquatable<TQuantity>, IComparisonOperators (NET7+), IParsable (NET7+)
IQuantity : IFormattable IQuantity : IComparable, IFormattable

Generated quantities:

Before (per-quantity flat list):

public readonly partial struct Length :
    IArithmeticQuantity<Length, LengthUnit>,
#if NET7_0_OR_GREATER
    IDivisionOperators<Length, Length, double>,
    IMultiplyOperators<Length, Length, Area>,
    IComparisonOperators<Length, Length, bool>,
    IParsable<Length>,
#endif
    IComparable, IComparable<Length>, IEquatable<Length>, IFormattable

After (clean hierarchy):

public readonly partial struct Length : IQuantity<Length, LengthUnit>, ILinearQuantity<Length>
#if NET7_0_OR_GREATER
    , IMultiplyOperators<Length, Length, Area>
    ...
#endif

Issues

1. Breaking: IDivisionOperators<TSelf, TSelf, double> silently removed

The generator previously emitted IDivisionOperators<Length, Length, double> for all non-affine quantities. The operator itself (public static double operator /(Length left, Length right)) still exists in code, but this interface is no longer declared. External code that constrains on where T : IDivisionOperators<T, T, double> to work with any self-dividing quantity will break silently — the cast still works at runtime, but the constraint won't satisfy.

This removal seems intentional (the old generator emitted it for all non-affine quantities, new generator doesn't), but deserves an explicit mention in the PR description or changelog. Relevant generator diff →

2. Breaking: Removal of covariance from IQuantityOfType

IQuantityOfType<out TQuantity>IQuantityOfType<TQuantity>. Removing the out is required because IComparisonOperators is invariant, but it's still a binary breaking change. Code like IQuantityOfType<IQuantity> where a IQuantityOfType<Length> was expected (covariant assignment) will no longer compile. [IQuantity.cs:122](https://github.com/angularsen/UnitsNet/pull/1662/files#diff-d9f4ac20669a77ba849573acd66b0b5eb73d1bbe)

3. Breaking: IComparable added to base IQuantity

IQuantity now inherits IComparable (IQuantity.cs:11). Custom implementations that don't implement IComparable will break. The HowMuch.cs update shows this impact. For a widely-consumed library, this warrants a changelog entry.

4. Breaking: More interfaces required on IQuantityOfType implementations

On NET7+, IQuantityOfType<TQuantity> now requires IComparisonOperators<TQuantity, TQuantity, bool> and IParsable<TQuantity> from implementors. The HowMuch.cs file shows ~100 lines of new required boilerplate. This is significant for third-party quantity implementors.

5. Structural: Constraint duplication in IQuantityOfType

// IQuantity.cs:122-133
public interface IQuantityOfType<TQuantity> : IQuantity
#if NET7_0_OR_GREATER
    , IComparisonOperators<TQuantity, TQuantity, bool>  // (A) interface inherits this
    , IParsable<TQuantity>
#endif
    , IComparable<TQuantity>
    , IEquatable<TQuantity>
    where TQuantity : IQuantity
#if NET7_0_OR_GREATER
    , IComparisonOperators<TQuantity, TQuantity, bool>  // (B) constraint repeats it
    , IParsable<TQuantity>
#endif

This duplication is technically required for C# to let the compiler use the operators through the type parameter TQuantity, but it's visually confusing. A short comment explaining why both are needed (the interface inheritance satisfies the implementor, while the constraint satisfies callers) would help. IQuantity.cs:119 →

6. Minor: LogarithmicQuantityExtensions constraint is slightly more verbose

where TQuantity : ILogarithmicQuantity<TQuantity, TUnit> was simple. Now it's where TQuantity : ILogarithmicQuantity<TQuantity>, IQuantity<TQuantity, TUnit>. Not a problem, just noting the caller-side impact of the two-param removal.


Positives

  • The generated code is much cleaner — a flat list of 10+ interfaces per quantity becomes just 2-3
  • ILinearQuantity and ILogarithmicQuantity now share IArithmeticQuantity<TSelf> consistently, making the hierarchy semantically correct
  • IComparisonOperators in IQuantityOfType is genuinely useful for generic code
  • IParsable in IQuantityOfType on NET7+ is a nice-to-have
  • IAffineQuantity<TSelf, TOffset> now correctly includes the arithmetic operators it was missing (they were only on the removed three-param version)

Recommendation

The direction is good. The main concern is that this is a significant breaking change for custom quantity implementors and for any external code constraining on the old two-param interfaces or on IDivisionOperators<T, T, double>. If the library is on a major version or the team is comfortable with the break, this is a clear improvement. Otherwise, it warrants a migration guide or at least a CHANGELOG entry listing the removed interfaces.

The HowMuch.cs updated example is helpful for showing the new implementation contract. It might also be worth updating Docs/ to reflect the new interface requirements for custom quantities.

@Muximize Muximize force-pushed the simplify-interfaces branch from 4a7a992 to bd39d28 Compare June 11, 2026 22:13
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.

2 participants