I have a Money struct that has currency and amount. I would like to be able to sum an List by using linq.
public struct Money
{
public string Currency { get; set; }
public decimal Amount { get; set; }
public static Money operator +(Money m1, Money m2)
{
if (m1.Currency != m2.Currency)
throw new InvalidOperationException();
return new Money() { Amount = m1.Amount + m2.Amount, Currency = m1.Currency };
}
}
Given the above code if I have a list of Items that have Money value objects is it possible to get the Sum function to work with a Money value object.
ie
Items.Sum(m => m.MoneyValue);
public static class SumExtensions
{
public static Money Sum(this IEnumerable<Money> source)
{
return source.Aggregate((x, y) => x + y);
}
public static Money Sum<T>(this IEnumerable<T> source, Func<T, Money> selector)
{
return source.Select(selector).Aggregate((x, y) => x + y);
}
}
Usage:
IEnumerable<Money> moneys = ...
Money sum = moneys.Sum();
and
IEnumerable<Transaction> txs = ...
Money sum = txs.Sum(x=>x.Amount);
Operators are a pain. However, if you look at MiscUtil, I have implemented a generic Enumerable.Sum
that does respect custom operators. The usage is (intentionally) identical - so your line:
var moneySum = Items.Sum(m => m.MoneyValue);
should work, with the expected result - except that you don't currently handle default(Money)
for addition purposes. Alternatively, if this is just for MoneyValue
, just write an extension method:
public static class MoneyExtensions {
public static Money Sum(this IEnumerable<Money> source) {
Money sum = source.First();
foreach(var item in source.Skip(1)) sum += item;
return sum;
}
}
Actually, to avoid 2 enumerations, I might tweak this to:
using (var iter = source.GetEnumerator())
{
if (!iter.MoveNext()) return default(Money);
var sum = iter.Current;
while (iter.MoveNext()) sum += iter.Current;
return sum;
}
I know this is old. But I have a similar Money class in my system where I do this. I've changed my Sum implementation to the way Jesper is doing it.
I don't have a default
for money, but what I did to resolve the cases of empty collections was add the seed of the aggregate.
public static class SumExtensions
{
public static Money Sum(this IEnumerable<Money> source)
=> source.Aggregate(new Money(0), (x, y) => x + y);
public static Money Sum<T>(this IEnumerable<T> source, Func<T, Money> selector)
=> source.Select(selector).Sum();
}
My implementation handles adding Monies without currencies. Any other cases will throw a DifferentCurrenciesException.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With