Skip to content

Commit 6a9ae6c

Browse files
authored
Add type safety and improve type inference (#1374)
Some of the extension methods in `UnitMath.cs` (e.g. Average) take an `Enum unitType` argument. The compiler should be able to detect when the units type doesn't match the quantity type. * This will break backwards compatibility, but only for: * People doing really weird things. * People using the explicit generic type instead of inference (e.g. `Average<Length>(LengthUnit.Inch)`), in which case they can fix the code by changing it to e.g. `Average<Length, LengthUnit>(LengthUnit.Inch)` * This change might be warranted in `UnitConverter.cs` as well, but can't be implemented as a straight-forward refactor since it breaks compatibility in generated code (e.g. `unitConverter.SetConversionFunction<ElectricPotential>` in `ElectricPotential` should be `SetConversionFunction<ElectricPotential, ElectricPotentialUnit>` In addition, had to remove a few unit tests that were asserting type safety. All tests that were removed: * Were only testing that an incorrect behavior throws an exception (`Assert.Throws`) * Don't compile after the changes in `UnitMath.cs`
1 parent 5572dc2 commit 6a9ae6c

File tree

2 files changed

+32
-84
lines changed

2 files changed

+32
-84
lines changed

UnitsNet.Tests/UnitMathTests.cs

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,6 @@ public void AbsoluteValueOfNullReferenceThrowsException()
4545
Assert.Throws<NullReferenceException>(() => quantity.Abs());
4646
}
4747

48-
[Fact]
49-
public void AverageOfDifferentUnitsThrowsException()
50-
{
51-
var units = new IQuantity[] {Length.FromMeters(1), Volume.FromLiters(50)};
52-
53-
Assert.Throws<ArgumentException>(() => units.Average(LengthUnit.Centimeter));
54-
}
55-
5648
[Fact]
5749
public void AverageOfEmptySourceThrowsException()
5850
{
@@ -61,14 +53,6 @@ public void AverageOfEmptySourceThrowsException()
6153
Assert.Throws<InvalidOperationException>(() => units.Average(LengthUnit.Centimeter));
6254
}
6355

64-
[Fact]
65-
public void AverageOfLengthsWithNullValueThrowsException()
66-
{
67-
var units = new IQuantity[] {Length.FromMeters(1), null!};
68-
69-
Assert.Throws<NullReferenceException>(() => units.Average(LengthUnit.Centimeter));
70-
}
71-
7256
[Fact]
7357
public void AverageOfLengthsCalculatesCorrectly()
7458
{
@@ -119,22 +103,6 @@ public void MaxOfTwoLengthsReturnsTheLargestValue()
119103
Assert.Equal(LengthUnit.Meter, max.Unit);
120104
}
121105

122-
[Fact]
123-
public void MaxOfDifferentUnitsThrowsException()
124-
{
125-
var units = new IQuantity[] {Length.FromMeters(1), Volume.FromLiters(50)};
126-
127-
Assert.Throws<ArgumentException>(() => units.Max(LengthUnit.Centimeter));
128-
}
129-
130-
[Fact]
131-
public void MaxOfLengthsWithNullValueThrowsException()
132-
{
133-
var units = new IQuantity[] {Length.FromMeters(1), null!};
134-
135-
Assert.Throws<NullReferenceException>(() => units.Max(LengthUnit.Centimeter));
136-
}
137-
138106
[Fact]
139107
public void MaxOfEmptySourceThrowsException()
140108
{
@@ -193,22 +161,6 @@ public void MinOfTwoLengthsReturnsTheSmallestValue()
193161
Assert.Equal(LengthUnit.Centimeter, min.Unit);
194162
}
195163

196-
[Fact]
197-
public void MinOfDifferentUnitsThrowsException()
198-
{
199-
var units = new IQuantity[] {Length.FromMeters(1), Volume.FromLiters(50)};
200-
201-
Assert.Throws<ArgumentException>(() => units.Min(LengthUnit.Centimeter));
202-
}
203-
204-
[Fact]
205-
public void MinOfLengthsWithNullValueThrowsException()
206-
{
207-
var units = new IQuantity[] {Length.FromMeters(1), null!};
208-
209-
Assert.Throws<NullReferenceException>(() => units.Min(LengthUnit.Centimeter));
210-
}
211-
212164
[Fact]
213165
public void MinOfEmptySourceThrowsException()
214166
{
@@ -255,22 +207,6 @@ public void MinOfLengthsWithSelectorCalculatesCorrectly()
255207
Assert.Equal(LengthUnit.Centimeter, min.Unit);
256208
}
257209

258-
[Fact]
259-
public void SumOfDifferentUnitsThrowsException()
260-
{
261-
var units = new IQuantity[] {Length.FromMeters(1), Volume.FromLiters(50)};
262-
263-
Assert.Throws<ArgumentException>(() => units.Sum(LengthUnit.Centimeter));
264-
}
265-
266-
[Fact]
267-
public void SumOfLengthsWithNullValueThrowsException()
268-
{
269-
var units = new IQuantity[] {Length.FromMeters(1), null!};
270-
271-
Assert.Throws<NullReferenceException>(() => units.Sum(LengthUnit.Centimeter));
272-
}
273-
274210
[Fact]
275211
public void SumOfEmptySourceReturnsZero()
276212
{

UnitsNet/UnitMath.cs

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ public static TQuantity Abs<TQuantity>(this TQuantity value) where TQuantity : I
3030
/// <exception cref="ArgumentException">
3131
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
3232
/// </exception>
33-
public static TQuantity Sum<TQuantity>(this IEnumerable<TQuantity> source, Enum unitType)
34-
where TQuantity : IQuantity
33+
public static TQuantity Sum<TQuantity, TUnitType>(this IEnumerable<TQuantity> source, TUnitType unitType)
34+
where TUnitType : Enum
35+
where TQuantity : IQuantity<TUnitType>
3536
{
3637
return (TQuantity) Quantity.From(source.Sum(x => x.As(unitType)), unitType);
3738
}
@@ -44,16 +45,18 @@ public static TQuantity Sum<TQuantity>(this IEnumerable<TQuantity> source, Enum
4445
/// <param name="selector">A transform function to apply to each element.</param>
4546
/// <param name="unitType">The desired unit type for the resulting quantity</param>
4647
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
47-
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation</typeparam>
48+
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation.</typeparam>
49+
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
4850
/// <returns>The sum of the projected values, represented in the specified unit type.</returns>
4951
/// <exception cref="T:System.ArgumentNullException">
5052
/// <paramref name="source">source</paramref> or <paramref name="selector">selector</paramref> is null.
5153
/// </exception>
5254
/// <exception cref="ArgumentException">
5355
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
5456
/// </exception>
55-
public static TQuantity Sum<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, Enum unitType)
56-
where TQuantity : IQuantity
57+
public static TQuantity Sum<TSource, TQuantity, TUnitType>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, TUnitType unitType)
58+
where TUnitType : Enum
59+
where TQuantity : IQuantity<TUnitType>
5760
{
5861
return source.Select(selector).Sum(unitType);
5962
}
@@ -79,8 +82,9 @@ public static TQuantity Min<TQuantity>(TQuantity val1, TQuantity val2) where TQu
7982
/// <exception cref="ArgumentException">
8083
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
8184
/// </exception>
82-
public static TQuantity Min<TQuantity>(this IEnumerable<TQuantity> source, Enum unitType)
83-
where TQuantity : IQuantity
85+
public static TQuantity Min<TQuantity, TUnitType>(this IEnumerable<TQuantity> source, TUnitType unitType)
86+
where TUnitType : Enum
87+
where TQuantity : IQuantity<TUnitType>
8488
{
8589
return (TQuantity) Quantity.From(source.Min(x => x.As(unitType)), unitType);
8690
}
@@ -93,7 +97,8 @@ public static TQuantity Min<TQuantity>(this IEnumerable<TQuantity> source, Enum
9397
/// <param name="selector">A transform function to apply to each element.</param>
9498
/// <param name="unitType">The desired unit type for the resulting quantity</param>
9599
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
96-
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation</typeparam>
100+
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation.</typeparam>
101+
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
97102
/// <returns>The min of the projected values, represented in the specified unit type.</returns>
98103
/// <exception cref="T:System.ArgumentNullException">
99104
/// <paramref name="source">source</paramref> or <paramref name="selector">selector</paramref> is null.
@@ -102,8 +107,9 @@ public static TQuantity Min<TQuantity>(this IEnumerable<TQuantity> source, Enum
102107
/// <exception cref="ArgumentException">
103108
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
104109
/// </exception>
105-
public static TQuantity Min<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, Enum unitType)
106-
where TQuantity : IQuantity
110+
public static TQuantity Min<TSource, TQuantity, TUnitType>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, TUnitType unitType)
111+
where TUnitType : Enum
112+
where TQuantity : IQuantity<TUnitType>
107113
{
108114
return source.Select(selector).Min(unitType);
109115
}
@@ -129,8 +135,9 @@ public static TQuantity Max<TQuantity>(TQuantity val1, TQuantity val2) where TQu
129135
/// <exception cref="ArgumentException">
130136
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
131137
/// </exception>
132-
public static TQuantity Max<TQuantity>(this IEnumerable<TQuantity> source, Enum unitType)
133-
where TQuantity : IQuantity
138+
public static TQuantity Max<TQuantity, TUnitType>(this IEnumerable<TQuantity> source, TUnitType unitType)
139+
where TUnitType : Enum
140+
where TQuantity : IQuantity<TUnitType>
134141
{
135142
return (TQuantity) Quantity.From(source.Max(x => x.As(unitType)), unitType);
136143
}
@@ -143,7 +150,8 @@ public static TQuantity Max<TQuantity>(this IEnumerable<TQuantity> source, Enum
143150
/// <param name="selector">A transform function to apply to each element.</param>
144151
/// <param name="unitType">The desired unit type for the resulting quantity</param>
145152
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
146-
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation</typeparam>
153+
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation.</typeparam>
154+
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
147155
/// <returns>The max of the projected values, represented in the specified unit type.</returns>
148156
/// <exception cref="T:System.ArgumentNullException">
149157
/// <paramref name="source">source</paramref> or <paramref name="selector">selector</paramref> is null.
@@ -152,8 +160,9 @@ public static TQuantity Max<TQuantity>(this IEnumerable<TQuantity> source, Enum
152160
/// <exception cref="ArgumentException">
153161
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
154162
/// </exception>
155-
public static TQuantity Max<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, Enum unitType)
156-
where TQuantity : IQuantity
163+
public static TQuantity Max<TSource, TQuantity, TUnitType>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, TUnitType unitType)
164+
where TUnitType : Enum
165+
where TQuantity : IQuantity<TUnitType>
157166
{
158167
return source.Select(selector).Max(unitType);
159168
}
@@ -169,8 +178,9 @@ public static TQuantity Max<TSource, TQuantity>(this IEnumerable<TSource> source
169178
/// <exception cref="ArgumentException">
170179
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
171180
/// </exception>
172-
public static TQuantity Average<TQuantity>(this IEnumerable<TQuantity> source, Enum unitType)
173-
where TQuantity : IQuantity
181+
public static TQuantity Average<TQuantity, TUnitType>(this IEnumerable<TQuantity> source, TUnitType unitType)
182+
where TUnitType : Enum
183+
where TQuantity : IQuantity<TUnitType>
174184
{
175185
return (TQuantity) Quantity.From(source.Average(x => x.As(unitType)), unitType);
176186
}
@@ -183,7 +193,8 @@ public static TQuantity Average<TQuantity>(this IEnumerable<TQuantity> source, E
183193
/// <param name="selector">A transform function to apply to each element.</param>
184194
/// <param name="unitType">The desired unit type for the resulting quantity</param>
185195
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
186-
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation</typeparam>
196+
/// <typeparam name="TQuantity">The type of quantity that is produced by this operation.</typeparam>
197+
/// <typeparam name="TUnitType">The type of unit enum.</typeparam>
187198
/// <returns>The average of the projected values, represented in the specified unit type.</returns>
188199
/// <exception cref="T:System.ArgumentNullException">
189200
/// <paramref name="source">source</paramref> or <paramref name="selector">selector</paramref> is null.
@@ -192,8 +203,9 @@ public static TQuantity Average<TQuantity>(this IEnumerable<TQuantity> source, E
192203
/// <exception cref="ArgumentException">
193204
/// <paramref name="source">source</paramref> contains quantity types different from <paramref name="unitType" />.
194205
/// </exception>
195-
public static TQuantity Average<TSource, TQuantity>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, Enum unitType)
196-
where TQuantity : IQuantity
206+
public static TQuantity Average<TSource, TQuantity, TUnitType>(this IEnumerable<TSource> source, Func<TSource, TQuantity> selector, TUnitType unitType)
207+
where TUnitType : Enum
208+
where TQuantity : IQuantity<TUnitType>
197209
{
198210
return source.Select(selector).Average(unitType);
199211
}

0 commit comments

Comments
 (0)