Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,17 @@ Units.NET allows you to add your own units and quantities at runtime, to represe

Read more at [Extending-with-Custom-Units](https://github.com/angularsen/UnitsNet/wiki/Extending-with-Custom-Units).

#### Convert to and from your own unit of a quantity

To convert a quantity to or from a unit that Units.NET doesn't define, describe the unit with a `UnitOf<TQuantity>` and its value in the base unit of the quantity. No unit enum value or setup is needed.

```c#
var furlong = new UnitOf<Length>("Furlong", "Furlongs", 201.168); // 1 furlong = 201.168 m

double furlongs = Length.FromMiles(1).As(furlong); // 8
Length length = Length.Info.From(2, furlong); // 402.336 m, in the base unit
```

#### Map between unit enum values and unit abbreviations
```c#
// Map unit enum values to unit abbreviations
Expand Down
99 changes: 99 additions & 0 deletions UnitsNet.Tests/UnitOfTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Licensed under MIT No Attribution, see LICENSE file at the root.
// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet.

using System;
using UnitsNet.Units;
using Xunit;

namespace UnitsNet.Tests
{
public class UnitOfTests
{
// 1 furlong = 201.168 m
private static readonly UnitOf<Length> Furlong = new("Furlong", "Furlongs", 201.168);

[Fact]
public void Constructor_SetsNames()
{
Assert.Equal("Furlong", Furlong.Name);
Assert.Equal("Furlongs", Furlong.PluralName);
}

[Fact]
public void Constructor_WithNullArguments_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => new UnitOf<Length>(null!, "Furlongs", 1));
Assert.Throws<ArgumentNullException>(() => new UnitOf<Length>("Furlong", null!, 1));
Assert.Throws<ArgumentNullException>(() => new UnitOf<Length>("Furlong", "Furlongs", (BaseUnits)null!, 1));
}

[Fact]
public void BaseUnits_WhenNotGiven_IsUndefined()
{
Assert.Equal(BaseUnits.Undefined, Furlong.BaseUnits);
}

[Fact]
public void Constructor_WithBaseUnits_KeepsThem()
{
var baseUnits = new BaseUnits(length: LengthUnit.Foot, time: DurationUnit.Day);
var footPerDay = new UnitOf<Speed>("FootPerDay", "FeetPerDay", baseUnits, 0.3048 / 86400);

Assert.Equal(baseUnits, footPerDay.BaseUnits);
Assert.Equal(86400, Speed.FromFeetPerSecond(1).As(footPerDay), 9);
}

[Fact]
public void ToString_ReturnsName()
{
Assert.Equal("Furlong", Furlong.ToString());
}

[Fact]
public void As_ReturnsValueInThatUnit()
{
Assert.Equal(1, Length.FromFeet(660).As(Furlong), 12);
Assert.Equal(8, Length.FromMiles(1).As(Furlong), 12);
}

[Fact]
public void As_FromOtherUnitOfTheQuantity_ReturnsValueInThatUnit()
{
Assert.Equal(1, Length.FromKilometers(0.201168).As(Furlong), 12);
}

[Fact]
public void As_NullUnit_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => Length.FromMeters(1).As((UnitOf<Length>)null!));
}

[Fact]
public void From_ReturnsQuantityInBaseUnit()
{
Length length = Length.Info.From(2, Furlong);

Assert.Equal(LengthUnit.Meter, length.Unit);
Assert.Equal(402.336, length.Value, 12);
}

[Fact]
public void From_RoundTripsWithAs()
{
Assert.Equal(42, Length.Info.From(42, Furlong).As(Furlong), 12);
}

[Fact]
public void From_InfoOfAnotherQuantity_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => Mass.Info.From(1, Furlong));
}

[Fact]
public void From_NullArguments_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => Length.Info.From(1, (UnitOf<Length>)null!));
Assert.Throws<ArgumentNullException>(() => ((QuantityInfo)null!).From(1, Furlong));
}
}
}
90 changes: 90 additions & 0 deletions UnitsNet/UnitOf.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Licensed under MIT No Attribution, see LICENSE file at the root.
// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet.

using System;
using System.Diagnostics;

namespace UnitsNet
{
/// <summary>
/// A unit of <typeparamref name="TQuantity" />, described by its names and its value in the base unit of the
/// quantity, such as a furlong of <see cref="Length" />.
/// </summary>
/// <remarks>
/// Quantities can be converted to and from the unit with
/// <see cref="UnitOfExtensions.As{TQuantity}(TQuantity, UnitOf{TQuantity})" /> and
/// <see cref="UnitOfExtensions.From{TQuantity}(QuantityInfo, QuantityValue, UnitOf{TQuantity})" />.
/// Since the unit is tied to its quantity, using it with another quantity doesn't compile.
/// </remarks>
/// <typeparam name="TQuantity">The quantity this is a unit of, such as <see cref="Length" />.</typeparam>
[DebuggerDisplay("{Name}")]
public sealed class UnitOf<TQuantity>
where TQuantity : IQuantity
{
/// <summary>
/// Initializes a new instance of the <see cref="UnitOf{TQuantity}" /> class.
/// </summary>
/// <param name="singularName">The singular name of the unit, such as "Furlong".</param>
/// <param name="pluralName">The plural name of the unit, such as "Furlongs".</param>
/// <param name="valueInBaseUnit">
/// The value of one of this unit in the base unit of the quantity, such as 201.168 for a furlong, which is
/// 201.168 meters.
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="singularName" /> or <paramref name="pluralName" /> is <c>null</c>.
/// </exception>
public UnitOf(string singularName, string pluralName, QuantityValue valueInBaseUnit)
: this(singularName, pluralName, BaseUnits.Undefined, valueInBaseUnit)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="UnitOf{TQuantity}" /> class for a unit made of the base units of
/// UnitsNet, such as feet per day of <see cref="Speed" />.
/// </summary>
/// <param name="singularName">The singular name of the unit, such as "FootPerDay".</param>
/// <param name="pluralName">The plural name of the unit, such as "FeetPerDay".</param>
/// <param name="baseUnits">The base units the unit is made of, such as feet and days.</param>
/// <param name="valueInBaseUnit">The value of one of this unit in the base unit of the quantity.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="singularName" />, <paramref name="pluralName" />, or <paramref name="baseUnits" />
/// is <c>null</c>.
/// </exception>
public UnitOf(string singularName, string pluralName, BaseUnits baseUnits, QuantityValue valueInBaseUnit)
{
Name = singularName ?? throw new ArgumentNullException(nameof(singularName));
PluralName = pluralName ?? throw new ArgumentNullException(nameof(pluralName));
BaseUnits = baseUnits ?? throw new ArgumentNullException(nameof(baseUnits));
ValueInBaseUnit = (double)valueInBaseUnit;
}

/// <summary>
/// The singular name of the unit, such as "Furlong".
/// </summary>
public string Name { get; }

/// <summary>
/// The plural name of the unit, such as "Furlongs".
/// </summary>
public string PluralName { get; }

/// <summary>
/// The base units the unit is made of, or <see cref="UnitsNet.BaseUnits.Undefined" /> if it isn't made of the
/// base units of UnitsNet, such as a furlong.
/// </summary>
public BaseUnits BaseUnits { get; }

/// <summary>
/// The value of one of this unit in the base unit of the quantity.
/// </summary>
internal double ValueInBaseUnit { get; }

/// <summary>
/// Returns the name of the unit.
/// </summary>
public override string ToString()
{
return Name;
}
}
}
58 changes: 58 additions & 0 deletions UnitsNet/UnitOfExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Licensed under MIT No Attribution, see LICENSE file at the root.
// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet.

using System;

namespace UnitsNet
{
/// <summary>
/// Converts quantities to and from a <see cref="UnitOf{TQuantity}" />.
/// </summary>
public static class UnitOfExtensions
{
/// <summary>
/// Gets the value of the quantity in a <see cref="UnitOf{TQuantity}" />, such as a furlong of
/// <see cref="Length" />.
/// </summary>
/// <param name="quantity">The quantity to convert.</param>
/// <param name="unit">The unit to get the value in.</param>
/// <returns>The value in <paramref name="unit" />.</returns>
/// <exception cref="ArgumentNullException"><paramref name="unit" /> is <c>null</c>.</exception>
public static double As<TQuantity>(this TQuantity quantity, UnitOf<TQuantity> unit)
where TQuantity : IQuantity
{
if (unit is null) throw new ArgumentNullException(nameof(unit));
return quantity.As(quantity.QuantityInfo.BaseUnitInfo.Value) / unit.ValueInBaseUnit;
}

/// <summary>
/// Creates a quantity from a value in a <see cref="UnitOf{TQuantity}" />, such as a furlong of
/// <see cref="Length" />.
/// </summary>
/// <param name="quantityInfo">The information of the quantity, such as <see cref="Length.Info" />.</param>
/// <param name="value">The numerical value in <paramref name="unit" />.</param>
/// <param name="unit">The unit of the value.</param>
/// <returns>
/// The quantity in its base unit, since a quantity can only be in one of its own units.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="quantityInfo" /> or <paramref name="unit" /> is <c>null</c>.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="quantityInfo" /> is not the information of <typeparamref name="TQuantity" />.
/// </exception>
public static TQuantity From<TQuantity>(this QuantityInfo quantityInfo, QuantityValue value, UnitOf<TQuantity> unit)
where TQuantity : IQuantity
{
if (quantityInfo is null) throw new ArgumentNullException(nameof(quantityInfo));
if (unit is null) throw new ArgumentNullException(nameof(unit));
if (quantityInfo.ValueType != typeof(TQuantity))
{
throw new ArgumentException(
$"A unit of {typeof(TQuantity).Name} can't be used with {quantityInfo.Name}.", nameof(quantityInfo));
}

return (TQuantity)Quantity.From((double)value * unit.ValueInBaseUnit, quantityInfo.BaseUnitInfo.Value);
}
}
}