Search code examples
c#linqgroup-byiequalitycomparer

LINQ GroupBy on multiple ref-type fields; Custom EqualityComparer


So I've looked through about 20 examples on this on SO and elsewhere, but haven't found one which covers what I'm trying to do. This - Can I specify my explicit type comparator inline? - looks like what I need, but doesn't go far enough (or I don't understand how to take it further).

  • I have a List of LoadData, the LoadData object has fields of both reference and value types
  • Need to group on a mixture of ref and value fields, project the output to an anonymous type
  • Need (I think) to provide a custom IEqualityComparer to specify how to compare the GroupBy fields, but they are an anonymous type

    private class LoadData
    {
        public PeriodEndDto PeriodEnd { get; set; }
        public ComponentDto Component { get; set; }
        public string GroupCode { get; set; }
        public string PortfolioCode { get; set; }
    }
    

The best GroupBy query I have going so far:

var distinctLoads = list.GroupBy(
    dl => new { PeriodEnd = dl.PeriodEnd, 
                Component = dl.Component, 
                GroupCode = dl.GroupCode },
    (key, data) => new {PeriodEnd = key.PeriodEnd, 
                Component = key.Component, 
                GroupCode = key.GroupCode, 
                PortfolioList = data.Select(d=>d.PortfolioCode)
                                    .Aggregate((g1, g2) => g1 + "," + g2)},
    null);

This groups, but there are still duplicates.

  1. How can I specify custom code to compare the GroupBy fields? For example, the Components could be compared by Component.Code.

Solution

  • The problem here is that your key type is anonymous, which means you can't declare a class that implements IEqualityComparer<T> for that key type. While it would be possible to write a comparator which compared anonymous types for equality in a custom manner (via a generic method, delegates and type inference), it wouldn't be terribly pleasant.

    The two simplest options are probably:

    • Make the anonymous type "just work" by overriding Equals/GetHashCode in PeriodEndDto and ComponentDto. If there's a natural equality you'd want to use everywhere, this is probably the sanest option. I'd recommend implementing IEquatable<T> as well
    • Don't use an anonymous type for grouping - use a named type, and then you can either override GetHashCode and Equals on that, or you could write a custom equality comparer in the normal way.

    EDIT: ProjectionEqualityComparer wouldn't really work. It would be feasible to write something similar though - a sort of CompositeEqualityComparer which allowed you create an equality comparer from several "projection + comparer" pairs. It would be pretty ugly compared with the anonymous type though.