SumGuy
SumGuy

Reputation: 622

Linq to Sql Group By Class

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

Answers (3)

Soto
Soto

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

Servy
Servy

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

Selman Gen&#231;
Selman Gen&#231;

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

Related Questions