Reputation: 622
I would like to pass a groupedList to several functions, however I'm getting strange results when trying to group by using a class rather than an anonymous type. In the following example the anonymous type returns 2 results, as expected, but the concrete class returns 5, as if it wasn't even grouped.
The Question: Is it possible to do Linq to Sql Group By with concrete classes?
public class person
{
public string Name;
public string State;
}
public class personGroup
{
public string State;
public personGroup(string personState)
{
State = personState;
}
}
void Main()
{
var people = new List<person>();
people.Add(new person {Name = "Bob", State = "Tx"});
people.Add(new person {Name = "Bill", State = "Tx"});
people.Add(new person {Name = "Tracy", State = "Tx"});
people.Add(new person {Name = "Steve", State = "Md"});
people.Add(new person {Name = "Kelly", State = "Md"});
var groupedPeople = people.GroupBy (p => p.State );
groupedPeople.Count().Dump();//Returns 2
var morePeople = people.GroupBy (p => new personGroup(p.State) );
morePeople.Count().Dump();//Returns 5
}
Upvotes: 7
Views: 3478
Reputation: 679
As Servy has stated above you need to create a custom IEqualityComparer of type PersonGroup in order for your example to work.
I've run into this a lot myself so I created a generic EqualityComparer that will work with any model (that uses properties)!
/// <summary>
/// Given two models, return TRUE if all the properties are equal,
/// else return FALSE
/// </summary>
/// <typeparam name="TModel">Type of the models being compared</typeparam>
public class PropertyEqualityComparer<TModel> : IEqualityComparer<TModel>
{
public bool Equals(TModel x, TModel y)
{
PropertyInfo[] props = typeof(TModel).GetProperties();
foreach (PropertyInfo prop in props)
{
if (!Object.Equals(prop.GetValue(x), prop.GetValue(y)))
{
return false;
}
}
return true;
}
public int GetHashCode(TModel obj)
{
int hash = 1;
PropertyInfo[] props = typeof(TModel).GetProperties();
foreach (PropertyInfo prop in props)
{
hash ^= prop.GetValue(obj).GetHashCode();
}
return hash;
}
}
With this class you will be able to do what you what.
// NOTE: Use properties instead of public variables
public class Person
{
public string Name { get; set; }
public string State { get; set; }
}
// NOTE: Use properties instead of public variables
public class personGroup
{
public string State { get; set; }
}
void Main()
{
var people = new List<Person>();
people.Add(new Person{Name = "Bob", State = "Tx"});
people.Add(new Person{Name = "Bill", State = "Tx"});
var morePeople = people.GroupBy(p => new PersonGroup{State = p.State}, new PropertyEqualityComparer<PersonGroup>());
morePeople.Count();
}
Upvotes: 3
Reputation: 203838
The GroupBy
method uses EqualityComparer<T>.Default
to compare the items in question when a custom IEqualityComparer
is not provided (you did not provided one). This will be based on the implementation of IEquatable<T>
of the type T
in question, if there is one, and if not, then it will simply use object.Equals
and object.GetHashCode
of that type.
Your custom type does not provide any implementation whatsoever, it relies entirely on the implementations defined in object
, which, for a reference type, is based on the reference of the object. Each of the personGroup
objects you created have a different reference, and so are different.
Anonymous types don't use that default equality behvaior; they override the definition of Equals
and GetHashCode
to be dependent on the identity of each of the properties they represent.
If you want to use your own custom type to group on, and the default equality semantics aren't what you want, you'll need to either provide a custom IEqualityComparer
implementation, or override Equals
and GetHashCode
for the type in question.
Upvotes: 8
Reputation: 101701
In your second group by, there are 5 groups because the key is always different. personGroups
are compared by reference, and all of the objects have different references.You need to override Equals
and GetHashCode
methods in your personGroup
class to compare your instances based on State, or implement an IEqualityComparer<personGroup>
and pass it to GroupBy
.
Upvotes: 3