1

I have a simple one-line linq query to find items in a list which contain the highest property value, and create a new list with them only if there are a certain count of items that match this criteria. For example:

List<MyObject> objects = new List<MyObject>()
{
    new MyObject() { MyValue = 6 },
    new MyObject() { MyValue = 7 },
    new MyObject() { MyValue = 7 },
    new MyObject() { MyValue = 8 },
    new MyObject() { MyValue = 8 },
};

int countRequired = 2;

List<MyObject> highestValue2Required = objects.GroupBy( o => o.MyValue )
    .OrderByDescending( g => g.Key )
    .Where( g => g.Count() == countRequired )
    .FirstOrDefault()
    .ToList();

Providing there is data in my list that sattisfies these conditions, a new list will be created containing two instances of MyObject, the ones where MyValue = 8.

However, if there is no data matching the criteria, I hit an exception because the IGrouping result from the query is null, and calling ToList() on this doesnt work. For example:

int countRequired = 3;

List<MyObject> highestValue3Required = objects.GroupBy( o => o.MyValue )
    .OrderByDescending( g => g.Key )
    .Where( g => g.Count() == countRequired )
    .FirstOrDefault()
    .ToList();

Because there are only two instances of MyObject in the list where MyValue equals the highest value (which is 8), the query throws an exception

System.ArgumentNullException: 'Value cannot be null.


Is it possible to this null value in order to return an empty list within my "one line" query?

The obvious way to do this would be to remove the ToList() call and simply return an IGrouping result, and only call ToList() when it's not null, but I'm curious as to whether it can be done inline.

devklick
  • 2,000
  • 3
  • 30
  • 47

2 Answers2

1

You will want to use a null-conditional operator after calling FirstOrDefault(). If this method returns null, the result of the entire expression will be null. You can then use the null-coalescing operator to convert a null result to an empty list.

var highestValue3Required = objects.GroupBy( o => o.MyValue )
    .OrderByDescending( g => g.Key )
    .Where( g => g.Count() == countRequired )
    .FirstOrDefault()
    ?.ToList() ?? new List<MyObject>();
Brett Wolfington
  • 6,587
  • 4
  • 32
  • 51
1

The problem is in .FirstOrDefault().ToList(). Since FirstOrDefault() may return a value or NULL, you can't directly call .ToList().

If you want the end result to be null if there are no matches, you can simply add a null conditioner (?) to the FirstOrDefault() call:

List<MyObject> highestValue2Required = objects.GroupBy(o => o.MyValue)
    .OrderByDescending(g => g.Key)
    .Where(g => g.Count() == countRequired)
    .FirstOrDefault()?
    .ToList();

If, however, you're not okay with a null value and you want to have an empty list instead, you can add the following extension method to your project in order to be able to use a one-liner:

static class Extensions
{
    public static IEnumerable<T> OrEmptyIfNull<T>(this IEnumerable<T> source)
    {
        return (source ?? Enumerable.Empty<T>());
    }
}

Now, you can easily do something like this:

List<MyObject> highestValue2Required = objects.GroupBy(o => o.MyValue)
    .OrderByDescending(g => g.Key)
    .Where(g => g.Count() == countRequired)
    .FirstOrDefault()
    .OrEmptyIfNull()
    .ToList();
  • This is great, but doesnt completely answer my question, as it returns a null value instead of an empty list. However,@Brett's answer does exactly that. Thanks for your help! – devklick Mar 14 '19 at 00:51
  • @Klicker I was adding a different solution to the answer already :) Glad you got the answer you were looking for anyway! – 41686d6564 stands w. Palestine Mar 14 '19 at 00:55
  • Just seen your edit. This looks like quite a handy generic extension method. Would I be right in thinking it'll act in the same way as a null conditional operator, but can be used by any IEnumerable collection? – devklick Mar 14 '19 at 00:57
  • @Klicker If you mean a null coalescing operator, then yes. It's a generic extension method for any `IEnumerable` source. I use it in most of my projects and it's very useful, indeed. – 41686d6564 stands w. Palestine Mar 14 '19 at 01:09