7

Is it possible to GroupBy in LINQ, using a collection property?

e.g.

void Main()
{
    var t1 = new Test() { Children = new List<string>() { "one", "two" } };
    var t2 = new Test() { Children = new List<string>() { "one", "two" } };
    var t3 = new Test() { Children = new List<string>() { "one", "three" }        };

    var tests = new List<Test>() { t1, t2, t3 };
    var anon =  from t in tests
                select new
                {
                    Children = t.Children
                };

    anon.GroupBy(t => t.Children).Dump();
}

public class Test
{
    public List<string> Children {get;set;}
}

In this example, I would hope for two groups:

Key: List() { "one", "two" } Value: t1, t2

Key: List() { "one", "three" } Value: t3

My understanding is that anonymous types are compared not by reference, but by comparing equality on their public properties.

However, the actual result is three groups:

Key: List() { "one", "two" } Value: t1

Key: List() { "one", "two" } Value: t2

Key: List() { "one", "three" } Value: t3

If this is not possible, is there a way to get the result I want?

Hopefully explained this clearly...

TheNextman
  • 12,428
  • 2
  • 36
  • 75

4 Answers4

4

By default, GroupBy is going to use reference equality when grouping by lists (which are reference types).

Since you've got new instances of the list each time, they are not equal.

However, there is an overload of GroupBy which lets you specify a custom IEqualityComparer, so that you can implement your own way of comparing a list of strings, for example.

To implement this, there are many other threads here about comparing two lists.

Community
  • 1
  • 1
wsanville
  • 37,158
  • 8
  • 76
  • 101
  • Thanks - I do understand that (my code sample was simplified from my real problem). Please see my modified code sample – TheNextman Nov 15 '11 at 15:21
  • 1
    My answer still applies, you need to create a custom `IEqualityComparer` that compares the equality of each member in two lists of strings. Then, pass that as the second parameter to `GroupBy`. – wsanville Nov 15 '11 at 15:23
  • Would you consider a custom IEqualityComparer a little too much for a one-off LINQ grouping that probably will not be needed twice? – CassOnMars Nov 15 '11 at 15:29
2

The reason you get 3 groups is because List<T> implements equality with default reference-equality, not by considering the "sequence equality" of the contained elements between any two lists. If you want such semantics, you'll have to implement an IEqualityComparer<IList<T>> (or similar) yourself and inject that into the GroupBy query using the overload that accepts an equality-comparer. Here's a sample implementation (for arrays, not lists, but easily adaptable).

If you're comfortable with set equality (order and duplicates are irrelevant), you're in luck: you can directly use HashSet<T> and the provided CreateSetComparer method for the comparer implementation:

  var t1 = new Test { Children = new HashSet<string> { "one", "two" } };
  var t2 = new Test { Children = new HashSet<string> { "one", "two" } };
  var t3 = new Test { Children = new HashSet<string> { "one", "three" } };

  var tests = new List<Test> { t1, t2, t3 };

  // Only two groups: { one, two } and { one, three }
  tests.GroupBy(t => t.Children, HashSet<string>.CreateSetComparer())
       .Dump();
Community
  • 1
  • 1
Ani
  • 111,048
  • 26
  • 262
  • 307
0

The problem is that the lists are not exactly identical. It's comparing equality for grouping, and you have two new List<string>s, which aren't exactly equal. You can, however, join the strings by hash code, which would produce a correct result:

tests.GroupBy(t => String.Join(string.Empty, t.Children.Select(c => c.GetHashCode().ToString())));
CassOnMars
  • 6,153
  • 2
  • 32
  • 47
  • Thanks - I do understand that (my code sample was simplified from my real problem). Please see my modified code sample – TheNextman Nov 15 '11 at 15:20
  • 1
    I have updated my answer to work with your existing code. Please take a look at it. It also doesn't require a custom `IEqualityComparer`. – CassOnMars Nov 15 '11 at 15:25
  • This generates very weird results if the hashcode happens to be the same. – Caramiriel Apr 20 '17 at 12:48
0

I don't think there's a built in method to that.

Look at Jon Skeet's answer here:

Any chance to get unique records using Linq (C#)?

Community
  • 1
  • 1
Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185