Skip to content

Commit 3f1456d

Browse files
authored
💥Require unit enum types to also be struct (#1472)
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/
1 parent 8b3696b commit 3f1456d

12 files changed

+35
-52
lines changed

UnitsNet.Tests/UnitAbbreviationsCacheTests.cs

-17
Original file line numberDiff line numberDiff line change
@@ -404,23 +404,6 @@ public void MapAndLookup_WithSpecificEnumType()
404404
Assert.Equal("sm", UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(HowMuchUnit.Some));
405405
}
406406

407-
/// <inheritdoc cref="MapAndLookup_WithSpecificEnumType"/>
408-
[Fact]
409-
public void MapAndLookup_WithEnumType()
410-
{
411-
Enum valueAsEnumType = HowMuchUnit.Some;
412-
UnitsNetSetup.Default.UnitAbbreviations.MapUnitToDefaultAbbreviation(valueAsEnumType, "sm");
413-
Assert.Equal("sm", UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(valueAsEnumType));
414-
}
415-
416-
/// <inheritdoc cref="MapAndLookup_WithSpecificEnumType"/>
417-
[Fact]
418-
public void MapAndLookup_MapWithSpecificEnumType_LookupWithEnumType()
419-
{
420-
UnitsNetSetup.Default.UnitAbbreviations.MapUnitToDefaultAbbreviation(HowMuchUnit.Some, "sm");
421-
Assert.Equal("sm", UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation((Enum)HowMuchUnit.Some));
422-
}
423-
424407
/// <summary>
425408
/// Convenience method to the proper culture parameter type.
426409
/// </summary>

UnitsNet/CustomCode/QuantityParser.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace UnitsNet
1919
/// <typeparam name="TUnitType">The type of unit enum that belongs to this quantity, such as <see cref="LengthUnit"/> for <see cref="Length"/>.</typeparam>
2020
public delegate TQuantity QuantityFromDelegate<out TQuantity, in TUnitType>(double value, TUnitType fromUnit)
2121
where TQuantity : IQuantity
22-
where TUnitType : Enum;
22+
where TUnitType : struct, Enum;
2323

