Skip to content

💥Require unit enum types to also be struct #1472

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

Merged
merged 2 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
17 changes: 0 additions & 17 deletions UnitsNet.Tests/UnitAbbreviationsCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,23 +404,6 @@ public void MapAndLookup_WithSpecificEnumType()
Assert.Equal("sm", UnitAbbreviationsCache.Default.GetDefaultAbbreviation(HowMuchUnit.Some));
}

/// <inheritdoc cref="MapAndLookup_WithSpecificEnumType"/>
[Fact]
public void MapAndLookup_WithEnumType()
{
Enum valueAsEnumType = HowMuchUnit.Some;
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(valueAsEnumType, "sm");
Assert.Equal("sm", UnitAbbreviationsCache.Default.GetDefaultAbbreviation(valueAsEnumType));
}

/// <inheritdoc cref="MapAndLookup_WithSpecificEnumType"/>
[Fact]
public void MapAndLookup_MapWithSpecificEnumType_LookupWithEnumType()
{
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(HowMuchUnit.Some, "sm");
Assert.Equal("sm", UnitAbbreviationsCache.Default.GetDefaultAbbreviation((Enum)HowMuchUnit.Some));
}

/// <summary>
/// Convenience method to the proper culture parameter type.
/// </summary>
Expand Down
12 changes: 6 additions & 6 deletions UnitsNet/CustomCode/QuantityParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace UnitsNet
/// <typeparam name="TUnitType">The type of unit enum that belongs to this quantity, such as <see cref="LengthUnit"/> for <see cref="Length"/>.</typeparam>
public delegate TQuantity QuantityFromDelegate<out TQuantity, in TUnitType>(double value, TUnitType fromUnit)
where TQuantity : IQuantity
where TUnitType : Enum;
where TUnitType : struct, Enum;

