Можете ли вы перегрузить Sum для добавления пользовательских типов

У меня есть структура Money, у которой есть валюта и сумма. Я хотел бы иметь возможность суммировать список с помощью 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 };
    }
}

Учитывая приведенный выше код, если у меня есть список элементов, имеющих объекты денежных значений, можно получить функцию Sum для работы с объектом Money value.

т

Items.Sum(m => m.MoneyValue);

Ответ 1

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);
    }
}

Использование:

IEnumerable<Money> moneys = ...
Money sum = moneys.Sum();

и

IEnumerable<Transaction> txs = ...
Money sum = txs.Sum(x=>x.Amount);

Ответ 2

Операторы - это боль. Однако, если вы посмотрите MiscUtil, я внедрил общий Enumerable.Sum, который уважает настраиваемые операторы. Использование (намеренно) идентично - поэтому ваша строка:
var moneySum = Items.Sum(m => m.MoneyValue);

должен работать с ожидаемым результатом - кроме, который вы в настоящее время не обрабатываете default(Money) для дополнительных целей. Альтернативно, если это просто для MoneyValue, просто напишите метод расширения:

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;
    }
}

Собственно, чтобы избежать 2 перечислений, я мог бы настроить это на:

using (var iter = source.GetEnumerator())
{
    if (!iter.MoveNext()) return default(Money);
    var sum = iter.Current;
    while (iter.MoveNext()) sum += iter.Current;
    return sum;
}

Ответ 3

Это должно работать, если вы вернете m.MoneyValue.Amount вместо этого - набор перегрузок Sum, которые принимают Func.

К сожалению, Sum не подчиняется никакому operator+, который вы определяете. (Фактически, он не мог вызвать operator+ без использования отражения.)