Skip to content

Commit

Permalink
💥Require unit enum types to also be struct (#1472)
Browse files Browse the repository at this point in the history
Change all `where TUnitType : Enum` generic type constraints to include
`struct` constraint, to avoid usages like this.

```cs
UnitsNetSetup.Default.UnitParser.Parse<LengthUnit>("abc"); // Ok
UnitsNetSetup.Default.UnitParser.Parse<Enum>("abc"); // Compiles, but throws exception
```

This also fixes inconsistency, between
`UnitAbbreviationsCache.GetUnitAbbreviations<TUnitType>` and
`UnitAbbreviationsCache.GetDefaultAbbreviation<TUnitType>`.

### Changes
- Add `struct` constraint to all `Enum` constraints
- Remove two test cases
`MapAndLookup_MapWithSpecificEnumType_LookupWithEnumType`,
`MapAndLookup_WithEnumType`

### Noteworthy

There are some minor use cases for passing non-generic `Enum` value,
like getting unit abbreviations for an `IQuantity` such as
`UnitAbbreviations.GetDefaultAbbreviation(quantity.Unit)`.
After this PR, you now have to do
`GetDefaultAbbreviation(quantity.Unit.GetType(),
Convert.ToInt32(quantity.Unit))` instead.
`GetAbbreviations()` already had this requirement, so now it's more
consistent at least.

### Sources
https://stackoverflow.com/a/74773273

https://devblogs.microsoft.com/premier-developer/dissecting-new-generics-constraints-in-c-7-3/
  • Loading branch information
angularsen authored Dec 29, 2024
1 parent 8b3696b commit 3f1456d
Show file tree
Hide file tree
Showing 12 changed files with 35 additions and 52 deletions.
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", UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(HowMuchUnit.Some));
}

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

/// <inheritdoc cref="MapAndLookup_WithSpecificEnumType"/>
[Fact]
public void MapAndLookup_MapWithSpecificEnumType_LookupWithEnumType()
{
UnitsNetSetup.Default.UnitAbbreviations.MapUnitToDefaultAbbreviation(HowMuchUnit.Some, "sm");
Assert.Equal("sm", UnitsNetSetup.Default.UnitAbbreviations.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 @@ -61,7 +61,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 @@ -148,7 +148,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 @@ -175,7 +175,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 @@ -236,7 +236,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 @@ -245,7 +245,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 @@ -71,7 +71,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 @@ -84,7 +84,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 @@ -98,7 +98,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 @@ -112,7 +112,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 @@ -166,7 +166,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 @@ -198,7 +198,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);
}
Expand Down
2 changes: 1 addition & 1 deletion UnitsNet/CustomCode/UnitParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,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
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public AbbreviationDisplay(IQuantity quantity)
}

[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));

[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public string[] Abbreviations =>
Expand All @@ -55,7 +55,7 @@ internal readonly struct ConvertedQuantity(IQuantity baseQuantity, Enum unit)
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));

public override string ToString()
{
Expand Down Expand Up @@ -129,7 +129,7 @@ internal readonly struct StringFormatsDisplay(IQuantity quantity)
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));
public ValueDisplay Value => new(Quantity);
public IQuantity Quantity { get; } = quantity;

Expand Down
10 changes: 5 additions & 5 deletions UnitsNet/QuantityFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ 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);
}
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 @@ -208,14 +208,14 @@ private static string FormatUntrimmed<TUnitType>(IQuantity<TUnitType> quantity,
}

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
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 = UnitsNetSetup.Default.UnitAbbreviations.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

0 comments on commit 3f1456d

Please sign in to comment.