1

Take a look at this code:

var categories = tokens.SelectMany(x => x.Categories);

if (categories != null)
{
    if (categories.Contains("interp")) //null ref exception
    {
        return null;
    }
}

I get Null Reference Exception when I try fo find "interp" string within categories. So it seems that "categories != null" doesn't work.

I found some suggestions (here How to check if IEnumerable is null or empty?) but they involve using .Any(). But it only makes the exception accure earlier (while using .Any()). Even ?.Any() throws the exception.

Any ideas?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Peter82
  • 21
  • 4
  • 6
    Check `tokens != null` if `tokens` is not `null`, `SelectMany` will never return `null` but empty enumerable – Dmitry Bychenko Mar 15 '19 at 08:23
  • Maybe an element of `tokens` is `null`. `x.Categories` would then throw an exception when you call `Contains`. – Dirk Mar 15 '19 at 08:27
  • Null checks work. If you get an NRE it means something else failed. Post the *full* exception text, including the call stack. Perhaps it was a *different* line that threw, or `Enumerable.Contains` threw because `categories` is empty. Or perhaps `x.Categories` actually contained `null` entries that ended up in the `categories` variable. – Panagiotis Kanavos Mar 15 '19 at 08:27
  • `categories != null` checks the `categories` enumerable, not its contents. If there's any chance that `x.Categories` contains nulls you should include a `Where(category=>category !=null)` – Panagiotis Kanavos Mar 15 '19 at 08:28
  • 1
    What type is your selectmany returning? – Marco Salerno Mar 15 '19 at 08:29
  • @DmitryBychenko If `tokens == null`, `SelectMany` should throw an `ArgumentNullException` though. Am I missing something? – 41686d6564 stands w. Palestine Mar 15 '19 at 08:30
  • 2
    It would be awesome if you could provide a [mcve]. What is the type of `categories`? – mjwills Mar 15 '19 at 08:32
  • Run to this line of code `if (categories.Contains("interp"))`. When you hit it, please go to the `Immediate Window` and type in `?categories`. What is returned? What about for `?categories.Count()`? What about for `?categories.GetType()`? – mjwills Mar 15 '19 at 08:35
  • @Peter82 this question should be closed either as unclear or a duplicate of [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it). There's no way to reproduce what the question claims - an emtpy Enumerable doesn't throw. Comparison to null isn't broken, millions of developers would have noticed if it was. You need to post a *reproducible* example and the *full* exception text, including the call stack. You can get that easily with a simple `Exception.ToString()`. – Panagiotis Kanavos Mar 15 '19 at 08:36

4 Answers4

4

This code will throw an NRE in categories.Contains only if the Categories property is null.

The following code will throw :

class Token
{
    public string[] Categories{get;set;}
}

var tokens=new []{new Token()};
var categories = tokens.SelectMany(x => x.Categories);
if (categories != null)
{
    if (categories.Contains("interp")) 
    {
        Console.WriteLine("Found");
    }
}

But so would

tokens.SelectMany(x => x.Categories).ToArray();

The thing that actually throws is the nested iterator inside SelectMany, not ToArray() or Contains. The stack trace for that exception is :

at System.Linq.Enumerable.<SelectManyIterator>d__17`2.MoveNext()
at System.Linq.Enumerable.Contains[TSource](IEnumerable`1 source, TSource value, IEqualityComparer`1 comparer)
at UserQuery.Main()

SelectMany will try to iterate over each Categories entry, find that the property is actually null and throw.

The quick fix is to add a Where before SelectMany to eliminate null Categories :

var categories = tokens.Where(x=>x.Categories!=null).SelectMany(x => x.Categories);

The real solution is to ensure Categories is never empty - it should be initialized to an empty array, list, whatever upon construction. When reassigned, it should never be set to null.

This example sets the _categories field to new string[0] even if a caller passes null to Categories

class Token
{
    string[] _categories=new string[0];
    public string[] Categories{
        get => _categories;
        set => _categories = value??new string[0];
    }

}

With that, Where(x=>x.Categories !=null) is no longer necessary

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
3

When working with collections and IEnumerable<T> avoid using null; if you have nothing to return, return an empty collection (not null).

In your particular case SelectMany will never return null, but empty collection, that's why categories != null check is useless, and you have to check tokens instead

if (null != tokens)
  // Where(x => x != null) - to be on the safe side if x == null or x.Categories == null
  if (tokens
       .Where(x => x != null && x.Categories != null)
       .SelectMany(x => x.Categories)
       .Contains("interp"))
    return null;

However, constant checking for null makes code being unreadable, that's why try check for null once:

// if tokens is null change it for an empty collection
tokens = tokens ?? new MyToken[0];

...

if (tokens 
      .Where(x => x != null && x.Categories != null)
      .SelectMany(x => x.Categories)
      .Contains("interp"))
    return null;
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
0

var categories = tokens.SelectMany(x => x.Categories).ToList();

add .ToList() and you should know more about where the error is with that information we have in the post, we can only guess

  • `ToList();` - try avoiding *materialization* when you don't have to do it; imaging that `tokens.SelectMany` usually returns *millions* of items (what a huge list we create for nothing since `Contains` doesn't want it) – Dmitry Bychenko Mar 15 '19 at 08:51
0

Can use where clause and make it as list , then just check if there is any element in the list

 var categories = list.Where(x => x.Categories.Contains("interp")).ToList();
 if (categories.Count() == 0)
  {
     return null;

   }
Saif
  • 2,611
  • 3
  • 17
  • 37