Linq to Sql Group By Class

I would like to pass the groupedList to several functions, however, I am getting strange results when I try to group using a class and not an anonymous type. In the following example, the anonymous type returns 2 results as expected, but the concrete class returns 5 as if it were not even grouped.

Question: is it possible to Linq in Sql Group By with specific 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
}

      

+3


source to share


3 answers


The method GroupBy

uses EqualityComparer<T>.Default

to compare the elements in question when a custom one is IEqualityComparer

not provided (you did not provide one). It will be based on the implementation IEquatable<T>

of this type T

, if any, and if not, it will be simple to use object.Equals

and object.GetHashCode

the type.

Your own type does not provide any implementation, it relies entirely on the implementations defined in object

, which for a reference type are based on an object reference. Each of the objects you create personGroup

has a different reference, so they are different.



Anonymous types do not use this equality by default; they redefine Equals

and GetHashCode

as dependent on the identity of each of the properties they represent.

If you want to use your own custom type for the group, and the semantics of the default equality - this is not what you want, you need to either provide a custom implementation IEqualityComparer

, or overridden Equals

, and GetHashCode

for the type.

+4


source


There are 5 groups in your second group because the key is always different. personGroups

are compared by reference and all objects have different references. You need to override methods Equals

and GetHashCode

in the class personGroup

to compare your instances based on state, or implement IEqualityComparer<personGroup>

and pass it GroupBy

.



+3


source


As stated above, Servy states that you need to create a custom IEqualityComparer of type PersonGroup for your example to work.

I've worked a lot with this myself, so I've 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 can do what you need.

// 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();
}

      

0


source







All Articles