angularsen / UnitsNet

Makes life working with units of measurement just a little bit better.
https://www.nuget.org/packages/UnitsNet/
MIT No Attribution
2.65k stars 382 forks source link

Removing the operators with `TimeSpan` in v6? #1413

Closed lipchev closed 2 months ago

lipchev commented 4 months ago

I just found minor breaking change between v5 and v6 that I don't believe was mentioned anywhere:

        /// <summary>Get <see cref="Mass"/> from <see cref="TimeSpan"/> times <see cref="MassFlow"/>.</summary>
        public static Mass operator *(TimeSpan time, MassFlow massFlow)
        {
            return Mass.FromKilograms(massFlow.KilogramsPerSecond * time.TotalSeconds);
        }

        /// <summary>Get <see cref="Mass"/> from <see cref="MassFlow"/> times <see cref="Duration"/>.</summary>
        public static Mass operator *(MassFlow massFlow, Duration duration)
        {
            return Mass.FromKilograms(massFlow.KilogramsPerSecond * duration.Seconds);
        }

The TimeSpan overload has been removed (relying on the implicit conversion to Duration), and while we have the inverse overload in Duration (i.e. *(Duration duration, MassFlow massFlow)) the following expression is no longer correctly inferred:

var massEvaporated = (weightAssay.Date - currentValue.Date) * evaporationRate; // does not compile

Everything is fine if we inverted the order of operations:

var massEvaporated = evaporationRate * (weightAssay.Date - currentValue.Date); // no problem

I personally don't mind the removal (note that the implicit conversion from TimeSpan is lossless in my upcoming proposal for v6), but if we wanted - there would be no particular difficulty in having the operation overload added to the MassFlow.extra.cs.

I assume this affects all multiplications with TimeSpan (e.g. VolumeFlow and such)..

angularsen commented 4 months ago

I think it's important that UnitsNet don't add unnecessary friction on this, so if some operator overloads don't work with implicit cast to Duration, then we should add the corresponding TimeSpan overload.

Muximize commented 4 months ago

This was discussed here: https://github.com/angularsen/UnitsNet/pull/1354#discussion_r1493344825

And implemented here: https://github.com/angularsen/UnitsNet/pull/1365

angularsen commented 4 months ago

@Muximize I believe we can adapt #1365 to take into account left hand args of TimeSpan, and autogenerate explicit overloads for these cases, right?

Muximize commented 4 months ago

Yes, this will add 28 additional operators to the current set of units. I tested two ways of doing this:

  1. Generating the operators exactly like the others, adding to QuantityRelationsParser.cs:

    var timeSpanQuantity = pseudoQuantity with { Name = "TimeSpan" };
    relations.AddRange(relations
    .Where(r => r.LeftQuantity.Name is "Duration")
    .Select(r => r with { LeftQuantity = timeSpanQuantity, LeftUnit = r.LeftUnit with { PluralName = "Total" + r.LeftUnit.PluralName } })
    .ToList());
  2. Just reversing the operands so they use the implicit conversion, adding to QuantityGenerator.cs:

    if (relation.RightQuantity.Name is "Duration")
    {
    Writer.WL($@"
        /// <summary>Get <see cref=""{relation.ResultQuantity.Name}""/> from <see cref=""TimeSpan""/> {relation.Operator} <see cref=""{relation.LeftQuantity.Name}""/>.</summary>
        public static {relation.ResultQuantity.Name} operator {relation.Operator}(TimeSpan timeSpan, {relation.LeftQuantity.Name} {leftParameter}) => {leftParameter} {relation.Operator} timeSpan;
    ");
    }
angularsen commented 3 months ago

Without fully groking the full context here, I think solution 1 feels natural? I'm fine either way as long as there is a comment next to it explaining why.

angularsen commented 3 months ago

Nr2 seems the most straight forward though, but I was puzzled by relation.RightQuantity.Name, should it not be LeftQuantity? Was the operands swapped before this?

Muximize commented 3 months ago

Normally, if a quantity (say Power) has a relation with Duration, then power * duration will be in Power and duration * power will be in Duration

Because implicit conversion only works for the right operand, we have to generate timespan * power. We cannot add this to TimeSpan (not ours) and not to Duration (not part of the operator) so we have to add it to Power, so we infer it from power * duration. Hence the relation.RightQuantity.Name

However, as we do a similar thing with relations involving double, the first solution is a bit more natural indeed.

github-actions[bot] commented 2 months ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

github-actions[bot] commented 2 months ago

This issue was automatically closed due to inactivity.