Skip to content

Commit 17e3bfc

Browse files
authored
Constrained the GenericMathExtensions to IQuantity (#1448)
- replaced the aggregation with an iterator based version (slightly faster) - the Average extension now throws an `InvalidOperationException` when the source is empty
1 parent 095c85b commit 17e3bfc

File tree

2 files changed

+95
-39
lines changed

2 files changed

+95
-39
lines changed

UnitsNet.Tests/GenericMathExtensionsTests.cs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
33

44
#if NET7_0_OR_GREATER
5+
using System;
6+
using System.Collections.Generic;
57
using UnitsNet.GenericMath;
68
using Xunit;
79

@@ -10,19 +12,59 @@ namespace UnitsNet.Tests;
1012
public class GenericMathExtensionsTests
1113
{
1214
[Fact]
13-
public void CanCalcSum()
15+
public void Sum_Empty_ReturnsTheAdditiveIdentity()
1416
{
15-
Length[] values = { Length.FromCentimeters(100), Length.FromCentimeters(200) };
17+
Length[] values = [];
1618

17-
Assert.Equal(Length.FromCentimeters(300), values.Sum());
19+
Assert.Equal(Length.Zero, GenericMathExtensions.Sum(values));
1820
}
1921

2022
[Fact]
21-
public void CanCalcAverage_ForQuantitiesWithDoubleValueType()
23+
public void Sum_OneQuantity_ReturnsTheSameQuantity()
2224
{
23-
Length[] values = { Length.FromCentimeters(100), Length.FromCentimeters(200) };
25+
IEnumerable<Length> values = [Length.FromCentimeters(100)];
2426

25-
Assert.Equal(Length.FromCentimeters(150), values.Average());
27+
Length sumOfQuantities = GenericMathExtensions.Sum(values);
28+
29+
Assert.Equal(Length.FromCentimeters(100), sumOfQuantities);
30+
}
31+
32+
[Fact]
33+
public void Sum_TwoQuantities_ReturnsTheExpectedSum()
34+
{
35+
IEnumerable<Length> values = [Length.FromCentimeters(100), Length.FromCentimeters(200)];
36+
37+
Length sumOfQuantities = GenericMathExtensions.Sum(values);
38+
39+
Assert.Equal(Length.FromCentimeters(300), sumOfQuantities);
40+
}
41+
42+
[Fact]
43+
public void Average_Empty_ThrowsInvalidOperationException()
44+
{
45+
IEnumerable<Length> values = [];
46+
47+
Assert.Throws<InvalidOperationException>(() => GenericMathExtensions.Average(values));
48+
}
49+
50+
[Fact]
51+
public void Average_OneQuantity_ReturnsTheSameQuantity()
52+
{
53+
IEnumerable<Length> values = [Length.FromCentimeters(100)];
54+
55+
Length averageOfQuantities = GenericMathExtensions.Average(values);
56+
57+
Assert.Equal(Length.FromCentimeters(100), averageOfQuantities);
58+
}
59+
60+
[Fact]
61+
public void Average_TwoQuantities_ReturnsTheExpectedAverage()
62+
{
63+
IEnumerable<Length> values = [Length.FromCentimeters(100), Length.FromCentimeters(200)];
64+
65+
Length averageOfQuantities = GenericMathExtensions.Average(values);
66+
67+
Assert.Equal(Length.FromCentimeters(150), averageOfQuantities);
2668
}
2769
}
2870

UnitsNet/GenericMath/GenericMathExtensions.cs

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,81 @@
22
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
33

44
#if NET7_0_OR_GREATER
5+
using System;
56
using System.Collections.Generic;
6-
using System.Linq;
77
using System.Numerics;
88

99
namespace UnitsNet.GenericMath;
1010

1111
/// <summary>
12-
/// Provides generic math operations to test out the new generic math interfaces implemented in .NET7 for UnitsNet
13-
/// quantities using <see cref="double" /> as the internal value type, which is the majority of quantities.
12+
/// Provides generic math operations using the generic math interfaces implemented in .NET7 for UnitsNet.
1413
/// </summary>
1514
public static class GenericMathExtensions
1615
{
1716
/// <summary>
18-
/// Returns the sum of values.
17+
/// Returns the sum of a sequence of vector quantities, such as Mass and Length.
1918
/// </summary>
19+
/// <param name="source">The values.</param>
20+
/// <typeparam name="TQuantity">The type of the quantity elements in the source sequence.</typeparam>
21+
/// <returns>The sum of the quantities, using the unit of the first element in the sequence.</returns>
2022
/// <remarks>
21-
/// This method is experimental and intended to test out the new generic math interfaces implemented in .NET7 for
22-
/// UnitsNet quantities.<br />
23-
/// Generic math interfaces might replace <see cref="UnitMath" />.<br />
24-
/// Generic math LINQ support is still missing in the BCL, but is being worked on:
23+
/// Note that the generic math LINQ support is still missing in the BCL, but is being worked on:
2524
/// <a href="https://github.com/dotnet/runtime/issues/64031">
26-
/// API Proposal: Generic LINQ Numeric Operators · Issue #64031 · dotnet/runtime
25+
/// API Proposal: Generic LINQ Numeric Operators · Issue
26+
/// #64031 · dotnet/runtime
2727
/// </a>
2828
/// </remarks>
29-
/// <param name="source">The values.</param>
30-
/// <typeparam name="T">The type of value.</typeparam>
31-
/// <returns>The sum.</returns>
32-
public static T Sum<T>(this IEnumerable<T> source)
33-
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
29+
public static TQuantity Sum<TQuantity>(this IEnumerable<TQuantity> source)
30+
where TQuantity : IQuantity, IAdditionOperators<TQuantity, TQuantity, TQuantity>, IAdditiveIdentity<TQuantity, TQuantity>
3431
{
35-
// Put accumulator on right hand side of the addition operator to construct quantities with the same unit as the values.
36-
// The addition operator implementation picks the unit from the left hand side, and the additive identity (e.g. Length.Zero) is always the base unit.
37-
return source.Aggregate(T.AdditiveIdentity, (acc, item) => item + acc);
32+
using IEnumerator<TQuantity> e = source.GetEnumerator();
33+
if (!e.MoveNext())
34+
{
35+
return TQuantity.AdditiveIdentity;
36+
}
37+
38+
TQuantity result = e.Current;
39+
while (e.MoveNext())
40+
{
41+
result += e.Current;
42+
}
43+
44+
return result;
3845
}
3946

4047
/// <summary>
41-
/// Returns the average of values.
48+
/// Calculates the arithmetic average of a sequence of vector quantities, such as Mass and Length.
4249
/// </summary>
50+
/// <param name="source">The values.</param>
51+
/// <typeparam name="TQuantity">The type of the quantity elements in the source sequence.</typeparam>
52+
/// <returns>The average of the quantities, using the unit of the first element in the sequence.</returns>
53+
/// <exception cref="InvalidOperationException">Thrown when the sequence is empty.</exception>
4354
/// <remarks>
44-
/// This method is experimental and intended to test out the new generic math interfaces implemented in .NET7 for
45-
/// UnitsNet quantities.<br />
46-
/// Generic math interfaces might replace <see cref="UnitMath" />.<br />
47-
/// Generic math LINQ support is still missing in the BCL, but is being worked on:
55+
/// Note that the generic math LINQ support is still missing in the BCL, but is being worked on:
4856
/// <a href="https://github.com/dotnet/runtime/issues/64031">
4957
/// API Proposal: Generic LINQ Numeric Operators · Issue
5058
/// #64031 · dotnet/runtime
5159
/// </a>
5260
/// </remarks>
53-
/// <param name="source">The values.</param>
54-
/// <typeparam name="T">The value type.</typeparam>
55-
/// <returns>The average.</returns>
56-
public static T Average<T>(this IEnumerable<T> source)
57-
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>, IDivisionOperators<T, double, T>
61+
public static TQuantity Average<TQuantity>(this IEnumerable<TQuantity> source)
62+
where TQuantity : IQuantity, IAdditionOperators<TQuantity, TQuantity, TQuantity>, IAdditiveIdentity<TQuantity, TQuantity>,
63+
IDivisionOperators<TQuantity, double, TQuantity>
5864
{
59-
// Put accumulator on right hand side of the addition operator to construct quantities with the same unit as the values.
60-
// The addition operator implementation picks the unit from the left hand side, and the additive identity (e.g. Length.Zero) is always the base unit.
61-
(T value, int count) result = source.Aggregate(
62-
(value: T.AdditiveIdentity, count: 0),
63-
(acc, item) => (value: item + acc.value, count: acc.count + 1));
65+
using IEnumerator<TQuantity> e = source.GetEnumerator();
66+
if (!e.MoveNext())
67+
{
68+
throw new InvalidOperationException("Sequence contains no elements");
69+
}
70+
71+
TQuantity result = e.Current;
72+
var nbQuantities = 1;
73+
while (e.MoveNext())
74+
{
75+
result += e.Current;
76+
nbQuantities++;
77+
}
6478

65-
return result.value / result.count;
79+
return result / nbQuantities;
6680
}
6781
}
6882
#endif

0 commit comments

Comments
 (0)