/// <summary>
/// Parses quantities from strings, such as "1.2 kg" to <see cref="Length"/> or "100 cm" to <see cref="Mass"/>.
Expand Down Expand Up @@ -67,7 +67,7 @@ public TQuantity Parse<TQuantity, TUnitType>(string str,
IFormatProvider? formatProvider,
QuantityFromDelegate<TQuantity, TUnitType> fromDelegate)
where TQuantity : IQuantity
where TUnitType : Enum
where TUnitType : struct, Enum
{
if (str == null) throw new ArgumentNullException(nameof(str));
str = str.Trim();
Expand Down Expand Up @@ -154,7 +154,7 @@ internal string CreateRegexPatternForUnit<TUnitType>(
TUnitType unit,
IFormatProvider? formatProvider,
bool matchEntireString = true)
where TUnitType : Enum
where TUnitType : struct, Enum
{
var unitAbbreviations = _unitAbbreviationsCache.GetUnitAbbreviations(unit, formatProvider);
var pattern = GetRegexPatternForUnitAbbreviations(unitAbbreviations);
Expand All @@ -181,7 +181,7 @@ private TQuantity ParseWithRegex<TQuantity, TUnitType>(string valueString,
QuantityFromDelegate<TQuantity, TUnitType> fromDelegate,
IFormatProvider? formatProvider)
where TQuantity : IQuantity
where TUnitType : Enum
where TUnitType : struct, Enum
{
var value = double.Parse(valueString, ParseNumberStyles, formatProvider);
var parsedUnit = _unitParser.Parse<TUnitType>(unitString, formatProvider);
Expand Down Expand Up @@ -242,7 +242,7 @@ private static bool TryExtractValueAndUnit(Regex regex, string str, [NotNullWhen
return true;
}

private string CreateRegexPatternForQuantity<TUnitType>(IFormatProvider? formatProvider) where TUnitType : Enum
private string CreateRegexPatternForQuantity<TUnitType>(IFormatProvider? formatProvider) where TUnitType : struct, Enum
{
var unitAbbreviations = _unitAbbreviationsCache.GetAllUnitAbbreviationsForQuantity(typeof(TUnitType), formatProvider);
var pattern = GetRegexPatternForUnitAbbreviations(unitAbbreviations);
Expand All @@ -251,7 +251,7 @@ private string CreateRegexPatternForQuantity<TUnitType>(IFormatProvider? formatP
return $"^{pattern}$";
}

private Regex CreateRegexForQuantity<TUnitType>(IFormatProvider? formatProvider) where TUnitType : Enum
private Regex CreateRegexForQuantity<TUnitType>(IFormatProvider? formatProvider) where TUnitType : struct, Enum
{
var pattern = CreateRegexPatternForQuantity<TUnitType>(formatProvider);
return new Regex(pattern, RegexOptions.Singleline | RegexOptions.IgnoreCase);
Expand Down
12 changes: 6 additions & 6 deletions UnitsNet/CustomCode/UnitAbbreviationsCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ internal UnitAbbreviationsCache(QuantityInfoLookup quantityInfoLookup)
/// <param name="unit">The unit enum value.</param>
/// <param name="abbreviations">Unit abbreviations to add.</param>
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, params string[] abbreviations) where TUnitType : Enum
public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, params string[] abbreviations) where TUnitType : struct, Enum
{
PerformAbbreviationMapping(unit, CultureInfo.CurrentCulture, false, abbreviations);
}
Expand All @@ -101,7 +101,7 @@ public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, params string[] abb
/// <param name="unit">The unit enum value.</param>
/// <param name="abbreviation">Unit abbreviations to add as default.</param>
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, string abbreviation) where TUnitType : Enum
public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, string abbreviation) where TUnitType : struct, Enum
{
PerformAbbreviationMapping(unit, CultureInfo.CurrentCulture, true, abbreviation);
}
Expand All @@ -115,7 +115,7 @@ public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, string abbre
/// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
/// <param name="abbreviations">Unit abbreviations to add.</param>
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider, params string[] abbreviations) where TUnitType : Enum
public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider, params string[] abbreviations) where TUnitType : struct, Enum
{
PerformAbbreviationMapping(unit, formatProvider, false, abbreviations);
}
Expand All @@ -129,7 +129,7 @@ public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? fo
/// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
/// <param name="abbreviation">Unit abbreviation to add as default.</param>
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider, string abbreviation) where TUnitType : Enum
public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider, string abbreviation) where TUnitType : struct, Enum
{
PerformAbbreviationMapping(unit, formatProvider, true, abbreviation);
}
Expand Down Expand Up @@ -183,7 +183,7 @@ private void PerformAbbreviationMapping(Enum unitValue, IFormatProvider? formatP
/// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
/// <returns>The default unit abbreviation string.</returns>
public string GetDefaultAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : Enum
public string GetDefaultAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : struct, Enum
{
Type unitType = typeof(TUnitType);

Expand Down Expand Up @@ -215,7 +215,7 @@ public string GetDefaultAbbreviation(Type unitType, int unitValue, IFormatProvid
/// <param name="unit">Enum value for unit.</param>
/// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
/// <returns>Unit abbreviations associated with unit.</returns>
public string[] GetUnitAbbreviations<TUnitType>(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : Enum
public string[] GetUnitAbbreviations<TUnitType>(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : struct, Enum
{
return GetUnitAbbreviations(typeof(TUnitType), Convert.ToInt32(unit), formatProvider);
}
Comment on lines -201 to 204
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, introducing the struct constraint here opens up the possibility for using
Unsafe.As<TUnit, int>(ref unit)
instead of
Convert.ToInt32(unit)
which is ~34x faster on net8.0 and ~92x faster on net48 🚀

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

Expand Down
4 changes: 2 additions & 2 deletions UnitsNet/CustomCode/UnitParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public UnitParser(UnitAbbreviationsCache? unitAbbreviationsCache)
/// <typeparam name="TUnitType"></typeparam>
/// <returns></returns>
public TUnitType Parse<TUnitType>(string unitAbbreviation, IFormatProvider? formatProvider = null)
where TUnitType : Enum
where TUnitType : struct, Enum
{
return (TUnitType)Parse(unitAbbreviation, typeof(TUnitType), formatProvider);
}
Expand Down Expand Up @@ -205,7 +205,7 @@ public bool TryParse([NotNullWhen(true)] string? unitAbbreviation, Type unitType
{
return false;
}

unit = matches[0].Unit;
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion UnitsNet/IArithmeticQuantity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public interface IArithmeticQuantity<TSelf, TUnitType> : IQuantity<TSelf, TUnitT
, IUnaryNegationOperators<TSelf, TSelf>
#endif
where TSelf : IArithmeticQuantity<TSelf, TUnitType>
where TUnitType : Enum
where TUnitType : struct, Enum
{
#if NET7_0_OR_GREATER
/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions UnitsNet/IQuantity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public interface IQuantity : IFormattable
/// </example>
/// <typeparam name="TUnitType">The unit type of the quantity.</typeparam>
public interface IQuantity<TUnitType> : IQuantity
where TUnitType : Enum
where TUnitType : struct, Enum
{
/// <summary>
/// Convert to a unit representation <typeparamref name="TUnitType"/>.
Expand Down Expand Up @@ -149,7 +149,7 @@ public interface IQuantity<TUnitType> : IQuantity
/// <typeparam name="TUnitType">The underlying unit enum type.</typeparam>
public interface IQuantity<in TSelf, TUnitType> : IQuantity<TUnitType>
where TSelf : IQuantity<TSelf, TUnitType>
where TUnitType : Enum
where TUnitType : struct, Enum
{
/// <summary>
/// <para>
Expand Down
6 changes: 3 additions & 3 deletions UnitsNet/QuantityDisplay.cs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is really bugging me with it's 0% coverage - I'm definitely going to do something about it soon..

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
}

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string DefaultAbbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(_quantity.Unit);
public string DefaultAbbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(_quantity.Unit.GetType(), Convert.ToInt32(_quantity.Unit));

Check warning on line 40 in UnitsNet/QuantityDisplay.cs

View check run for this annotation

Codecov / codecov/patch

UnitsNet/QuantityDisplay.cs#L40

Added line #L40 was not covered by tests

[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public string[] Abbreviations =>
Expand All @@ -55,7 +55,7 @@
public IQuantity Quantity => baseQuantity.ToUnit(Unit);

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Unit);
public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Unit.GetType(), Convert.ToInt32(Unit));

Check warning on line 58 in UnitsNet/QuantityDisplay.cs

View check run for this annotation

Codecov / codecov/patch

UnitsNet/QuantityDisplay.cs#L58

Added line #L58 was not covered by tests

public override string ToString()
{
Expand Down Expand Up @@ -129,7 +129,7 @@
internal readonly struct ConvertedQuantity(IQuantity quantity)
{
public Enum Unit => Quantity.Unit;
public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Quantity.Unit);
public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Quantity.Unit.GetType(), Convert.ToInt32(Quantity.Unit));

Check warning on line 132 in UnitsNet/QuantityDisplay.cs

View check run for this annotation

Codecov / codecov/patch

UnitsNet/QuantityDisplay.cs#L132

Added line #L132 was not covered by tests
public ValueDisplay Value => new(Quantity);
public IQuantity Quantity { get; } = quantity;

Expand Down
18 changes: 9 additions & 9 deletions UnitsNet/QuantityFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class QuantityFormatter
/// Any of the
/// <see href="https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#standard-format-specifiers">
/// Standard format specifiers
/// </see>.
/// </see>.
/// </description>
/// </item>
/// <item>
Expand Down Expand Up @@ -63,11 +63,11 @@ public class QuantityFormatter
/// <returns>The string representation.</returns>
/// <exception cref="FormatException">Thrown when the format specifier is invalid.</exception>
public static string Format<TUnitType>(IQuantity<TUnitType> quantity, string format)
where TUnitType : Enum
where TUnitType : struct, Enum
{
return Format(quantity, format, CultureInfo.CurrentCulture);
}

/// <summary>
/// Formats a quantity using the given format string and format provider.
/// </summary>
Expand All @@ -86,7 +86,7 @@ public static string Format<TUnitType>(IQuantity<TUnitType> quantity, string for
/// Any of the
/// <see href="https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#standard-format-specifiers">
/// Standard format specifiers
/// </see>.
/// </see>.
/// </description>
/// </item>
/// <item>
Expand Down Expand Up @@ -124,13 +124,13 @@ public static string Format<TUnitType>(IQuantity<TUnitType> quantity, string for
/// <returns>The string representation.</returns>
/// <exception cref="FormatException">Thrown when the format specifier is invalid.</exception>
public static string Format<TUnitType>(IQuantity<TUnitType> quantity, string? format, IFormatProvider? formatProvider)
where TUnitType : Enum
where TUnitType : struct, Enum
{
return FormatUntrimmed(quantity, format, formatProvider).TrimEnd();
}

private static string FormatUntrimmed<TUnitType>(IQuantity<TUnitType> quantity, string? format, IFormatProvider? formatProvider)
where TUnitType : Enum
where TUnitType : struct, Enum
{
formatProvider ??= CultureInfo.CurrentCulture;
if (format is null)
Expand Down Expand Up @@ -202,20 +202,20 @@ private static string FormatUntrimmed<TUnitType>(IQuantity<TUnitType> quantity,
#endif
}
}

// Anything else is a standard numeric format string with default unit abbreviation postfix.
return FormatWithValueAndAbbreviation(quantity, format, formatProvider);
}

private static string FormatWithValueAndAbbreviation<TUnitType>(IQuantity<TUnitType> quantity, string format, IFormatProvider formatProvider)
where TUnitType : Enum
where TUnitType : struct, Enum
{
var abbreviation = UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(quantity.Unit, formatProvider);
return string.Format(formatProvider, $"{{0:{format}}} {{1}}", quantity.Value, abbreviation);
}

private static string ToStringWithSignificantDigitsAfterRadix<TUnitType>(IQuantity<TUnitType> quantity, IFormatProvider formatProvider, int number)
where TUnitType : Enum
where TUnitType : struct, Enum
{
var formatForSignificantDigits = UnitFormatter.GetFormat(quantity.Value, number);
var formatArgs = UnitFormatter.GetFormatArgs(quantity.Unit, quantity.Value, formatProvider, []);
Expand Down
2 changes: 1 addition & 1 deletion UnitsNet/QuantityInfo.cs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to be really strict about it, we should consider making the QuantityInfo class abstract- this way we know that the user won't be able to implement IQuantity without having actually implemented IQuantity<TUnit>.

Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public IEnumerable<UnitInfo> GetUnitInfosFor(BaseUnits baseUnits)
/// </remarks>
/// <typeparam name="TUnit">The unit enum type, such as <see cref="LengthUnit" />. </typeparam>
public class QuantityInfo<TUnit> : QuantityInfo
where TUnit : Enum
where TUnit : struct, Enum
{
/// <inheritdoc />
public QuantityInfo(string name, UnitInfo<TUnit>[] unitInfos, TUnit baseUnit, IQuantity<TUnit> zero, BaseDimensions baseDimensions)
Expand Down
2 changes: 1 addition & 1 deletion UnitsNet/UnitFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private static bool NearlyEqual(double a, double b)
/// <param name="args">The list of format arguments.</param>
/// <returns>An array of ToString format arguments.</returns>
public static object[] GetFormatArgs<TUnitType>(TUnitType unit, double value, IFormatProvider? culture, IEnumerable<object> args)
where TUnitType : Enum
where TUnitType : struct, Enum
{
string abbreviation = UnitAbbreviationsCache.Default.GetDefaultAbbreviation(typeof(TUnitType), Convert.ToInt32(unit), culture);
return new object[] {value, abbreviation}.Concat(args).ToArray();
Expand Down
2 changes: 1 addition & 1 deletion UnitsNet/UnitInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public UnitInfo(Enum value, string pluralName, BaseUnits baseUnits, string quant
/// </remarks>
/// <typeparam name="TUnit">The unit enum type, such as <see cref="LengthUnit" />. </typeparam>
public class UnitInfo<TUnit> : UnitInfo
where TUnit : Enum
where TUnit : struct, Enum
{
/// <inheritdoc />
[Obsolete("Use the constructor that also takes a quantityName parameter.")]
Expand Down
Loading