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

Generic math for UnitsNet in .NET 7 #1164

Merged
merged 11 commits into from
Dec 29, 2022

Conversation

AndreasLeeb
Copy link
Contributor

Hi,

since it's already been quite some time since #984 and as #1129 shows that the topic is of interest, I also gave it a shot.
So far, it's been a surprisingly minimally invasive undertaking.

Changes so far:

  • build UnitsNet in .NET 7 as well with very limited conditional compilation for the generic math interfaces
  • extended IQuantity interface hierarchy to support generic math (as the TSelf parameter for CRT pattern, and the underlying value type are needed)
  • automatically generated operator overloads and parsing methods are covered by the respective generic math interfaces

Still to do:

  • also cover the manually added operator overloads in the *.extra.cs files with generic math interfaces
  • also implement ISpanParsable, which will need some work, especially in the QuantityParser
  • maybe for symmetry reasons also overload the unary + operator and implement IUnaryPlusOperators. However, I'm unsure because it's a pretty useless operator
  • resolve nullable reference types chaos: since multi-targeting with net7.0 even when disabling the nullability in the csproj I kept getting tons of errors for both netstandard2.0 and net7.0 builds, so for now I've disabled the respective warnings. We should fully use the nullable reference types feature in the whole code base also for .netstandard2.0 to solve this tangle

