Home > Software engineering >  Make a reusable GroupBy function
Make a reusable GroupBy function

Time:10-24

I want to make a grouping function, that could be reusable by many others. Here is a concrete example :

I made a function to group by Numero

public static List<Meb> GroupByNumber(this List<Meb> listMeb)
{
    return listMeb.GroupBy(x => new { x.Numero })
        .Select(cl => new Meb
        {
            ID = -1,
            IdUser = cl.First().IdUser,
            Barre = cl.First().Barre,
            CNC = cl.First().CNC,
            // ... and so long
        }).OrderBy(x => x.Numero).ThenBy(x => x.Avancement).ToList();
}

Then another grouping by CNC

public static List<Meb> GroupByCNC(this List<Meb> listMeb)
{
    return listMeb.GroupBy(x => new { x.CNC })
        .Select(cl => new Meb
        {
            ID = -1,
            IdUser = cl.First().IdUser,
            Barre = cl.First().Barre,
            CNC = cl.First().CNC,
            // ... and so long
        }).OrderBy(x => x.Numero).ThenBy(x => x.Avancement).ToList();
}

The problem is in the group function, I have a lot of attributes following (around 40). So, when I want to change the way I want to make grouping (evolution of code), I need to do it in each GroupBy function. (I also can have many sometimes).

So, I'm looking a way I could write the function only once, then call it changing only the GroupBy(x => new { x.Numero }) part.

CodePudding user response:

Enumerable.GroupBy takes a Func<TSource, TKey> as keyselector.

Make your function accept such an argument too:

public static <TKey> List<Meb> GroupMeb(
    this List<Meb> listMeb,
    Func<Meb,TKey> keySelector)
{
    return listMeb.GroupBy(keySelector)
        .Select(cl => new Meb
        {
            ID = -1,
            IdUser = cl.First().IdUser,
            Barre = cl.First().Barre,
            CNC = cl.First().CNC,
            // ... and so long
        }).OrderBy(x => x.Numero).ThenBy(x => x.Avancement).ToList();
}

Usage:

yourList.GroupMeb(x => new { x.CNC })

CodePudding user response:

I see two options.

  1. Make your own version of GroupBy() that also does the mapping and ordering:
public static List<Meb> MyGroupBy<TKey>(
    this List<Meb> mebs,
    Func<Meb, TKey> groupBy)
    => mebs
        .GroupBy(groupBy)
        .Select(cl => new Meb
        {
            // ...and so on
        })
        .OrderBy(x => x.Numero).ThenBy(x => x.Avancement)
        .ToList();

Usage:

myList
    .MyGroupBy(x => x.CNC)
  1. Extract the mapping and ordering into a reusable function and use the standard GroupBy():
public static IEnumerable<Meb> MapAndOrder<TKey>(
    this IEnumerable<IGrouping<TKey, Meb>> mebs)
    => mebs
        .Select(cl => new Meb
        {
            // ...and so on
        })
        .OrderBy(x => x.Numero).ThenBy(x => x.Avancement);

Usage:

myList
    .GroupBy(x => x.CNC)
    .MapAndOrder()
    .ToList()

It's a little more "duplication" but it's also more modular IMHO.

Note that MapAndOrder() now receives an IEnumerable<IGrouping<TKey, Meb>> and returns an IEnumerable<Meb> instead.

Also be aware that there's no need to use new { ... } in a GroupBy() when you group by a single property.

You could take this even further by extracting the mapping into a function and creating a helper extension for your ordering:

public Meb MapMebGroup<TKey>(IGrouping<TKey, Meb> g) => new Meb
{
    // ...and so on
}

public static IEnumerable<Meb> WithDefaultOrdering(
    this IEnumerable<Meb> mebs)
    => mebs.OrderBy(x => x.Numero).ThenBy(x => x.Avancement);

Usage:

myList
    .GroupBy(x => x.CNC)
    .Select(MapMebGroup)
    .WithDefaultOrdering()
    .ToList();

Again, this looks like duplication, but in reality you are splitting up your logic into reusable and composable functions/operators.

  • Related