Skip to content

QuantityInfo: internalizing the UnitInfo construction #1555

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
129 changes: 95 additions & 34 deletions CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using CodeGen.Helpers;
using CodeGen.JsonTypes;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace CodeGen.Generators.UnitsNetGen
{
Expand Down Expand Up @@ -34,13 +35,10 @@ public string Generate()
{
Writer.WL(GeneratedFileHeader);
Writer.WL(@"
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

using System.Globalization;
using System.Linq;
using System.Resources;
using System.Runtime.Serialization;
using UnitsNet.Units;
#if NET
using System.Numerics;
#endif
Expand Down Expand Up @@ -122,6 +120,7 @@ namespace UnitsNet
[DataMember(Name = ""Unit"", Order = 2)]
private readonly {_unitEnumName}? _unit;
");
GenerateQuantityInfo();
GenerateStaticConstructor();
GenerateInstanceConstructors();
GenerateStaticProperties();
Expand All @@ -141,36 +140,86 @@ namespace UnitsNet
}}");
return Writer.ToString();
}

private void GenerateStaticConstructor()
private void GenerateQuantityInfo()
{
var quantityInfoClassName = $"{_quantity.Name}Info";
BaseDimensions baseDimensions = _quantity.BaseDimensions;
var createDimensionsExpression = _isDimensionless
? "BaseDimensions.Dimensionless"
: $"new BaseDimensions({baseDimensions.L}, {baseDimensions.M}, {baseDimensions.T}, {baseDimensions.I}, {baseDimensions.Θ}, {baseDimensions.N}, {baseDimensions.J})";

Writer.WL($@"
static {_quantity.Name}()
/// <summary>
/// Provides detailed information about the <see cref=""{_quantity.Name}""/> quantity, including its name, base unit, unit mappings, base dimensions, and conversion functions.
/// </summary>
public sealed class {quantityInfoClassName}: QuantityInfo<{_quantity.Name}, {_unitEnumName}>
{{");
Writer.WL(_isDimensionless ? $@"
BaseDimensions = BaseDimensions.Dimensionless;" : $@"
BaseDimensions = new BaseDimensions({baseDimensions.L}, {baseDimensions.M}, {baseDimensions.T}, {baseDimensions.I}, {baseDimensions.Θ}, {baseDimensions.N}, {baseDimensions.J});");

Writer.WL($@"
BaseUnit = {_unitEnumName}.{_quantity.BaseUnit};
Units = Enum.GetValues(typeof({_unitEnumName})).Cast<{_unitEnumName}>().ToArray();
Zero = new {_quantity.Name}(0, BaseUnit);
Info = new QuantityInfo<{_unitEnumName}>(""{_quantity.Name}"",
new UnitInfo<{_unitEnumName}>[]
{{");
/// <inheritdoc />
public {quantityInfoClassName}(string name, {_unitEnumName} baseUnit, IEnumerable<IUnitDefinition<{_unitEnumName}>> unitMappings, {_quantity.Name} zero, BaseDimensions baseDimensions,
QuantityFromDelegate<{_quantity.Name}, {_unitEnumName}> fromDelegate, ResourceManager? unitAbbreviations)
: base(name, baseUnit, unitMappings, zero, baseDimensions, fromDelegate, unitAbbreviations)
{{
}}

/// <inheritdoc />
public {quantityInfoClassName}(string name, {_unitEnumName} baseUnit, IEnumerable<IUnitDefinition<{_unitEnumName}>> unitMappings, {_quantity.Name} zero, BaseDimensions baseDimensions)
: this(name, baseUnit, unitMappings, zero, baseDimensions, {_quantity.Name}.From, new ResourceManager(""UnitsNet.GeneratedCode.Resources.{_quantity.Name}"", typeof({_quantity.Name}).Assembly))
{{
}}

/// <summary>
/// Creates a new instance of the <see cref=""{quantityInfoClassName}""/> class with the default settings for the {_quantity.Name} quantity.
/// </summary>
/// <returns>A new instance of the <see cref=""{quantityInfoClassName}""/> class with the default settings.</returns>
public static {quantityInfoClassName} CreateDefault()
{{
return new {quantityInfoClassName}(nameof({_quantity.Name}), DefaultBaseUnit, GetDefaultMappings(), new {_quantity.Name}(0, DefaultBaseUnit), DefaultBaseDimensions);
}}

/// <summary>
/// Creates a new instance of the <see cref=""{quantityInfoClassName}""/> class with the default settings for the {_quantity.Name} quantity and a callback for customizing the default unit mappings.
/// </summary>
/// <param name=""customizeUnits"">
/// A callback function for customizing the default unit mappings.
/// </param>
/// <returns>
/// A new instance of the <see cref=""{quantityInfoClassName}""/> class with the default settings.
/// </returns>
public static {quantityInfoClassName} CreateDefault(Func<IEnumerable<UnitDefinition<{_unitEnumName}>>, IEnumerable<IUnitDefinition<{_unitEnumName}>>> customizeUnits)
{{
return new {quantityInfoClassName}(nameof({_quantity.Name}), DefaultBaseUnit, customizeUnits(GetDefaultMappings()), new {_quantity.Name}(0, DefaultBaseUnit), DefaultBaseDimensions);
}}

/// <summary>
/// The <see cref=""BaseDimensions"" /> for <see cref=""{_quantity.Name}""/> is {_quantity.BaseDimensions}.
/// </summary>
public static BaseDimensions DefaultBaseDimensions {{ get; }} = {createDimensionsExpression};

/// <summary>
/// The default base unit of {_quantity.Name} is {_baseUnit.SingularName}. All conversions, as defined in the <see cref=""GetDefaultMappings""/>, go via this value.
/// </summary>
public static {_unitEnumName} DefaultBaseUnit {{ get; }} = {_unitEnumName}.{_baseUnit.SingularName};

/// <summary>
/// Retrieves the default mappings for <see cref=""{_unitEnumName}""/>.
/// </summary>
/// <returns>An <see cref=""IEnumerable{{T}}""/> of <see cref=""UnitDefinition{{{_unitEnumName}}}""/> representing the default unit mappings for {_quantity.Name}.</returns>
public static IEnumerable<UnitDefinition<{_unitEnumName}>> GetDefaultMappings()
{{");

foreach (Unit unit in _quantity.Units)
{
BaseUnits? baseUnits = unit.BaseUnits;
string baseUnitsFormat;
if (baseUnits == null)
{
Writer.WL($@"
new UnitInfo<{_unitEnumName}>({_unitEnumName}.{unit.SingularName}, ""{unit.PluralName}"", BaseUnits.Undefined, ""{_quantity.Name}""),");
baseUnitsFormat = "BaseUnits.Undefined";
}
else
{
var baseUnitsCtorArgs = string.Join(", ",
baseUnitsFormat = $"new BaseUnits({string.Join(", ",
new[]
{
baseUnits.L != null ? $"length: LengthUnit.{baseUnits.L}" : null,
Expand All @@ -180,17 +229,26 @@ private void GenerateStaticConstructor()
baseUnits.Θ != null ? $"temperature: TemperatureUnit.{baseUnits.Θ}" : null,
baseUnits.N != null ? $"amount: AmountOfSubstanceUnit.{baseUnits.N}" : null,
baseUnits.J != null ? $"luminousIntensity: LuminousIntensityUnit.{baseUnits.J}" : null
}.Where(str => str != null));

Writer.WL($@"
new UnitInfo<{_unitEnumName}>({_unitEnumName}.{unit.SingularName}, ""{unit.PluralName}"", new BaseUnits({baseUnitsCtorArgs}), ""{_quantity.Name}""),");
}.Where(str => str != null))})";
}

Writer.WL($@"
yield return new ({_unitEnumName}.{unit.SingularName}, ""{unit.SingularName}"", ""{unit.PluralName}"", {baseUnitsFormat});");
}

Writer.WL($@"
}},
BaseUnit, Zero, BaseDimensions);

}}
}}
");
}

private void GenerateStaticConstructor()
{
Writer.WL($@"
static {_quantity.Name}()
{{");
Writer.WL($@"
Info = {_quantity.Name}Info.CreateDefault();
DefaultConversionFunctions = new UnitConverter();
RegisterDefaultConversions(DefaultConversionFunctions);
}}
Expand Down Expand Up @@ -244,27 +302,27 @@ private void GenerateStaticProperties()
public static UnitConverter DefaultConversionFunctions {{ get; }}

/// <inheritdoc cref=""IQuantity.QuantityInfo""/>
public static QuantityInfo<{_unitEnumName}> Info {{ get; }}
public static QuantityInfo<{_quantity.Name}, {_unitEnumName}> Info {{ get; }}

/// <summary>
/// The <see cref=""BaseDimensions"" /> of this quantity.
/// </summary>
public static BaseDimensions BaseDimensions {{ get; }}
public static BaseDimensions BaseDimensions => Info.BaseDimensions;

/// <summary>
/// The base unit of {_quantity.Name}, which is {_quantity.BaseUnit}. All conversions go via this value.
/// </summary>
public static {_unitEnumName} BaseUnit {{ get; }}
public static {_unitEnumName} BaseUnit => Info.BaseUnitInfo.Value;

/// <summary>
/// All units of measurement for the {_quantity.Name} quantity.
/// </summary>
public static {_unitEnumName}[] Units {{ get; }}
public static IReadOnlyCollection<{_unitEnumName}> Units => Info.Units;

/// <summary>
/// Gets an instance of this quantity with a value of 0 in the base unit {_quantity.BaseUnit}.
/// </summary>
public static {_quantity.Name} Zero {{ get; }}
public static {_quantity.Name} Zero => Info.Zero;
");

if (_quantity.GenerateArithmetic)
Expand Down Expand Up @@ -294,7 +352,7 @@ private void GenerateProperties()
public {_unitEnumName} Unit => _unit.GetValueOrDefault(BaseUnit);

/// <inheritdoc />
public QuantityInfo<{_unitEnumName}> QuantityInfo => Info;
public QuantityInfo<{_quantity.Name}, {_unitEnumName}> QuantityInfo => Info;

/// <summary>
/// The <see cref=""BaseDimensions"" /> of this quantity.
Expand All @@ -312,6 +370,9 @@ private void GenerateProperties()
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
QuantityInfo IQuantity.QuantityInfo => Info;

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
QuantityInfo<{_unitEnumName}> IQuantity<{_unitEnumName}>.QuantityInfo => Info;

#endregion

#endregion
Expand Down
114 changes: 12 additions & 102 deletions CodeGen/Generators/UnitsNetGen/StaticQuantityGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using CodeGen.Helpers;
using CodeGen.JsonTypes;
using CodeGen.JsonTypes;

namespace CodeGen.Generators.UnitsNetGen
{
Expand All @@ -16,120 +15,31 @@ public string Generate()
{
Writer.WL(GeneratedFileHeader);
Writer.WL(@"
using System;
using System.Globalization;
using UnitsNet.Units;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

#nullable enable

namespace UnitsNet
namespace UnitsNet;

/// <summary>
/// Dynamically parse or construct quantities when types are only known at runtime.
/// </summary>
public partial class Quantity
{
/// <summary>
/// Dynamically parse or construct quantities when types are only known at runtime.
/// Serves as a repository for predefined quantity conversion mappings, facilitating the automatic generation and retrieval of unit conversions in the UnitsNet library.
/// </summary>
public partial class Quantity
internal static class Provider
{
/// <summary>
/// All QuantityInfo instances mapped by quantity name that are present in UnitsNet by default.
/// All QuantityInfo instances that are present in UnitsNet by default.
/// </summary>
public static readonly IDictionary<string, QuantityInfo> ByName = new Dictionary<string, QuantityInfo>
internal static IReadOnlyList<QuantityInfo> DefaultQuantities => new QuantityInfo[]
{");
foreach (var quantity in _quantities)
Writer.WL($@"
{{ ""{quantity.Name}"", {quantity.Name}.Info }},");
{quantity.Name}.Info,");
Writer.WL(@"
};

/// <summary>
/// Dynamically constructs a quantity of the given <see cref=""QuantityInfo""/> with the value in the quantity's base units.
/// </summary>
/// <param name=""quantityInfo"">The <see cref=""QuantityInfo""/> of the quantity to create.</param>
/// <param name=""value"">The value to construct the quantity with.</param>
/// <returns>The created quantity.</returns>
public static IQuantity FromQuantityInfo(QuantityInfo quantityInfo, double value)
{
return quantityInfo.Name switch
{");
foreach (var quantity in _quantities)
{
var quantityName = quantity.Name;
Writer.WL($@"
""{quantityName}"" => {quantityName}.From(value, {quantityName}.BaseUnit),");
}

Writer.WL(@"
_ => throw new ArgumentException($""{quantityInfo.Name} is not a supported quantity."")
};
}

/// <summary>
/// Try to dynamically construct a quantity.
/// </summary>
/// <param name=""value"">Numeric value.</param>
/// <param name=""unit"">Unit enum value.</param>
/// <param name=""quantity"">The resulting quantity if successful, otherwise <c>default</c>.</param>
/// <returns><c>True</c> if successful with <paramref name=""quantity""/> assigned the value, otherwise <c>false</c>.</returns>
public static bool TryFrom(double value, Enum? unit, [NotNullWhen(true)] out IQuantity? quantity)
{
quantity = unit switch
{");
foreach (var quantity in _quantities)
{
var quantityName = quantity.Name;
var unitTypeName = $"{quantityName}Unit";
var unitValue = unitTypeName.ToCamelCase();
Writer.WL($@"
{unitTypeName} {unitValue} => {quantityName}.From(value, {unitValue}),");
}

Writer.WL(@"
_ => null
};

return quantity is not null;
}

/// <summary>
/// Try to dynamically parse a quantity string representation.
/// </summary>
/// <param name=""formatProvider"">The format provider to use for lookup. Defaults to <see cref=""CultureInfo.CurrentCulture"" /> if null.</param>
/// <param name=""quantityType"">Type of quantity, such as <see cref=""Length""/>.</param>
/// <param name=""quantityString"">Quantity string representation, such as ""1.5 kg"". Must be compatible with given quantity type.</param>
/// <param name=""quantity"">The resulting quantity if successful, otherwise <c>default</c>.</param>
/// <returns>The parsed quantity.</returns>
public static bool TryParse(IFormatProvider? formatProvider, Type quantityType, [NotNullWhen(true)] string? quantityString, [NotNullWhen(true)] out IQuantity? quantity)
{
quantity = default(IQuantity);

if (!typeof(IQuantity).IsAssignableFrom(quantityType))
return false;

var parser = UnitsNetSetup.Default.QuantityParser;

return quantityType switch
{");
foreach (var quantity in _quantities)
{
var quantityName = quantity.Name;
Writer.WL($@"
Type _ when quantityType == typeof({quantityName}) => parser.TryParse<{quantityName}, {quantityName}Unit>(quantityString, formatProvider, {quantityName}.From, out quantity),");
}

Writer.WL(@"
_ => false
};
}

internal static IEnumerable<Type> GetQuantityTypes()
{");
foreach (var quantity in _quantities)
Writer.WL($@"
yield return typeof({quantity.Name});");
Writer.WL(@"
}
}
}");
return Writer.ToString();
Expand Down
Loading