LINQ SelectMany和Where扩展方法忽略空值

时间:2013-01-22 22:23:01

标签: c# linq .net-4.0 linq-to-objects

我有以下示例代码,我很想知道如何使这更干净,可能更好地使用SelectMany()。此时,QuestionList属性不会为null。我想要的只是answerRows的列表,它不是空的,但Questions有时也可以为空。

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;

更新 稍微更改了我的代码,因为我的示例使用var

时不够清楚

问题更多的是帮助我更多地了解LINQ的使用。

更新2:

我对Jon关于Enumerable.SelectMany和Null的评论感兴趣。 所以我想尝试使用一些假数据来更轻松地查看错误的位置,请参阅下面的内容,具体说明我如何使用SelectMany()SelectMany()的结果,对我来说更清楚既然问题是必须确保你没有在空引用上使用SelectMany(),那么当我真正阅读NullReferenceException名称时显而易见:(并最终把事情放在一起。

同样在这样做时,我意识到在这个例子中使用try { } catch() { }是没用的,像往常一样Jon Skeet有answer :)延迟执行..

所以,如果你想看到第2行的异常,请注释相关的第1行:P,抱歉,我无法弄清楚如何在不重写代码示例的情况下停止此错误。

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; }
    }
}

3 个答案:

答案 0 :(得分:40)

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

我建议你确保你的收藏永远不会null。如果你处理得不好,null可能会有点麻烦。您最终会在代码中找到if (something != null) {}。然后使用:

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

答案 1 :(得分:8)

public static IEnumerable<TResult> SelectNotNull<TSource, TResult>(
    this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
    where TResult : class
{
    return source.Select(selector)
        .Where(sequence => sequence != null)
        .SelectMany(x => x)
        .Where(item => item != null);
}

然后,您可以执行以下操作:

var allAnswers = survey.QuestionList
    .SelectNotNull(list => list.Questions)
    .SelectNotNull(question => question.AnswerRows);

答案 2 :(得分:0)

一种符合DRY的解决方案是在您的SelectMany lambda表达式中使用the null-coalescing operator ??

IEnumerable<IQuestion> questions = survey.QuestionList.SelectMany(q => q.Questions ?? Enumerable.Empty<IQuestion>());

IEnumerable<IAnswerRow> answerRows = questions.SelectMany(q => q.AnswerRows ?? Enumerable.Empty<IAnswerRow>());

在OP的代码和上面的代码中,questionsanswerRows永远不会为空,因此不需要空检查(根据情况,您可能希望进行.Any()检查)您的业​​务逻辑)。但是,如果q.Questionsq.AnswerRows为空,则上面的代码也永远不会导致异常。