Reputation: 11894
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?
Upvotes: 12
Views: 13177
Reputation: 2613
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.
Upvotes: 2
Reputation: 1502696
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.
Upvotes: 25
Reputation: 110181
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};
Upvotes: 2