我有下面的示例代码,我很想知道如何通过更好地使用SelectMany()
来使其更干净。此时,QuestionList
属性不会为空。我想要的只是一个不null
answerRows
列表,但有时也可以null
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;
我对乔恩关于Enumerable.SelectMany
和空的评论很感兴趣。所以我想用一些假数据尝试我的例子,以便更容易地查看错误在哪里,请参阅以下内容,特别是我如何在SelectMany()
的结果上使用SelectMany()
,现在对我来说更清楚的是,问题必须确保您不要在空引用上使用SelectMany()
, 当我真正读到NullReferenceException
的名字时很明显:(最后把东西放在一起。
同样在这样做时,我意识到在这个例子中使用try { } catch() { }
是没有用的,像往常一样,Jon Skeet 有答案:)延迟执行..
因此,如果您想查看第 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; }
}
}
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);
符合 DRY 的解决方案是在 SelectMany
lambda 表达式中使用空合并运算符??
。
IEnumerable<IQuestion> questions = survey.QuestionList.SelectMany(q => q.Questions ?? Enumerable.Empty<IQuestion>());
IEnumerable<IAnswerRow> answerRows = questions.SelectMany(q => q.AnswerRows ?? Enumerable.Empty<IAnswerRow>());
在 OP 的代码和上面的代码中,questions
和 answerRows
永远不会为 null,因此不需要 null 检查(您可能希望根据业务逻辑进行.Any()
检查)。但是,如果 q.Questions
或 q.AnswerRows
为 null,上面的代码也永远不会导致异常。
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);
使用一些简短且可重用的东西:
public static IEnumerable<T> OrEmpty<T>(this IEnumerable<T> enumerable)
{
return enumerable ?? Enumerable.Empty<T>();
}
然后在代码中,它看起来像:
survey.QuestionList.SelectMany(q => q.Questions.OrEmpty())