3

I am trying to find the number of items in a list that differ in a property which itself is a list. I found this example using Linq here:

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();

This works nicely if the property PersonId is scalar. But in my case this does not work and in the following the items in SelectedTargets are always returned as distinct even though the ListOfActions is equal in all items:

List<Target> distinctTargets = SelectedTargets.GroupBy(p => p.ListOfActions).Select(g => g.First()).ToList();

If instead I pick the first item in ListOfActions it works:

List<Target> distinctTargets = SelectedTargets.GroupBy(p => p.ListOfActions[0]).Select(g => g.First()).ToList();

So how can I check for equality of the whole list ListOfActions? (it doesn't necessarily have to user Linq)

The definition of SelectedTargets is:

List<Target> SelectedTargets = new List<Target>();

and is DispensingActionList:

private DispensingActionList ListOfActions = new DispensingActionList();
public class DispensingActionList : List<DispensingAction>
    { ...
Community
  • 1
  • 1
packoman
  • 1,230
  • 1
  • 16
  • 36
  • Why don't you just override the Equals() method for ListOfActions class? – Tamas Ionut Jan 22 '16 at 10:14
  • Please see ["Should questions include “tags” in their titles?"](http://meta.stackexchange.com/questions/19190/should-questions-include-tags-in-their-titles), where the consensus is "no, they should not"! –  Jan 22 '16 at 12:40
  • Ok. I will remember that. Sorry. – packoman Jan 22 '16 at 12:47

2 Answers2

3

You could use a custom IEqualityComparer<T> for the GroupBy overload which compares sequences. For example this which uses Enumerable.SequenceEqual:

public class SequenceComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;

        var comparer = EqualityComparer<T>.Default;
        return x.SequenceEqual(y, comparer);
    }

    public int GetHashCode(IEnumerable<T> items)
    {
        unchecked
        {
            int hash = 17;
            foreach (T item in items)
            {
                hash = hash * 23 + (item == null ? 0 : item.GetHashCode());
            }
            return hash;
        }
    }
}

Now this should work:

 List<Target> distinctTargets = SelectedTargets
    .GroupBy(p => p.ListOfActions, new SequenceComparer<DispensingAction>())
    .Select(g => g.First())
    .ToList();

Of course DispensingAction also needs to override Equals to compare the objects meaningfully and not only checks whether they're the same reference or not.

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • I just got around to trying this. It works nicely, so thanks! However I would prefer a solution, where I do not have to instantiate a new object (the SequenceComparer) to test equality, so that the Linq line would remain like this: `List distinctTargets = SelectedTargets.GroupBy(p => p.ListOfActions).Select(g => g.First()).ToList();` I overriding `Equals(IList obj)` and using the Linq-line as above, but it returns `false`. Any suggestions? – packoman Jan 26 '16 at 13:05
1

You could use Enumerable.SequenceEqual<TSource>

You will have to override the GetHashCode and Equals methods for your types if you didn't do so yet.

DDan
  • 8,068
  • 5
  • 33
  • 52