Search code examples
c#linqlinq-group

Linq group query gives wrong results


I have some problems in executing a Linq query with the group by clause in a C# project as follows:

public class Stock {
    public string Name {get; set;}
    public List<int> Values {get; set;}
    public float Price {get; set;}
}

// Stock collection
List<Stock> stocks = new List<Stock>()
{
    new Stock(){Name="Prod1", Values=new List<int>{1, 2, 3, 0}, Price=5.0f, 
    new Stock(){Name="Prod1", Values=new List<int>{1, 2, 3, 0}, Price=5.0f,
    new Stock(){Name="Prod11", Values=new List<int>{1, 0, 3, 1}, Price=8.0f,
    new Stock(){Name="Prod11", Values=new List<int>{1, 0, 3, 1}, Price=8.0f,
    new Stock(){Name="Prod18", Values=new List<int>{0, 0, 4, 1}, Price=4.5f,
    new Stock(){Name="Prod20", Values=new List<int>{4, 0, 0, 2}, Price=9.9f,
    new Stock(){Name="Prod20", Values=new List<int>{4, 0, 0, 2}, Price=9.9f,
    new Stock(){Name="Prod29", Values=new List<int>{2, 1, 0, 1}, Price=7.2f,
};

var query = stocks.GroupBy(x => x, (x, g) => new { Count = g.Count(), Values = x}).ToList();

This query gives wrong grouping results.


Solution

  • In order for GroupBy to produce a grouping, the object by which you group must have an override of GetHashCode and Equals, or you need to provide a suitable equality comparer to GroupBy.

    Your Stock does not override GetHashCode/Equals, and your GroupBy query does not use a custom equality comparer, so you get unexpected results.

    Providing suitable overrides will fix this problem:

    public class Stock {
        public string Name {get; set;}
        public List<int> Values {get; set;}
        public float Price {get; set;}
        public override bool Equals(object obj) {
            if (obj == this) return true;
            var other = obj as Stock;
            if (other == null) return false;
            return Name.Equals(other.Name)
                && Price == other.Price
                && Values.SequenceEqual(other.Values);
        }
        public override int GetHashCode() {
            return Name.GetHashCode()
                 + Price.GetHashCode()
                 + Values.Sum();
        }
    }
    

    Demo.