Skip to content

Overview

mangh edited this page May 10, 2021 · 11 revisions

UnitsOfMeasurement project provides you with the source components (C# source files and T4 text templates) for creating and managing unit of measurement types that you may need in your C# application.

Basically what is left for you to do - after the project is created from the template - is to specify required units in a text file (_definitions.txt) and generate corresponding unit types according to T4 text templates (_unit.t4, _scale.t4, _generator.tt). Target unit types may be created either in a single .cs file (_generator.cs) or in multiple .cs files (one for each unit: Meter.cs, Second.cs, ...), depending on boolean variable (__one_cs_file) specified in the template. The process is shown on the below diagram:

Overview

Sample definitions

Assume you have prepared the following definitions:

///////////////////////////////////////////////////////
//
//  Length
//
unit Meter "m" = <Length>; 
unit Kilometer "km" = Meter / 1000;
unit Inch "in" = 100 * Meter / 2.54;
unit /* international */ Foot "ft" = Inch / 12;
unit /* international */ Yard "yd" = Foot / 3;
unit /* international */ Mile "mil" = Yard / 1760;

///////////////////////////////////////////////////////
//
//  Time
//
unit Second "s" = <Time>;
unit Minute "min" = Second / 60;
unit Hour "h" = Minute / 60;

///////////////////////////////////////////////////////
//
//  Velocity
//
unit Meter_Sec "m/s" = Meter / Second;
unit Kilometer_Hour "km/h" = Kilometer / Hour | Meter_Sec * (1/1000) / ((1/60)/60);

///////////////////////////////////////////////////////
// 
//  Currency
//  Note: the rates (conversion factors) below are
//  to be updated on application startup. 
//
unit<decimal> EUR "EUR" = <Money>;          // Euro 
unit<decimal> USD "USD" = 1.3433 * EUR;     // US Dollar 
unit<decimal> GBP "GBP" = 0.79055 * EUR;    // British Pound 
unit<decimal> PLN "PLN" = 4.1437 * EUR;     // Polish Zloty 

The text specifies required units (Meter, Meter_Sec etc.), unit symbols ("m", "m/s"), underlying value type (double as a default, decimal, float) as well as conversion (e.g. Hour = Minute / 60) and arithmetic (e.g. Meter_Sec = Meter / Second) relationships between units.

Next you run template transformation ("Run Custom Tool" on _generator.tt). It takes the above definitions as input and generates the following units (as C# partial structs):

  • length units: Meter, Kilometer, Inch, Foot, Yard, Mile
  • time units: Second, Minute, Hour
  • velocity units: Meter_Sec, Kilometer_Hour.
  • currency units: EUR, USD, GBP, PLN.

That's all. Now you can compile the project and make use of generated units in your application.

Usage

You can use generated units directly, referring to their names explicitly as in the below excerpt from ProjectileRange demo application (with a little more elaborate set of units):

void CalculateProjectileRange(Degree degrees, out Second tmax, out Meter xmax, out Meter ymax)
{
    Meter_Sec2 g = (Meter_Sec2)9.80665; // the gravitational acceleration
    Meter_Sec  v = (Meter_Sec)715.0;    // the velocity at which the projectile is launched (AK-47)
    Meter      h = (Meter)0.0;          // the initial height of the projectile
    Radian angle = (Radian)degrees;     // the angle at which the projectile is launched
    // the time it takes for the projectile to finish its trajectory:
    tmax = (v * Sin(angle) + Sqrt(v * Sin(angle) * v * Sin(angle) + 2.0 * g * h)) / g;
    // the max vertical distance traveled by the projectile
    ymax = h;
    for (Second t = (Second)1.0; t < tmax; t++)
    {
        Meter y = h + v * Sin(angle) * t - g * t * t / 2.0;
        if (y > ymax) ymax = y;
    }
    // the total horizontal distance traveled by the projectile
    xmax = v * Cos(angle) * tmax;
}

You can also refer to units via their proxies (Unit<T> and Scale<T> proxies gathered in a static Catalog class) and use them as variables or collections when you cannot refer to any explicit unit(s):

// unit of symbol "in" (Inch proxy):
Unit<double> inch = Catalog.Unit<double>("in");

// Units of Meter family:
IEnumerable<Unit<double>> meterFamily = Catalog.Units<double>(Meter.Family);

// Get length from an input string:
string input  = "10 m";
var parser = new QuantityParser<double>(meterFamily/*units allowed in the input*/);
IQuantity<double> length;
if (!parser.TryParse(input, out length))
{
    Console.WriteLine("Invalid number or unit other than any the following: \"{0}\".", 
        string.Join("\", \"", parser.Units.SelectMany(u => u.Symbol))
    );
}
else
{
    // Convert received length to units from the Meter family:
    foreach (var unit in parser.Units)
    {
        IQuantity<double> output = unit.From(length);
        Console.WriteLine("{0,-40}  {1} -> {2}", unit, input, output);
    }
    // Finally give the result in inches:
    Inch inches1 = Inch.From(length);               // conversion via Inch unit (explicitly)
    IQuantity<double> inches2 = inch.From(length);  // conversion via Inch proxy
}			

See Demo Applications and Demo Unit Tests for more examples.

Customizing

You can modify _unit.t4 and/or _scale.t4 templates to fit structure and functionality of generated units to your needs. You can further extend the functionality by writing your own extensions to the generated partial structs.

See also

Clone this wiki locally