Pain points:

  • multiplication with one operand of Quantity type and one operand of double/decimal type only supported with Quantity being the first parameter (when adding the commutative case, I get errors
    grafik
  • I suspect we'll run into the same issues with some custom overloads involving commutative multiplication with different quantity types

@angularsen
Copy link
Owner

Awesome! I will have to take a closer look later.

@angularsen angularsen force-pushed the AndreasLeeb/genericMath branch from 9d77f6f to b343f79 Compare December 22, 2022 19:21
- Add `Zero` static abstract property, already implemented by all quantities.
- Remove redundant `IEqualityOperators` constraint, covered by `IComparisonOperators`.
- Change `IDivisionOperators` to divide by number instead of TSelf, needed for average calculation.

Got error trying to keep both IDivisionOperators declarations:
```
Interface 'UnitsNet.IArithmeticQuantity<TSelf,TUnitType,TValueType>' cannot implement both 'System.Numerics.IDivisionOperators<TSelf,TValueType,TSelf>' and 'System.Numerics.IDivisionOperators<TSelf,TSelf,TValueType>' because they may unify for some type parameter substitutions
```
Add generic math implementations and tests to test out the new interfaces:
- Sum
- Average
angularsen added a commit that referenced this pull request Dec 28, 2022
Inspired by article: https://www.meziantou.net/how-to-use-nullable-reference-types-in-dotnet-standard-2-0-and-dotnet-.htm

Related to #1164 

### Changes
- Add `NullableAttributes.cs` via `Directory.Build.props` to shim annotation attributes for netstandard2.0 and older .NET versions
- Fix nullable warnings, observed when multitargeting net7.0 + netstandard2.0
- Add `[NotNullWhen(true)]` to all `Try` methods
- Remove redundant `!` nullable suppressions
@angularsen angularsen marked this pull request as ready for review December 28, 2022 15:09
@angularsen
Copy link
Owner

I fixed nullable annotations in #1175 and added some initial extension methods Sum and Average to test out the new generic math interfaces in practice.

Some changes made to the PR:

  • Merge in latest master, with nullable fix
  • Re-enable nullable, remove mute of warnings
  • Tweak IQuantity constraints for generic math, specifically the IDivisionOperators had to be changed to support dividing by a number for Average(). Details in ed27b33.
  • The extension methods were written so we don't have to specify the generic type arguments, in particular supporting Average for both double and decimal quantities required two separate extension method classes to keep the same method name. I tried adding both value types to the generic math interface list, but that resulted in ambiguous method errors.

Please review my commits and take a look whether we should expand on the extension methods or make other tweaks, but I think this is more or less ready to go.

There are definitely some paper cuts with regards to having units in the mix, such as addition picking the left hand side unit so my initial attempt at Average() resulted in the base unit instead of the unit in the collection. We'll just have to test this out more and get a better feel for it, but it does seem to kind of work.

@codecov
Copy link

codecov bot commented Dec 28, 2022

Codecov Report

Base: 88% // Head: 87% // Decreases project coverage by -0% ⚠️

Coverage data is based on head (d48d1e3) compared to base (3a763d7).
Patch coverage: 7% of modified lines in pull request are covered.

❗ Current head d48d1e3 differs from pull request most recent head a415d51. Consider uploading reports for the commit a415d51 to get more accurate results

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #1164    +/-   ##
=======================================
- Coverage      88%     87%    -1%     
=======================================
  Files         319     321     +2     
  Lines       31557   31679   +122     
=======================================
+ Hits        27858   27867     +9     
- Misses       3699    3812   +113     
Impacted Files Coverage Δ
...nitsNet/GeneratedCode/Quantities/Acceleration.g.cs 89% <0%> (-1%) ⬇️
...et/GeneratedCode/Quantities/AmountOfSubstance.g.cs 89% <0%> (-1%) ⬇️
...tsNet/GeneratedCode/Quantities/AmplitudeRatio.g.cs 83% <0%> (-1%) ⬇️
UnitsNet/GeneratedCode/Quantities/Angle.g.cs 89% <0%> (-1%) ⬇️
...tsNet/GeneratedCode/Quantities/ApparentEnergy.g.cs 82% <0%> (-1%) ⬇️
...itsNet/GeneratedCode/Quantities/ApparentPower.g.cs 83% <0%> (-1%) ⬇️
UnitsNet/GeneratedCode/Quantities/Area.g.cs 91% <0%> (-1%) ⬇️
UnitsNet/GeneratedCode/Quantities/AreaDensity.g.cs 83% <0%> (-1%) ⬇️
.../GeneratedCode/Quantities/AreaMomentOfInertia.g.cs 85% <0%> (-1%) ⬇️
UnitsNet/GeneratedCode/Quantities/BitRate.g.cs 91% <0%> (-1%) ⬇️
... and 108 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@AndreasLeeb
Copy link
Contributor Author

Looks good, I think 👍

Regarding the operators in the *.extra.cs files, that could be tackled easily by describing the dependencies (operations) between different quantities in the quantity JSON files, and then the operator overloads and the generic math interfaces for the quantity structs could also be automatically generated. But that's a topic for another time 😄

@angularsen angularsen changed the title Generic Math for UnitsNet in .NET 7 Generic math for UnitsNet in .NET 7 Dec 29, 2022
@angularsen angularsen merged commit 7e449a8 into angularsen:master Dec 29, 2022
@angularsen
Copy link
Owner

Thanks for the great work! The nuget should be out to play with shortly.

Release UnitsNet/5.0.0-rc008 · angularsen/UnitsNet

vKaras1337 added a commit to vKaras1337/UnitsNet that referenced this pull request Jun 28, 2023
…hange. Intoduced with a change from (angularsen#1164)

thats why I removed one space from QuantityGenerator.cs
angularsen added a commit that referenced this pull request Feb 4, 2024
Related #1200 

In the PR adding generic math (#1164) @AndreasLeeb states:
> Regarding the operators in the *.extra.cs files, that could be tackled
easily by describing the dependencies (operations) between different
quantities in the quantity JSON files, and then the operator overloads
and the generic math interfaces for the quantity structs could also be
automatically generated. But that's a topic for another time 😄

I decided to give this a shot.

`UnitRelations.json` contains relations extracted from the existing
*.extra.cs files. I decided on a new file because multiplication is
commutative and I didn't want to duplicate these in the individual
quantity JSON files, or risk missing one or the other, so it's best to
define them once in one place. The generator handles this by generating
two operators for a single multiplication relation.

The relations format uses the quantities method names. This is a bit
unfortunate, but it's the best I could come up with without making the
CodeGen project depend on UnitsNet, which would create a bit of a
chicken/egg problem. This is not unheard of (self-hosted compilers) but
I wanted to keep it simple for now.

The generated code enables the removal of 44 *.extra.cs files, and the
17 remaining contain much less code.

---------

Co-authored-by: Andreas Gullberg Larsen <[email protected]>
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