2424
/// <summary>
2525
/// Parses quantities from strings, such as "1.2 kg" to <see cref="Length"/> or "100 cm" to <see cref="Mass"/>.
@@ -61,7 +61,7 @@ public TQuantity Parse<TQuantity, TUnitType>(string str,
6161
IFormatProvider? formatProvider,
6262
QuantityFromDelegate<TQuantity, TUnitType> fromDelegate)
6363
where TQuantity : IQuantity
64-
where TUnitType : Enum
64+
where TUnitType : struct, Enum
6565
{
6666
if (str == null) throw new ArgumentNullException(nameof(str));
6767
str = str.Trim();
@@ -148,7 +148,7 @@ internal string CreateRegexPatternForUnit<TUnitType>(
148148
TUnitType unit,
149149
IFormatProvider? formatProvider,
150150
bool matchEntireString = true)
151-
where TUnitType : Enum
151+
where TUnitType : struct, Enum
152152
{
153153
var unitAbbreviations = _unitAbbreviationsCache.GetUnitAbbreviations(unit, formatProvider);
154154
var pattern = GetRegexPatternForUnitAbbreviations(unitAbbreviations);
@@ -175,7 +175,7 @@ private TQuantity ParseWithRegex<TQuantity, TUnitType>(string valueString,
175175
QuantityFromDelegate<TQuantity, TUnitType> fromDelegate,
176176
IFormatProvider? formatProvider)
177177
where TQuantity : IQuantity
178-
where TUnitType : Enum
178+
where TUnitType : struct, Enum
179179
{
180180
var value = double.Parse(valueString, ParseNumberStyles, formatProvider);
181181
var parsedUnit = _unitParser.Parse<TUnitType>(unitString, formatProvider);
@@ -236,7 +236,7 @@ private static bool TryExtractValueAndUnit(Regex regex, string str, [NotNullWhen
236236
return true;
237237
}
238238

239-
private string CreateRegexPatternForQuantity<TUnitType>(IFormatProvider? formatProvider) where TUnitType : Enum
239+
private string CreateRegexPatternForQuantity<TUnitType>(IFormatProvider? formatProvider) where TUnitType : struct, Enum
240240
{
241241
var unitAbbreviations = _unitAbbreviationsCache.GetAllUnitAbbreviationsForQuantity(typeof(TUnitType), formatProvider);
242242
var pattern = GetRegexPatternForUnitAbbreviations(unitAbbreviations);
@@ -245,7 +245,7 @@ private string CreateRegexPatternForQuantity<TUnitType>(IFormatProvider? formatP
245245
return $"^{pattern}$";
246246
}
247247

248-
private Regex CreateRegexForQuantity<TUnitType>(IFormatProvider? formatProvider) where TUnitType : Enum
248+
private Regex CreateRegexForQuantity<TUnitType>(IFormatProvider? formatProvider) where TUnitType : struct, Enum
249249
{
250250
var pattern = CreateRegexPatternForQuantity<TUnitType>(formatProvider);
251251
return new Regex(pattern, RegexOptions.Singleline | RegexOptions.IgnoreCase);

UnitsNet/CustomCode/UnitAbbreviationsCache.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ internal UnitAbbreviationsCache(QuantityInfoLookup quantityInfoLookup)
7171
/// <param name="unit">The unit enum value.</param>
7272
/// <param name="abbreviations">Unit abbreviations to add.</param>
7373
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
74-
public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, params string[] abbreviations) where TUnitType : Enum
74+
public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, params string[] abbreviations) where TUnitType : struct, Enum
7575
{
7676
PerformAbbreviationMapping(unit, CultureInfo.CurrentCulture, false, abbreviations);
7777
}
@@ -84,7 +84,7 @@ public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, params string[] abb
8484
/// <param name="unit">The unit enum value.</param>
8585
/// <param name="abbreviation">Unit abbreviations to add as default.</param>
8686
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
87-
public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, string abbreviation) where TUnitType : Enum
87+
public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, string abbreviation) where TUnitType : struct, Enum
8888
{
8989
PerformAbbreviationMapping(unit, CultureInfo.CurrentCulture, true, abbreviation);
9090
}
@@ -98,7 +98,7 @@ public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, string abbre
9898
/// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
9999
/// <param name="abbreviations">Unit abbreviations to add.</param>
100100
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
101-
public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider, params string[] abbreviations) where TUnitType : Enum
101+
public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider, params string[] abbreviations) where TUnitType : struct, Enum
102102
{
103103
PerformAbbreviationMapping(unit, formatProvider, false, abbreviations);
104104
}
@@ -112,7 +112,7 @@ public void MapUnitToAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? fo
112112
/// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
113113
/// <param name="abbreviation">Unit abbreviation to add as default.</param>
114114
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
115-
public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider, string abbreviation) where TUnitType : Enum
115+
public void MapUnitToDefaultAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider, string abbreviation) where TUnitType : struct, Enum
116116
{
117117
PerformAbbreviationMapping(unit, formatProvider, true, abbreviation);
118118
}
@@ -166,7 +166,7 @@ private void PerformAbbreviationMapping(Enum unitValue, IFormatProvider? formatP
166166
/// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
167167
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
168168
/// <returns>The default unit abbreviation string.</returns>
169-
public string GetDefaultAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : Enum
169+
public string GetDefaultAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : struct, Enum
170170
{
171171
Type unitType = typeof(TUnitType);
172172

@@ -198,7 +198,7 @@ public string GetDefaultAbbreviation(Type unitType, int unitValue, IFormatProvid
198198
/// <param name="unit">Enum value for unit.</param>
199199
/// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
200200
/// <returns>Unit abbreviations associated with unit.</returns>
201-
public string[] GetUnitAbbreviations<TUnitType>(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : Enum
201+
public string[] GetUnitAbbreviations<TUnitType>(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : struct, Enum
202202
{
203203
return GetUnitAbbreviations(typeof(TUnitType), Convert.ToInt32(unit), formatProvider);
204204
}

UnitsNet/CustomCode/UnitParser.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public UnitParser(UnitAbbreviationsCache unitAbbreviationsCache)
3939
/// <typeparam name="TUnitType"></typeparam>
4040
/// <returns></returns>
4141
public TUnitType Parse<TUnitType>(string unitAbbreviation, IFormatProvider? formatProvider = null)
42-
where TUnitType : Enum
42+
where TUnitType : struct, Enum
4343
{
4444
return (TUnitType)Parse(unitAbbreviation, typeof(TUnitType), formatProvider);
4545
}

UnitsNet/IArithmeticQuantity.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public interface IArithmeticQuantity<TSelf, TUnitType> : IQuantity<TSelf, TUnitT
2121
, IUnaryNegationOperators<TSelf, TSelf>
2222
#endif
2323
where TSelf : IArithmeticQuantity<TSelf, TUnitType>
24-
where TUnitType : Enum
24+
where TUnitType : struct, Enum
2525
{
2626
#if NET7_0_OR_GREATER
2727
/// <summary>

UnitsNet/IQuantity.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public interface IQuantity : IFormattable
111111
/// </example>
112112
/// <typeparam name="TUnitType">The unit type of the quantity.</typeparam>
113113
public interface IQuantity<TUnitType> : IQuantity
114-
where TUnitType : Enum
114+
where TUnitType : struct, Enum
115115
{
116116
/// <summary>
117117
/// Convert to a unit representation <typeparamref name="TUnitType"/>.
@@ -149,7 +149,7 @@ public interface IQuantity<TUnitType> : IQuantity
149149
/// <typeparam name="TUnitType">The underlying unit enum type.</typeparam>
150150
public interface IQuantity<in TSelf, TUnitType> : IQuantity<TUnitType>
151151
where TSelf : IQuantity<TSelf, TUnitType>
152-
where TUnitType : Enum
152+
where TUnitType : struct, Enum
153153
{
154154
/// <summary>
155155
/// <para>

UnitsNet/QuantityDisplay.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public AbbreviationDisplay(IQuantity quantity)
3737
}
3838

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

4242
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
4343
public string[] Abbreviations =>
@@ -55,7 +55,7 @@ internal readonly struct ConvertedQuantity(IQuantity baseQuantity, Enum unit)
5555
public IQuantity Quantity => baseQuantity.ToUnit(Unit);
5656

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

6060
public override string ToString()
6161
{
@@ -129,7 +129,7 @@ internal readonly struct StringFormatsDisplay(IQuantity quantity)
129129
internal readonly struct ConvertedQuantity(IQuantity quantity)
130130
{
131131
public Enum Unit => Quantity.Unit;
132-
public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Quantity.Unit);
132+
public string Abbreviation => UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(Quantity.Unit.GetType(), Convert.ToInt32(Quantity.Unit));
133133
public ValueDisplay Value => new(Quantity);
134134
public IQuantity Quantity { get; } = quantity;
135135

UnitsNet/QuantityFormatter.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public class QuantityFormatter
6363
/// <returns>The string representation.</returns>
6464
/// <exception cref="FormatException">Thrown when the format specifier is invalid.</exception>
6565
public static string Format<TUnitType>(IQuantity<TUnitType> quantity, string format)
66-
where TUnitType : Enum
66+
where TUnitType : struct, Enum
6767
{
6868
return Format(quantity, format, CultureInfo.CurrentCulture);
6969
}
@@ -124,13 +124,13 @@ public static string Format<TUnitType>(IQuantity<TUnitType> quantity, string for
124124
/// <returns>The string representation.</returns>
125125
/// <exception cref="FormatException">Thrown when the format specifier is invalid.</exception>
126126
public static string Format<TUnitType>(IQuantity<TUnitType> quantity, string? format, IFormatProvider? formatProvider)
127-
where TUnitType : Enum
127+
where TUnitType : struct, Enum
128128
{
129129
return FormatUntrimmed(quantity, format, formatProvider).TrimEnd();
130130
}
131131

132132
private static string FormatUntrimmed<TUnitType>(IQuantity<TUnitType> quantity, string? format, IFormatProvider? formatProvider)
133-
where TUnitType : Enum
133+
where TUnitType : struct, Enum
134134
{
135135
formatProvider ??= CultureInfo.CurrentCulture;
136136
if (format is null)
@@ -208,14 +208,14 @@ private static string FormatUntrimmed<TUnitType>(IQuantity<TUnitType> quantity,
208208
}
209209

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

217217
private static string ToStringWithSignificantDigitsAfterRadix<TUnitType>(IQuantity<TUnitType> quantity, IFormatProvider formatProvider, int number)
218-
where TUnitType : Enum
218+
where TUnitType : struct, Enum
219219
{
220220
var formatForSignificantDigits = UnitFormatter.GetFormat(quantity.Value, number);
221221
var formatArgs = UnitFormatter.GetFormatArgs(quantity.Unit, quantity.Value, formatProvider, []);

UnitsNet/QuantityInfo.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public IEnumerable<UnitInfo> GetUnitInfosFor(BaseUnits baseUnits)
130130
/// </remarks>
131131
/// <typeparam name="TUnit">The unit enum type, such as <see cref="LengthUnit" />. </typeparam>
132132
public class QuantityInfo<TUnit> : QuantityInfo
133-
where TUnit : Enum
133+
where TUnit : struct, Enum
134134
{
135135
/// <inheritdoc />
136136
public QuantityInfo(string name, UnitInfo<TUnit>[] unitInfos, TUnit baseUnit, IQuantity<TUnit> zero, BaseDimensions baseDimensions)

UnitsNet/UnitFormatter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private static bool NearlyEqual(double a, double b)
7171
/// <param name="args">The list of format arguments.</param>
7272
/// <returns>An array of ToString format arguments.</returns>
7373
public static object[] GetFormatArgs<TUnitType>(TUnitType unit, double value, IFormatProvider? culture, IEnumerable<object> args)
74-
where TUnitType : Enum
74+
where TUnitType : struct, Enum
7575
{
7676
string abbreviation = UnitsNetSetup.Default.UnitAbbreviations.GetDefaultAbbreviation(typeof(TUnitType), Convert.ToInt32(unit), culture);
7777
return new object[] {value, abbreviation}.Concat(args).ToArray();

UnitsNet/UnitInfo.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public UnitInfo(Enum value, string pluralName, BaseUnits baseUnits, string quant
8282
/// </remarks>
8383
/// <typeparam name="TUnit">The unit enum type, such as <see cref="LengthUnit" />. </typeparam>
8484
public class UnitInfo<TUnit> : UnitInfo
85-
where TUnit : Enum
85+
where TUnit : struct, Enum
8686
{
8787
/// <inheritdoc />
8888
[Obsolete("Use the constructor that also takes a quantityName parameter.")]

0 commit comments

Comments
 (0)