Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XP: Prototype source generator #1500

Draft
wants to merge 2 commits into
base: release/v6
Choose a base branch
from
Draft

XP: Prototype source generator #1500

wants to merge 2 commits into from

Conversation

angularsen
Copy link
Owner

@angularsen angularsen commented Jan 19, 2025

Investigate if we can split up UnitsNet by using c# source generators to only bring in quantities and operator overloads for the quantities you intend to use.

Benefits

  • Vastly less binary size, you only get what you actually use
  • No need to have hundreds of nuget packages to be modular.
  • Avoids cyclic reference problem if Speed, Length and Duration were provided in separate nugets to provide arithemetic operator overloads like Speed = Length / Duration, which quantity nugets should provide what overloads and which nugets should they depend on?

Proposal (not fleshed out yet)

  • UnitsNet.Core nuget
    • Core interfaces and types, like IQuantity, QuantityInfo, UnitsNetSetup etc.
    • No units provided out of the box, but supports adding custom units.
    • Source generator and related attributes for controlling source gen in client code, such as [assembly: UnitsNetSrcGenInit("Length,Mass")] to bring in Length and Mass quantities, and any arithmetic operators for these
  • UnitsNet.DefaultUnits nuget
    • Brings in all our units, conversions and arithmetic relations
  • UnitsNet nuget, brings in both of the above to mimic today's features out of the box - except built on source generators and only building what you actually use

Example

Declaring the quantities to generate:
https://github.com/angularsen/UnitsNet/pull/1500/files#diff-f671be4e1e6fa64f602565c6d10f3c035f90d5c2ea631709d7e8ecb0d48537c9

[assembly: UnitsNetSrcGenInit("Length,Mass")]

Using the generated quantities:
https://github.com/angularsen/UnitsNet/pull/1500/files#diff-3c14d449aa45f6d30c596b65d3d6f0625b03212c7cf3028d3c905b7db57e37c4

using UnitsNetSrcGen;

var l = new Length {Value = 10, Unit = LengthUnit.Centimeter};
var m = new Mass { Value = 20, Unit = MassUnit.Kilogram };

Console.WriteLine($"Length: {l.Value} {l.Unit}");
Console.WriteLine($"Mass: {m.Value} {m.Unit}");

@Muximize
Copy link
Contributor

Muximize commented Jan 21, 2025

This is a great idea! 👍

How about combining source generation with @lipchev's proposal of replacing the underlying Value type, to something the user can decide, double, decimal, Fraction or any INumber.

Something similar could be done by making the quantities generic over the underlying Value type and than aliasing them but I guess source generators can solve multiple problems at once.

While we're at it, what are your thoughts on giving quantities a static base unit to avoid loss of precision and comparison issues? Users can then choose a base unit that makes sense for their project, or even multiple and avoid mixing them up:

global using Power = Power<double, PowerUnit.MegaWatts>;
global using PrecisePower = Power<Fraction, PowerUnit.PicoWatts>;

With source generation we can make some things very explicit:

var x = Power.FromMegaWatts(3.3);        // this is the only FromX method
var y = Power.ConvertFromWatts(222.2);   // there is no ConvertFromMegaWatts method

namespace UnitsNet.Xp.SrcGen
{
[Generator]
public class QuantitySourceGenerator : ISourceGenerator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISourceGenerator has been deprecated in favor of IIncrementalGenerator

@lipchev
Copy link
Collaborator

lipchev commented Jan 22, 2025

I was also thinking of (eventually) offering a source generator to facilitate the creation of "custom" quantities, but I don't believe its possible to have the source-generated quantities as a replacement of the current nugget: consider how multiple projects, each with it's own source generator would interact- is the Mass from the first project compatible with the Mass from the second?

FYI- my fraction-based implementation requires only the QuantityInfo + the abbreviation mappings (possibly defined as resources) in order to get a fully functional Quantity. The only thing missing are the operators (I used to have these configurable as well, but decided to cut the feature from the initial release).

I also considered using a TNumber: INumber type thing, but the problem with that approach (besides handling the potential overflows) is that you don't know if you can rely on the == operator or not (1d + 1d == 2d vs 1m + 1m = 2m vs 1 / 3 == 1/3).

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.

3 participants