Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you overload Sum to add custom types

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);
like image 567
Joel Cunningham Avatar asked Nov 09 '10 10:11

Joel Cunningham


3 Answers

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);
like image 65
Jesper Larsen-Ledet Avatar answered Oct 18 '22 06:10

Jesper Larsen-Ledet


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;
}
like image 3
Marc Gravell Avatar answered Oct 18 '22 05:10

Marc Gravell


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.

like image 1
Carlos P. Avatar answered Oct 18 '22 06:10

Carlos P.