Search code examples
c#linq.net-4.0linq-to-objects

LINQ SelectMany and Where extension method ignoring nulls


I have the below example code, and I am interested to know how I can make this any cleaner, possibly through better use of SelectMany(). At this point the QuestionList property will not be null. All I want is a list of answerRows that are not null, but Questions can sometimes be null too.

IEnumerable<IQuestion> questions = survey.QuestionList
                    .Where(q => q.Questions != null)
                    .SelectMany(q => q.Questions);
            
if(questions == null)
return null;

IEnumerable<IAnswerRow> answerRows = questions
                    .Where(q => q.AnswerRows != null)
                    .SelectMany(q => q.AnswerRows);

if(answerRows == null)
return null;

I was interested by Jon's comment about Enumerable.SelectMany and Null.. so I wanted to try my example with some fake data to more easily see where the error is, please see the below, specifically how I am using SelectMany() on the result of a SelectMany(), its clearer to me now that the problem was having to make sure you don't use SelectMany() on a null reference, obvious when I actually read the NullReferenceException name :( and finally put things together.

Also while doing this, I realised that the use of try { } catch() { } in this example is useless and as usual Jon Skeet has the answer :) deferred execution..

so if you want to see the exception for row 2, comment out the relevant row 1 bits :P, sorry I couldn't figure out how to stop this error without re-writing the code example.

using System;
using System.Collections.Generic;
using System.Linq;

namespace SelectManyExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var questionGroupList1 = new List<QuestionGroup>() {
                new QuestionGroup() {
                    Questions = new List<Question>() {
                        new Question() {
                            AnswerRows = new List<AnswerRow>() {
                                new AnswerRow(),
                                new AnswerRow()
                            }
                        },

                        // empty question, causes cascading SelectMany to throw a NullReferenceException
                        null,

                        new Question() {
                            AnswerRows = new List<AnswerRow>() {
                                new AnswerRow() {
                                    Answers = new List<Answer>() {
                                        new Answer(),
                                        new Answer()
                                    }
                                }
                            }
                        }
                    }
                }
            };

            var questionGroupList2 = new List<QuestionGroup>() {
                null,
                new QuestionGroup()
            };

            IEnumerable<AnswerRow> answerRows1 = null;
            IEnumerable<AnswerRow> answerRows2 = null;

            try
            {
                answerRows1 = questionGroupList1
                    .SelectMany(q => q.Questions)
                    .SelectMany(q => q.AnswerRows);
            }
            catch(Exception e) {
                Console.WriteLine("row 1 error = " + e.Message);
            }

            try
            {
                answerRows2 = questionGroupList2
                    .SelectMany(q => q.Questions)
                    .SelectMany(q => q.AnswerRows);
            }
            catch (Exception e)
            {
                Console.WriteLine("row 2 error = " + e.Message);
            }


            Console.WriteLine("row 1: " + answerRows1.Count());
            Console.WriteLine("row 2: " + answerRows2.Count());
            Console.ReadLine();
        }


    }

    public class QuestionGroup {
        public IEnumerable<Question> Questions { get; set; }
    }

    public class Question {
        public IEnumerable<AnswerRow> AnswerRows { get; set; }
    }

    public class AnswerRow {
        public IEnumerable<Answer> Answers { get; set; }
    }

    public class Answer {
        public string Name { get; set; }
    }
}

Solution

  • survey.QuestionList
        .Where(l => l.Questions != null)
        .SelectMany(l => l.Questions)
        .Where(q => q != null && q.AnswerRows != null)
        .SelectMany(q => q.AnswerRows);
    

    I'd recommend you ensure your collections are never null. null can be a bit of a nuisance if you don't handle it well. You end up with if (something != null) {} all over your code. Then use:

    survey.QuestionList
        .SelectMany(l => l.Questions)
        .SelectMany(q => q.AnswerRows);