Skip to content

Commit e3d84e5

Browse files
tmilnthorpangularsen
authored andcommitted
Remove reflection (#618)
* UnitConverter no longer needs reflection due to IQuantity and QuantityInfo. * Reflection no longer needed in UnitsNetJsonConverter due to IQuantity and QuantityInfo * Updating doc
1 parent b61b9a7 commit e3d84e5

File tree

4 files changed

+29
-244
lines changed

4 files changed

+29
-244
lines changed

UnitsNet.Serialization.JsonNet/UnitsNetJsonConverter.cs

+7-141
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@
22
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
33

44
using System;
5-
using System.Linq;
6-
using System.Reflection;
75
using JetBrains.Annotations;
86
using Newtonsoft.Json;
97
using Newtonsoft.Json.Linq;
10-
using UnitsNet.Serialization.JsonNet.Internal;
11-
using UnitsNet.Units;
128

139
namespace UnitsNet.Serialization.JsonNet
1410
{
@@ -26,11 +22,6 @@ namespace UnitsNet.Serialization.JsonNet
2622
/// </remarks>
2723
public class UnitsNetJsonConverter : JsonConverter
2824
{
29-
/// <summary>
30-
/// Numeric value field of a quantity, typically of type double or decimal.
31-
/// </summary>
32-
private const string ValueFieldName = "_value";
33-
3425
/// <summary>
3526
/// Reads the JSON representation of the object.
3627
/// </summary>
@@ -46,29 +37,20 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
4637
JsonSerializer serializer)
4738
{
4839
if (reader.ValueType != null)
49-
{
5040
return reader.Value;
51-
}
41+
5242
object obj = TryDeserializeIComparable(reader, serializer);
5343
// A null System.Nullable value or a comparable type was deserialized so return this
5444
if (!(obj is ValueUnit vu))
55-
{
5645
return obj;
57-
}
5846

5947
// "MassUnit.Kilogram" => "MassUnit" and "Kilogram"
6048
string unitEnumTypeName = vu.Unit.Split('.')[0];
6149
string unitEnumValue = vu.Unit.Split('.')[1];
6250

63-
// "MassUnit" => "Mass"
64-
string quantityTypeName = unitEnumTypeName.Substring(0, unitEnumTypeName.Length - "Unit".Length);
65-
6651
// "UnitsNet.Units.MassUnit,UnitsNet"
6752
string unitEnumTypeAssemblyQualifiedName = "UnitsNet.Units." + unitEnumTypeName + ",UnitsNet";
6853

69-
// "UnitsNet.Mass,UnitsNet"
70-
string quantityTypeAssemblyQualifiedName = "UnitsNet." + quantityTypeName + ",UnitsNet";
71-
7254
// -- see http://stackoverflow.com/a/6465096/1256096 for details
7355
Type unitEnumType = Type.GetType(unitEnumTypeAssemblyQualifiedName);
7456
if (unitEnumType == null)
@@ -78,63 +60,10 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
7860
throw ex;
7961
}
8062

81-
Type quantityType = Type.GetType(quantityTypeAssemblyQualifiedName);
82-
if (quantityType == null)
83-
{
84-
var ex = new UnitsNetException("Unable to find unit type.");
85-
ex.Data["type"] = quantityTypeAssemblyQualifiedName;
86-
throw ex;
87-
}
88-
8963
double value = vu.Value;
90-
object unitValue = Enum.Parse(unitEnumType, unitEnumValue); // Ex: MassUnit.Kilogram
91-
92-
return CreateQuantity(quantityType, value, unitValue);
93-
}
94-
95-
/// <summary>
96-
/// Creates a quantity (ex: Mass) based on the reflected quantity type, a numeric value and a unit value (ex: MassUnit.Kilogram).
97-
/// </summary>
98-
/// <param name="quantityType">Type of quantity, such as <see cref="Mass"/>.</param>
99-
/// <param name="value">Numeric value.</param>
100-
/// <param name="unitValue">The unit, such as <see cref="MassUnit.Kilogram"/>.</param>
101-
/// <returns>The constructed quantity, such as <see cref="Mass"/>.</returns>
102-
private static object CreateQuantity(Type quantityType, double value, object unitValue)
103-
{
104-
// We want the non-nullable return type, example candidates if quantity type is Mass:
105-
// double Mass.From(double, MassUnit)
106-
// double? Mass.From(double?, MassUnit)
107-
MethodInfo notNullableFromMethod = quantityType
108-
.GetDeclaredMethods()
109-
.Single(m => m.Name == "From" && Nullable.GetUnderlyingType(m.ReturnType) == null);
110-
111-
// Of type QuantityValue
112-
object quantityValue = GetFromMethodValueArgument(notNullableFromMethod, value);
113-
114-
// Ex: Mass.From(55, MassUnit.Gram)
115-
// See ValueUnit about precision loss for quantities using decimal type.
116-
return notNullableFromMethod.Invoke(null, new[] {quantityValue, unitValue});
117-
}
118-
119-
/// <summary>
120-
/// Returns numeric value wrapped as <see cref="QuantityValue"/>, based on the type of argument
121-
/// of <paramref name="fromMethod"/>. Today this is always <see cref="QuantityValue"/>, but
122-
/// we may extend to other types later such as QuantityValueDecimal.
123-
/// </summary>
124-
/// <param name="fromMethod">The reflected From(value, unit) method.</param>
125-
/// <param name="value">The value to convert to the correct wrapper type.</param>
126-
/// <returns></returns>
127-
private static object GetFromMethodValueArgument(MethodInfo fromMethod, double value)
128-
{
129-
Type valueParameterType = fromMethod.GetParameters()[0].ParameterType;
130-
if (valueParameterType == typeof(QuantityValue))
131-
{
132-
// We use this type that takes implicit cast from all number types to avoid explosion of method overloads that take a numeric value.
133-
return (QuantityValue) value;
134-
}
64+
Enum unitValue = (Enum)Enum.Parse(unitEnumType, unitEnumValue); // Ex: MassUnit.Kilogram
13565

136-
throw new Exception(
137-
$"The first parameter of the reflected quantity From() method was expected to be either UnitsNet.QuantityValue, but was instead {valueParameterType}.");
66+
return Quantity.From(value, unitValue);
13867
}
13968

14069
private static object TryDeserializeIComparable(JsonReader reader, JsonSerializer serializer)
@@ -182,74 +111,14 @@ public override void WriteJson(JsonWriter writer, object obj, JsonSerializer ser
182111
return;
183112
}
184113

185-
object quantityValue = GetValueOfQuantity(obj, quantityType); // double or decimal value
186-
string quantityUnitName = GetUnitFullNameOfQuantity(obj, quantityType); // Example: "MassUnit.Kilogram"
114+
IQuantity quantity = obj as IQuantity;
187115

188116
serializer.Serialize(writer, new ValueUnit
189117
{
190118
// See ValueUnit about precision loss for quantities using decimal type.
191-
Value = Convert.ToDouble(quantityValue),
192-
Unit = quantityUnitName
193-
});
194-
}
195-
196-
/// <summary>
197-
/// Given quantity (ex: <see cref="Mass"/>), returns the full name (ex: "MassUnit.Kilogram") of the constructed unit given by the <see cref="Mass.Unit"/> property.
198-
/// </summary>
199-
/// <param name="obj">Quantity, such as <see cref="Mass"/>.</param>
200-
/// <param name="quantityType">The type of <paramref name="obj"/>, passed in here to reuse a previous lookup.</param>
201-
/// <returns>"MassUnit.Kilogram" for a mass quantity whose Unit property is MassUnit.Kilogram.</returns>
202-
private static string GetUnitFullNameOfQuantity(object obj, Type quantityType)
203-
{
204-
// Get value of Unit property
205-
PropertyInfo unitProperty = quantityType.GetProperty("Unit");
206-
Enum quantityUnit = (Enum) unitProperty.GetValue(obj, null); // MassUnit.Kilogram
207-
208-
Type unitType = quantityUnit.GetType(); // MassUnit
209-
return $"{unitType.Name}.{quantityUnit}"; // "MassUnit.Kilogram"
210-
}
211-
212-
private static object GetValueOfQuantity(object value, Type quantityType)
213-
{
214-
FieldInfo valueField = GetPrivateInstanceField(quantityType, ValueFieldName);
215-
216-
// See ValueUnit about precision loss for quantities using decimal type.
217-
object quantityValue = valueField.GetValue(value);
218-
return quantityValue;
219-
}
220-
221-
private static FieldInfo GetPrivateInstanceField(Type quantityType, string fieldName)
222-
{
223-
FieldInfo baseValueField;
224-
try
225-
{
226-
baseValueField = quantityType
227-
#if (NETSTANDARD1_0)
228-
.GetTypeInfo()
229-
.DeclaredFields
230-
.Where(f => !f.IsPublic && !f.IsStatic)
231-
#else
232-
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)
233-
#endif
234-
.SingleOrDefault(f => f.Name == fieldName);
235-
}
236-
catch (InvalidOperationException)
237-
{
238-
var ex = new UnitsNetException($"Expected exactly one private field named [{fieldName}], but found multiple.");
239-
ex.Data["type"] = quantityType;
240-
ex.Data["fieldName"] = fieldName;
241-
throw ex;
242-
}
243-
244-
if (baseValueField == null)
245-
{
246-
var ex = new UnitsNetException("No private fields found in type.");
247-
ex.Data["type"] = quantityType;
248-
ex.Data["fieldName"] = fieldName;
249-
throw ex;
250-
}
251-
252-
return baseValueField;
119+
Value = quantity.Value,
120+
Unit = $"{quantity.QuantityInfo.UnitType.Name}.{quantity.Unit}" // Example: "MassUnit.Kilogram"
121+
} );
253122
}
254123

