3
3
using System . Diagnostics . CodeAnalysis ;
4
4
using System . Globalization ;
5
5
using System . Linq ;
6
+ using UnitsNet . Units ;
6
7
7
8
namespace UnitsNet
8
9
{
@@ -53,17 +54,17 @@ public static bool TryGetUnitInfo(Enum unitEnum, [NotNullWhen(true)] out UnitInf
53
54
UnitTypeAndNameToUnitInfoLazy . Value . TryGetValue ( ( unitEnum . GetType ( ) , unitEnum . ToString ( ) ) , out unitInfo ) ;
54
55
55
56
/// <summary>
56
- /// Dynamically construct a quantity.
57
+ /// Dynamically constructs a quantity from a numeric value and a unit enum value .
57
58
/// </summary>
58
59
/// <param name="value">Numeric value.</param>
59
60
/// <param name="unit">Unit enum value.</param>
60
61
/// <returns>An <see cref="IQuantity"/> object.</returns>
61
- /// <exception cref="ArgumentException ">Unit value is not a know unit enum type.</exception>
62
+ /// <exception cref="UnitNotFoundException ">Unit value is not a known unit enum type.</exception>
62
63
public static IQuantity From ( QuantityValue value , Enum unit )
63
64
{
64
65
return TryFrom ( value , unit , out IQuantity ? quantity )
65
66
? quantity
66
- : throw new ArgumentException ( $ "Unit value { unit } of type { unit . GetType ( ) } is not a known unit enum type. Expected types like UnitsNet.Units.LengthUnit. Did you pass in a third-party enum type defined outside UnitsNet library?") ;
67
+ : throw new UnitNotFoundException ( $ "Unit value { unit } of type { unit . GetType ( ) } is not a known unit enum type. Expected types like UnitsNet.Units.LengthUnit. Did you pass in a custom enum type defined outside the UnitsNet library?") ;
67
68
}
68
69
69
70
/// <summary>
@@ -73,7 +74,7 @@ public static IQuantity From(QuantityValue value, Enum unit)
73
74
/// <param name="quantityName">The invariant quantity name, such as "Length". Does not support localization.</param>
74
75
/// <param name="unitName">The invariant unit enum name, such as "Meter". Does not support localization.</param>
75
76
/// <returns>An <see cref="IQuantity"/> object.</returns>
76
- /// <exception cref="ArgumentException">Unit value is not a know unit enum type.</exception>
77
+ /// <exception cref="ArgumentException">Unit value is not a known unit enum type.</exception>
77
78
public static IQuantity From ( QuantityValue value , string quantityName , string unitName )
78
79
{
79
80
// Get enum value for this unit, f.ex. LengthUnit.Meter for unit name "Meter".
@@ -82,6 +83,57 @@ public static IQuantity From(QuantityValue value, string quantityName, string un
82
83
: throw new UnitNotFoundException ( $ "Unit [{ unitName } ] not found for quantity [{ quantityName } ].") ;
83
84
}
84
85
86
+ /// <summary>
87
+ /// Dynamically construct a quantity from a numeric value and a unit abbreviation using <see cref="CultureInfo.CurrentCulture"/>.
88
+ /// </summary>
89
+ /// <remarks>
90
+ /// This method is currently not optimized for performance and will enumerate all units and their unit abbreviations each time.<br/>
91
+ /// Unit abbreviation matching is case-insensitive.<br/>
92
+ /// <br/>
93
+ /// This will fail if more than one unit across all quantities share the same unit abbreviation.<br/>
94
+ /// Prefer <see cref="From(UnitsNet.QuantityValue,System.Enum)"/> or <see cref="From(UnitsNet.QuantityValue,string,string)"/> instead.
95
+ /// </remarks>
96
+ /// <param name="value">Numeric value.</param>
97
+ /// <param name="unitAbbreviation">Unit abbreviation, such as "kg" for <see cref="MassUnit.Kilogram"/>.</param>
98
+ /// <returns>An <see cref="IQuantity"/> object.</returns>
99
+ /// <exception cref="UnitNotFoundException">Unit abbreviation is not known.</exception>
100
+ /// <exception cref="AmbiguousUnitParseException">Multiple units found matching the given unit abbreviation.</exception>
101
+ public static IQuantity FromUnitAbbreviation ( QuantityValue value , string unitAbbreviation ) => FromUnitAbbreviation ( null , value , unitAbbreviation ) ;
102
+
103
+ /// <summary>
104
+ /// Dynamically construct a quantity from a numeric value and a unit abbreviation.
105
+ /// </summary>
106
+ /// <remarks>
107
+ /// This method is currently not optimized for performance and will enumerate all units and their unit abbreviations each time.<br/>
108
+ /// Unit abbreviation matching is case-insensitive.<br/>
109
+ /// <br/>
110
+ /// This will fail if more than one unit across all quantities share the same unit abbreviation.<br/>
111
+ /// Prefer <see cref="From(UnitsNet.QuantityValue,System.Enum)"/> or <see cref="From(UnitsNet.QuantityValue,string,string)"/> instead.
112
+ /// </remarks>
113
+ /// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
114
+ /// <param name="value">Numeric value.</param>
115
+ /// <param name="unitAbbreviation">Unit abbreviation, such as "kg" for <see cref="MassUnit.Kilogram"/>.</param>
116
+ /// <returns>An <see cref="IQuantity"/> object.</returns>
117
+ /// <exception cref="UnitNotFoundException">Unit abbreviation is not known.</exception>
118
+ /// <exception cref="AmbiguousUnitParseException">Multiple units found matching the given unit abbreviation.</exception>
119
+ public static IQuantity FromUnitAbbreviation ( IFormatProvider ? formatProvider , QuantityValue value , string unitAbbreviation )
120
+ {
121
+ // TODO Optimize this with UnitValueAbbreviationLookup via UnitAbbreviationsCache.TryGetUnitValueAbbreviationLookup.
122
+ List < Enum > units = GetUnitsForAbbreviation ( formatProvider , unitAbbreviation ) ;
123
+ if ( units . Count > 1 )
124
+ {
125
+ throw new AmbiguousUnitParseException ( $ "Multiple units found matching the given unit abbreviation: { unitAbbreviation } ") ;
126
+ }
127
+
128
+ if ( units . Count == 0 )
129
+ {
130
+ throw new UnitNotFoundException ( $ "Unit abbreviation { unitAbbreviation } is not known. Did you pass in a custom unit abbreviation defined outside the UnitsNet library? This is currently not supported.") ;
131
+ }
132
+
133
+ Enum unit = units . Single ( ) ;
134
+ return From ( value , unit ) ;
135
+ }
136
+
85
137
/// <inheritdoc cref="TryFrom(QuantityValue,System.Enum,out UnitsNet.IQuantity)"/>
86
138
public static bool TryFrom ( double value , Enum unit , [ NotNullWhen ( true ) ] out IQuantity ? quantity )
87
139
{
@@ -110,6 +162,54 @@ public static bool TryFrom(double value, string quantityName, string unitName, [
110
162
TryFrom ( value , unitValue , out quantity ) ;
111
163
}
112
164
165
+ /// <summary>
166
+ /// Dynamically construct a quantity from a numeric value and a unit abbreviation using <see cref="CultureInfo.CurrentCulture"/>.
167
+ /// </summary>
168
+ /// <remarks>
169
+ /// This method is currently not optimized for performance and will enumerate all units and their unit abbreviations each time.<br/>
170
+ /// Unit abbreviation matching is case-insensitive.<br/>
171
+ /// <br/>
172
+ /// This will fail if more than one unit across all quantities share the same unit abbreviation.<br/>
173
+ /// Prefer <see cref="From(UnitsNet.QuantityValue,System.Enum)"/> or <see cref="From(UnitsNet.QuantityValue,string,string)"/> instead.
174
+ /// </remarks>
175
+ /// <param name="value">Numeric value.</param>
176
+ /// <param name="unitAbbreviation">Unit abbreviation, such as "kg" for <see cref="MassUnit.Kilogram"/>.</param>
177
+ /// <param name="quantity">The quantity if successful, otherwise null.</param>
178
+ /// <returns>True if successful.</returns>
179
+ /// <exception cref="ArgumentException">Unit value is not a known unit enum type.</exception>
180
+ public static bool TryFromUnitAbbreviation ( QuantityValue value , string unitAbbreviation , [ NotNullWhen ( true ) ] out IQuantity ? quantity ) =>
181
+ TryFromUnitAbbreviation ( null , value , unitAbbreviation , out quantity ) ;
182
+
183
+ /// <summary>
184
+ /// Dynamically construct a quantity from a numeric value and a unit abbreviation.
185
+ /// </summary>
186
+ /// <remarks>
187
+ /// This method is currently not optimized for performance and will enumerate all units and their unit abbreviations each time.<br/>
188
+ /// Unit abbreviation matching is case-insensitive.<br/>
189
+ /// <br/>
190
+ /// This will fail if more than one unit across all quantities share the same unit abbreviation.<br/>
191
+ /// Prefer <see cref="From(UnitsNet.QuantityValue,System.Enum)"/> or <see cref="From(UnitsNet.QuantityValue,string,string)"/> instead.
192
+ /// </remarks>
193
+ /// <param name="formatProvider">The format provider to use for lookup. Defaults to <see cref="CultureInfo.CurrentCulture" /> if null.</param>
194
+ /// <param name="value">Numeric value.</param>
195
+ /// <param name="unitAbbreviation">Unit abbreviation, such as "kg" for <see cref="MassUnit.Kilogram"/>.</param>
196
+ /// <param name="quantity">The quantity if successful, otherwise null.</param>
197
+ /// <returns>True if successful.</returns>
198
+ /// <exception cref="ArgumentException">Unit value is not a known unit enum type.</exception>
199
+ public static bool TryFromUnitAbbreviation ( IFormatProvider ? formatProvider , QuantityValue value , string unitAbbreviation , [ NotNullWhen ( true ) ] out IQuantity ? quantity )
200
+ {
201
+ // TODO Optimize this with UnitValueAbbreviationLookup via UnitAbbreviationsCache.TryGetUnitValueAbbreviationLookup.
202
+ List < Enum > units = GetUnitsForAbbreviation ( formatProvider , unitAbbreviation ) ;
203
+ if ( units . Count == 1 )
204
+ {
205
+ Enum ? unit = units . SingleOrDefault ( ) ;
206
+ return TryFrom ( value , unit , out quantity ) ;
207
+ }
208
+
209
+ quantity = default ;
210
+ return false ;
211
+ }
212
+
113
213
/// <inheritdoc cref="Parse(IFormatProvider, System.Type,string)"/>
114
214
public static IQuantity Parse ( Type quantityType , string quantityString ) => Parse ( null , quantityType , quantityString ) ;
115
215
@@ -121,6 +221,7 @@ public static bool TryFrom(double value, string quantityName, string unitName, [
121
221
/// <param name="quantityString">Quantity string representation, such as "1.5 kg". Must be compatible with given quantity type.</param>
122
222
/// <returns>The parsed quantity.</returns>
123
223
/// <exception cref="ArgumentException">Type must be of type UnitsNet.IQuantity -or- Type is not a known quantity type.</exception>
224
+ /// <exception cref="UnitNotFoundException">Type must be of type UnitsNet.IQuantity -or- Type is not a known quantity type.</exception>
124
225
public static IQuantity Parse ( IFormatProvider ? formatProvider , Type quantityType , string quantityString )
125
226
{
126
227
if ( ! typeof ( IQuantity ) . IsAssignableFrom ( quantityType ) )
@@ -129,7 +230,7 @@ public static IQuantity Parse(IFormatProvider? formatProvider, Type quantityType
129
230
if ( TryParse ( formatProvider , quantityType , quantityString , out IQuantity ? quantity ) )
130
231
return quantity ;
131
232
132
- throw new ArgumentException ( $ "Quantity string could not be parsed to quantity { quantityType } .") ;
233
+ throw new UnitNotFoundException ( $ "Quantity string could not be parsed to quantity { quantityType } .") ;
133
234
}
134
235
135
236
/// <inheritdoc cref="TryParse(IFormatProvider,System.Type,string,out UnitsNet.IQuantity)"/>
@@ -144,5 +245,23 @@ public static IEnumerable<QuantityInfo> GetQuantitiesWithBaseDimensions(BaseDime
144
245
{
145
246
return InfosLazy . Value . Where ( info => info . BaseDimensions . Equals ( baseDimensions ) ) ;
146
247
}
248
+
249
+ private static List < Enum > GetUnitsForAbbreviation ( IFormatProvider ? formatProvider , string unitAbbreviation )
250
+ {
251
+ // Use case-sensitive match to reduce ambiguity.
252
+ // Don't use UnitParser.TryParse() here, since it allows case-insensitive match per quantity as long as there are no ambiguous abbreviations for
253
+ // units of that quantity, but here we try all quantities and this results in too high of a chance for ambiguous matches,
254
+ // such as "cm" matching both LengthUnit.Centimeter (cm) and MolarityUnit.CentimolePerLiter (cM).
255
+ return Infos
256
+ . SelectMany ( i => i . UnitInfos )
257
+ . Select ( ui => UnitAbbreviationsCache . Default
258
+ . GetUnitAbbreviations ( ui . Value . GetType ( ) , Convert . ToInt32 ( ui . Value ) , formatProvider )
259
+ . Contains ( unitAbbreviation , StringComparer . Ordinal )
260
+ ? ui . Value
261
+ : null )
262
+ . Where ( unitValue => unitValue != null )
263
+ . Select ( unitValue => unitValue ! )
264
+ . ToList ( ) ;
265
+ }
147
266
}
148
267
}
0 commit comments