views:

124

answers:

3

I have two methods that look almost the same, except for the aggregate function used in the Linq query. For example:

public IEnumerable<Item> DoStuff(IEnumerable<Item> someItems) {
    var items = someItems.GroupBy(i => i.Date).Select(p => new Item(p.Key, p.Sum(r => r.Value)));
    // ...
}

public IEnumerable<Item> DoOtherStuff(IEnumerable<Item> someItems) {
    var items = someItems.GroupBy(i => i.Date).Select(p => new Item(p.Key, p.Max(r => r.Value)));
    // ...
}

where the Item is a class like this:

public class Item {
    public DateTime Date { get; private set; }
    public decimal Value { get; private set; }

    public Item(DateTime date, decimal value) {
     Date = date;
     Value = value;
    }
}

Since both methods do the same thing, I would like have just one method where I pass the IEnumerable and the aggregate function (sum / max) as parameters. How can I achieve that?

+2  A: 

The addition of the selector inside is a pain, but ultimately you could take a Func<IEnumerable<decimal>,decimal> , and pass in Enumerable.Max or Enumerable.Sum as the delegate instance. To do this without duplicating the =>r.Value selector would require doing the selector first, i.e.

// usage:
DoStuff(items, Enumerable.Sum);
DoStuff(items, Enumerable.Max);
// shared method:
public IEnumerable<Item> DoStuff(IEnumerable<Item> someItems,
        Func<IEnumerable<decimal>,decimal> aggregate)
{
    var items = someItems.GroupBy(i => i.Date).Select(
        p => new Item(p.Key, aggregate(p.Select(r=>r.Value))));
    ...
}
Marc Gravell
+8  A: 

Try the following

public IEnumerable<Item> DoStuff(
  IEnumerable<Item> someItems,
  Func<IGrouping<DateTime,decimal>,Item> reduce) {
  var items = someItems.GroupBy(i => i.Date).Select(reduce);
  ...
}

DoStuff(someItems, p => p.Sum(r => r.Value));
DoStuff(someItems, p => p.Max(r => r.Value));
JaredPar
+2  A: 

Yikes!:

public IEnumerable<Item> DoOtherStuff(IEnumerable<Item> someItems,
    Func<
        IGrouping<DateTime, Item>,
        Func<Func<Item, decimal>, decimal>
        > aggregateForGrouping
    )
{
    var items = someItems.GroupBy(i => i.Date)
        .Select(p => new Item(p.Key, aggregateForGrouping(p)(r => r.Value)));
    // ...
}

DoOtherStuff(someItems, p => p.Max);

Ummm, don't do this, do what JaredPar said...

Or if you still want to get this syntax, use some serious aliasing.

using ItemGroupingByDate = IGrouping<DateTime, Item>;
using AggregateItems = Func<Func<Item, decimal>, decimal>;

public IEnumerable<Item> DoOtherStuff(
    IEnumerable<Item> someItems,
    Func<ItemGroupingByDate, AggregateItems> getAggregateForGrouping
    )
{
    var items = someItems.GroupBy(i => i.Date)
        .Select(p => new Item(p.Key, getAggregateForGrouping(p)(r => r.Value)));
    // ...
}

It might almost be readable if you could use aliases in other aliases, or match custom delegates with matching Func signatures, then you could jam "Selector" in there instead of "Func".

We're still way down a rabbit hole, though, so this is not the solution you are looking for. Just put here for demonstration purposes :)

Merlyn Morgan-Graham
+1 - Altough I liked, the method signature becomes too hard to read, even with the aliases.
Fernando