13

I have an object with a nullable int property "GroupId".

With a List of this object, I would like to do a GroupBy on this "GroupId". But if I do it, all the null values will form a group.

Example :

Object 1 : GroupId : NULL

Object 2 : GroupId : NULL

Object 3 : GroupId : 1

Object 4 : GroupId : 1

Object 5 : GroupId : 2

Object 6 : GroupId : 2

MyList.GroupBy(f => f.GroupId, key => new {Object = key});

I will get 3 groups.

How can I get 4 groups instead ? A group for each NULL value...

3
  • (filter with not null and then apply group by GroupId) union (filter with null and apply group by to primary key). Commented Feb 27, 2015 at 8:51
  • @JenishRabadiya Note that the GroupBy maintains the order of the elements/groups, while your solution will destroy the ordering.
    – xanatos
    Commented Feb 27, 2015 at 8:58
  • @xanatos hmm.. you are correct. Commented Feb 27, 2015 at 9:02

2 Answers 2

12

This is probably the shortest solution:

var grouped = MyList.GroupBy(f => f.GroupId != null ? (object)f.GroupId : new object(), key => new { Object = key });

Note that the "key" of the groups will be of object type. For null elements I create a new "empty" object. The equality comparer of objects will make so that they are all different. For not-null numbers I simply box them in an object. Boxed integers maintain the equality operator. So:

new object().Equals(new object()) == false // always

and

((object)1).Equals((object)1) == true // always

and

((object)1).Equals((object)2) == false // always

a more correct solution would be implementing an IEqualityComparer<int?>

public class MyComparer : IEqualityComparer<int?> {
    public bool Equals(int? x, int? y) {
        if (x == null || y == null) {
            return false;
        }

        return x.Value == y.Value;
    }

    public int GetHashCode(int? obj) {
        return obj.GetHashCode(); // Works even if obj is null :-)
    }
}

and using it:

var grouped2 = MyList.GroupBy(f => f.GroupId, key => new { Object = key }, new MyComparer());
3
  • 2
    (object)1 == (object)1 always false. (object) == (object) is reference equality, it does not call to object.Equals. Commented Feb 27, 2015 at 8:57
  • It's working fine but the list coming as an anonymous list , is there any way to pull the list as our Model or Entity type of object?
    – Md Aslam
    Commented Nov 6, 2020 at 12:36
  • @MdAslam If you are using EF/EF Core it is a different question...
    – xanatos
    Commented Nov 7, 2020 at 9:23
4

Generic Comparer that can be used without boxing.

public class NullableComparer<T> : IEqualityComparer<T?>
        where T : struct
{
    public bool Equals(T? x, T? y)
    {
        if (x == null || y == null)
        {
            return false;
        }

        return x.Equals(y);
    }

    public int GetHashCode(T? obj)
    {
        return obj.GetHashCode();
    }
}

You would then use it like:

// where GroupId as a nullable Guid 
var grouped = MyList.GroupBy(f => f.GroupId, new NullableComparer<Guid>());

Not the answer you're looking for? Browse other questions tagged or ask your own question.