12

I have this class

public class Item
{
       public Coordinate coordinate { get; set; }
        ...
        ...
}

With Coordinate being define like this:

public class Coordinate
{
        public Coordinate(float latitude, float longitude)
        {
            Latitude = latitude;
            Longitude = longitude;
        }

        public float Latitude { get; private set; }
        public float Longitude { get; private set; }
}

And I want to have a linq query like that :

var grouped = from it in items
              group it by it.Coordinate into grp
              select grp;

As mentioned here by MSDN I thought this was possible if I would override Equals on my Coordinate class :

Use a named type if you must pass the query variable to another method. Create a special class using auto-implemented properties for the keys, and then override the Equals and GetHashCode methods. You can also use a struct, in which case you do not strictly have to override those methods. For more information see How to: Implement an Immutable Class That Has Auto-Implemented Properties

Equals implementation for Coordinate class :

public override bool Equals(object obj)
{
       var coord = obj as Coordinate;
       if(coord == null) return false;
       return (Latitude == coord.Latitude && Longitude == coord.Longitude);
}

still I cant get my linq query to group by similar coordinates, as my failing test shows :

[TestMethod]
public void GroupBy_3ItemsWith2DifferentCoordinates_Returns2Groups()
{
    var items = new List<Item>
        {
            new Item {Coordinate = new Coordinate(10, 10)},
            new Item {Coordinate = new Coordinate(10, 10)},
            new Item {Coordinate = new Coordinate(12, 10)},
        };
    var grouped = from it in items
                  group it by it.Coordinate into g
                  select g;
    Assert.AreEqual(2, grouped.Count());
}

There is an overload to the GrouBy method that takes an IEqualityComparer as a parameter, but is there the equivalent using the group clause? Am I doing something wrong?? Any thoughts?

Stéphane
  • 11,755
  • 7
  • 49
  • 63

3 Answers3

25

You've shown the Equals implementation, but not GetHashCode. You need to override both (and in a consistent way) for grouping to work.

Sample GetHashCode implementation:

public override int GetHashCode()
{
    int hash = 23;
    hash = hash * 31 + Latitude.GetHashCode();
    hash = hash * 31 + Longitude.GetHashCode();
    return hash;
}

Note that comparing float values for exact equality is always somewhat risky - but I'd at least expect your unit tests to pass, given that they're not performing any calculations.

mellamokb
  • 56,094
  • 12
  • 110
  • 136
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Just tried it, That's what I was missing. Great Thank you :) public override int GetHashCode() { return ((int)Latitude * 100 )^ ((int)Longitude*100); } – Stéphane Feb 17 '10 at 11:39
  • If that's what your hash code is doing, you should make sure that your equality code matches it - they should be consistent with each other. – Jon Skeet Feb 17 '10 at 11:43
  • Latitude.GetHashCode() ^ Longitude.GetHashCode() gives the same results when inverting Latitude and Longitude. So that wasn't a good solution as I want to ensure that coord(x,y) != coord(y,x); Your code works since the order of the operations matters. Thanks for the precision, that helped :) – Stéphane Feb 18 '10 at 08:45
2

There is an overload to the GrouBy method that takes an IEqualityComparer as a parameter, but is there the equivalent using the group clause?

You can always group by an anonymous type, if you just want a quick inline solution and aren't worried about hitting the exact type for the keys:

var grouped =
  from it in items 
  group it by new {it.Coordinate.Latitude, it.Coordinate.Longitude};
Amy B
  • 108,202
  • 21
  • 135
  • 185
0

A more modern answer; In C#9/.NET6, I believe you can use record types to accomplish what anonymous types do as far as Linq comparisons, but in an automatic way.

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records

You create record types when you want value-based equality and comparison, don't want to copy values, and want to use reference variables.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record

For record types, including record struct and readonly record struct, two objects are equal if they are of the same type and store the same values.

K0D4
  • 2,373
  • 1
  • 27
  • 26