255124
/// <summary>
@@ -280,9 +149,7 @@ private class ValueUnit
280149
public override bool CanConvert(Type objectType)
281150
{
282151
if (IsNullable(objectType))
283-
{
284152
return CanConvertNullable(objectType);
285-
}
286153

287154
return objectType.Namespace != null &&
288155
(objectType.Namespace.Equals(nameof(UnitsNet)) ||
@@ -314,6 +181,5 @@ protected virtual bool CanConvertNullable(Type objectType)
314181
}
315182

316183
#endregion
317-
318184
}
319185
}

UnitsNet.Tests/UnitConverterTest.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ public void ConvertByAbbreviation_ConvertsTheValueToGivenUnit(double expectedVal
7979

8080
[Theory]
8181
[InlineData(1, "UnknownQuantity", "m", "cm")]
82-
public void ConvertByAbbreviation_ThrowsQuantityNotFoundExceptionOnUnknownQuantity(double inputValue, string quantityTypeName, string fromUnit, string toUnit)
82+
public void ConvertByAbbreviation_ThrowsUnitNotFoundExceptionOnUnknownQuantity( double inputValue, string quantityTypeName, string fromUnit, string toUnit)
8383
{
84-
Assert.Throws<QuantityNotFoundException>(() => UnitConverter.ConvertByAbbreviation(inputValue, quantityTypeName, fromUnit, toUnit));
84+
Assert.Throws<UnitNotFoundException>(() => UnitConverter.ConvertByAbbreviation(inputValue, quantityTypeName, fromUnit, toUnit));
8585
}
8686

8787
[Theory]

UnitsNet/QuantityNotFoundException.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed under MIT No Attribution, see LICENSE file at the root.
1+
// Licensed under MIT No Attribution, see LICENSE file at the root.
22
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
33

44
using System;
@@ -9,6 +9,7 @@ namespace UnitsNet
99
/// Quantity type was not found. This is typically thrown for dynamic conversions,
1010
/// such as <see cref="UnitConverter.ConvertByName" />.
1111
/// </summary>
12+
[Obsolete("")]
1213
public class QuantityNotFoundException : UnitsNetException
1314
{
1415
/// <inheritdoc />

0 commit comments

Comments
 (0)