Skip to content

Design proposal: Base type (WIP) #371

Closed
@angularsen

Description

@angularsen

NOTE: This is work in progress and early thoughts, but input is most welcome.

An interface or abstract type implemented by all quantity types, such as Length and Mass.

Motivation

  • Represent generic quantities in code, where we don't need to know if it is Length or Mass but want to communicate it is a quantity - we need to use object today
  • Get textual representation of quantity with someQuantity.ToString() => "5 kg", this works today except we need to treat it as object and we don't have access to overloads for things like culture and significant digits after radix
  • Enumerate units for this quantity as this currently requires reflection, a usecase is to dynamically show GUI for converting a passed in quantity (1 meter) to any of the other length units
  • Present generic quantities visually in a 2D graph by showing unit selector for the quantities to change unit and getting the values for each quantity in the selected unit, without hardcoding support for specific quantities

Discussion points

  • Should we change quantities from struct to class? As discussed in the double/decimal issue, with class we might benefit from supporting both double and decimal internal representations without doubling the code. With an interface, it will quickly be boxed anyway unless using generic methods instead
  • Should the new members be added to quantity base type or be added to the UnitSystem type. One thing to consider is what culture to use for things like abbreviations and number formatting, where UnitSystem instances are created explicitly with a given culture (default uses CurrentUICulture in Windows).

Proposal

  • Add interface IQuantity since structs can't inherit base types, but users should be wary that casting to interface means boxing the value with those performance implications
  • ToString() should reflect the original value and unit instead of using the base unit, which may be very different than the units the user is working with (addressed in Preserve value and unit #389)
  • Add getter property for QuantityType since quantities already expose this value
  • Add getter properties for original value and unit to present or filter on these values separately
  • Add getter property for unit abbreviations as current you must figure out to use UnitSystem.Default.GetAllAbbreviations()
  • Add getter properties for singular and plural names of quantity for both "ForcePerLength" and "Force per length" forms, to make it easier to textually describe the quantity type. Currently you use reflection to obtain QuantityType enum value and call ToString() on it to get "ForcePerLength".
  • Add methods to obtain singular and plural names of a given unit to get `"Newton
  • Add getter property for the quantity description in JSON so this can be displayed next to the quantity selector GUI
  • Add getter property for enumerating all units for this QuantityType as this currently requires reflection (see sample app https://github.com/angularsen/UnitsNet/pull/380/files#diff-6f7804b386e2c6cd78fb5a331e9c9a58R18)
    -UnitConverter.ConvertBy methods should not use reflection as this is unnecessary brittle and can be solved by generating code to check if from/to unit types are LengthUnit and passing that to Length.From() and Length.As() methods
  • Reduce code size by moving shared code into base type (or helper class), we currently have ~80 quantities. We already do use helper classes for heavy things like Parse() and ToString(), so not sure how much more to gain here.

Case studies

Converter app with hard coded quantities and using strings for conversions

https://github.com/angularsen/UnitsNet#example-creating-a-unit-converter-app

  • Since quantities are hard coded in the app, we can obtain the list of units like this:
if (selectedQuantity == "Length") { SetConversionUnits(Length.Units.Select(x => x.ToString())) } 
else if (selectedQuantity == "Mass") {} /* and so on */
  • Alternative is to use reflection to obtain list of units, but this is brittle
  • Use strings for quantities and units in the GUI lists and use UnitConverter to convert between two units of a quantity like this:
double centimeters = UnitConverter.ConvertByName(5, "Length", "Meter", "Centimeter"); // 500

Converter app with generic quantities #353

The main difference to the example above is that we want to obtain the list of units for any selected quantity, without knowing the quantity type.
We either have the quantity string ("Length" or "Mass"), or its equivalent QuantityType.Length and QuantityType.Mass enum values, but there is currently no intuitive way of getting abbreviations from this.

Challenge: Present the plural name of any unit

For listing units in the GUI. We can get "meter" and "newton per meter" from LengthUnit.Meter and ForcePerLengthUnit.NewtonPerMeter by calling ToString() and splitting the pascal case into lower case words, but it would only be singular and difficult to reliably present in plural form. The plural form is defined in JSON, so we can easily generate that.

Challenge: Present abbreviation of any unit

This currently requires you to know the quantity or unit type, in order to look it up statically via string x = Length.GetAbbreviation(LengthUnit unit) or dynamically via string x = UnitSystem.Default.GetDefaultAbbreviation(Type unitType, int unitValue)

This dotnetfiddle shows how to get all length abbreviations, given that we added LengthUnit values to the list and can resolve abbreviations based on that type:

object[] lengthUnits = Length.Units.Skip(1).Take(10).Cast<object>().ToArray(); // Assume we add these to the GUI list and read those back as `object`s at some point
Console.WriteLine("Length unit abbreviations: " + string.Join(", ", lengthUnits.Select(unit => UnitSystem.Default.GetDefaultAbbreviation(unit.GetType(), (int)unit))));

// Output:
// Length unit abbreviations: cm, dm, pica, pt, fathom, ft, in, km, m, µin

https://dotnetfiddle.net/9f5TiF

A base type could have the method `GetAbbreviation(object unit)

Discussion on converting from struct to class to support decimal #285

This is related, because we are here discussing whether to convert from struct to class in order to get the benefit of supporting both double and decimal in the same library without doubling the code size.

Fixes #354

Related issues

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions