【问题标题】:LINQ SelectMany and Where extension method ignoring nullsLINQ SelectMany 和 Where 扩展方法忽略空值
【发布时间】:2013-01-22 22:23:00
【问题描述】:

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

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;

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

【问题讨论】:

  • 为什么你认为你的收藏永远是空的?
  • questionsanswerRows 永远不能是 null。在合理的设计中,q.Questionsq.AnswerRows 可能也不应该是 null
  • @Jon 解释为什么它们永远不能为空。如果 QuestionList 是长度为 1 的 List&lt;Type&gt;,我没有理由想阻止 QuestionList[0] 为空。同样,我的示例中的“类型”仍然可以未初始化属性“问题”。即null.
  • @flem 它们永远不能为空,因为 WhereSelectMany 就是这样工作的。如果输入为空,则抛出异常,如果不为空,则结果将是项目序列或空序列。它永远不会null。作为一条规则,您应该避免使用 null 值作为集合或序列,而只需使用空集合。
  • @flem: questionsEnumerable.SelectManyQueryable.SelectMany 决定返回的任何内容。并且保证不为空。

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


【解决方案1】:
survey.QuestionList
    .Where(l => l.Questions != null)
    .SelectMany(l => l.Questions)
    .Where(q => q != null && q.AnswerRows != null)
    .SelectMany(q => q.AnswerRows);

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

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

【讨论】:

    【解决方案2】:
    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);
    

    【讨论】:

    • "where TResult : class" — 是不必要的限制,可防止使用 long 和 int 序列。最后一个“where”也不需要,如有必要,可以将它添加到“外部”。
    • @greatvovan "one can add it outside" 不正确,因为扩展方法返回一个 IEnumerable 不可能添加(或删除)...
    • @BernoulliIT greatvovan 指的是在调用方的查询末尾添加对扩展方法的调用,而不是向序列中添加项目。
    • SelectManyNotNull 是比 SelectNotNull 更合适的名称
    【解决方案3】:

    符合 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 永远不会为 null,因此不需要进行 null 检查(您可能希望根据您的业务逻辑放置 .Any() 检查)。但如果q.Questionsq.AnswerRows 为null,上述代码也永远不会导致异常。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-20
      • 2018-12-24
      • 2021-04-29
      • 1970-01-01
      • 2021-10-19
      相关资源
      最近更新 更多