Skip to content

Commit 73f53bd

Browse files
authored
🐛 Fix unit abbreviation lookup for unit value cast to Enum (#1302)
Fixes #1301 When looking up unit abbreviation for a unit enum value that is cast to `Enum`, for example via variable or method parameter, the lookup failed due to using `typeof(TUnitEnum)` in the generic method. `Enum` satisfies the generic constraint, but the generic type no longer describes the original unit enum type. Instead, we must use `unitEnumValue.GetType()`. ``` System.ArgumentException: Type provided must be an Enum. at System.Enum.GetEnumInfo(RuntimeType enumType, Boolean getNames) at System.RuntimeType.GetEnumName(Object value) at UnitsNet.UnitAbbreviationsCache.TryGetUnitAbbreviations(Type unitType, Int32 unitValue, IFormatProvider formatProvider, String[]& abbreviations) in C:\dev\unitsnet\UnitsNet\CustomCode\UnitAbbreviationsCache.cs:line 246 ``` ### Changes - Handle edge-case when `Enum` type is passed instead of an actual unit enum type
1 parent e2720b0 commit 73f53bd

File tree

2 files changed

+41
-8
lines changed

2 files changed

+41
-8
lines changed

UnitsNet.Tests/UnitAbbreviationsCacheTests.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -280,26 +280,55 @@ public void MapUnitToAbbreviation_DoesNotInsertDuplicates()
280280
{
281281
var cache = new UnitAbbreviationsCache();
282282

283-
cache.MapUnitToAbbreviation(HowMuchUnit.Some, "soome");
284-
cache.MapUnitToAbbreviation(HowMuchUnit.Some, "soome");
285-
cache.MapUnitToAbbreviation(HowMuchUnit.Some, "soome");
283+
cache.MapUnitToAbbreviation(HowMuchUnit.Some, "sm");
284+
cache.MapUnitToAbbreviation(HowMuchUnit.Some, "sm");
285+
cache.MapUnitToAbbreviation(HowMuchUnit.Some, "sm");
286286

287-
Assert.Equal("soome", cache.GetDefaultAbbreviation(HowMuchUnit.Some));
288-
Assert.Equal(new[] { "soome" }, cache.GetUnitAbbreviations(HowMuchUnit.Some));
289-
Assert.Equal(new[] { "soome" }, cache.GetAllUnitAbbreviationsForQuantity(typeof(HowMuchUnit)));
287+
Assert.Equal("sm", cache.GetDefaultAbbreviation(HowMuchUnit.Some));
288+
Assert.Equal(new[] { "sm" }, cache.GetUnitAbbreviations(HowMuchUnit.Some));
289+
Assert.Equal(new[] { "sm" }, cache.GetAllUnitAbbreviationsForQuantity(typeof(HowMuchUnit)));
290290
}
291291

292292
[Fact]
293293
public void MapUnitToDefaultAbbreviation_Twice_SetsNewDefaultAndKeepsOrderOfExistingAbbreviations()
294294
{
295295
var cache = new UnitAbbreviationsCache();
296296

297-
cache.MapUnitToAbbreviation(HowMuchUnit.Some, "soome");
297+
cache.MapUnitToAbbreviation(HowMuchUnit.Some, "sm");
298298
cache.MapUnitToDefaultAbbreviation(HowMuchUnit.Some, "1st default");
299299
cache.MapUnitToDefaultAbbreviation(HowMuchUnit.Some, "2nd default");
300300

301301
Assert.Equal("2nd default", cache.GetDefaultAbbreviation(HowMuchUnit.Some));
302-
Assert.Equal(new[] { "2nd default", "1st default", "soome" }, cache.GetUnitAbbreviations(HowMuchUnit.Some));
302+
Assert.Equal(new[] { "2nd default", "1st default", "sm" }, cache.GetUnitAbbreviations(HowMuchUnit.Some));
303+
}
304+
305+
/// <summary>
306+
/// Test that lookup works when specifying unit enum value both as cast to <see cref="Enum"/> and as specific enum value type.
307+
/// We have had subtle bugs when <see cref="Enum"/> is passed to generic methods with constraint TUnitEnum : Enum,
308+
/// which the Enum type satisfies, but trying to use typeof(TUnitEnum) no longer represent the actual enum type so unitEnumValue.GetType() should be used.
309+
/// </summary>
310+
[Fact]
311+
public void MapAndLookup_WithSpecificEnumType()
312+
{
313+
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(HowMuchUnit.Some, "sm");
314+
Assert.Equal("sm", UnitAbbreviationsCache.Default.GetDefaultAbbreviation(HowMuchUnit.Some));
315+
}
316+
317+
/// <inheritdoc cref="MapAndLookup_WithSpecificEnumType"/>
318+
[Fact]
319+
public void MapAndLookup_WithEnumType()
320+
{
321+
Enum valueAsEnumType = HowMuchUnit.Some;
322+
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(valueAsEnumType, "sm");
323+
Assert.Equal("sm", UnitAbbreviationsCache.Default.GetDefaultAbbreviation(valueAsEnumType));
324+
}
325+
326+
/// <inheritdoc cref="MapAndLookup_WithSpecificEnumType"/>
327+
[Fact]
328+
public void MapAndLookup_MapWithSpecificEnumType_LookupWithEnumType()
329+
{
330+
UnitAbbreviationsCache.Default.MapUnitToDefaultAbbreviation(HowMuchUnit.Some, "sm");
331+
Assert.Equal("sm", UnitAbbreviationsCache.Default.GetDefaultAbbreviation((Enum)HowMuchUnit.Some));
303332
}
304333

305334
/// <summary>

UnitsNet/CustomCode/UnitAbbreviationsCache.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ private void PerformAbbreviationMapping(Enum unitValue, IFormatProvider? formatP
187187
public string GetDefaultAbbreviation<TUnitType>(TUnitType unit, IFormatProvider? formatProvider = null) where TUnitType : Enum
188188
{
189189
Type unitType = typeof(TUnitType);
190+
191+
// Edge-case: If the value was cast to Enum, it still satisfies the generic constraint so we must get the type from the value instead.
192+
if (unitType == typeof(Enum)) unitType = unit.GetType();
193+
190194
return GetDefaultAbbreviation(unitType, Convert.ToInt32(unit), formatProvider);
191195
}
192196

0 commit comments

Comments
 (0)