在LINQ中,可能为null的警告不正确
var reviewItems = chapter.ChildContainers
.Where(t => t.TypeId == (int)ContainerTypes.Topic)
.SelectMany(t => t.Questions)
.Select(q => new
{
Question = q,
UserQuestion = q.UserQuestions.FirstOrDefault(uq =>
uq.UserId == userId && uq.AlgorithmId == algorithmId)
})
.Where(x => x.UserQuestion != null)
.Select(x => new ReviewItem
{
AlgorithmId = x.UserQuestion.AlgorithmId,
DueDateUTC = x.UserQuestion.DueDate,
Success = x.UserQuestion.Success,
UserId = x.UserQuestion.UserId,
Repititions = x.UserQuestion.Repititions,
FlashCardId = x.UserQuestion.QuestionId,
LastReviewedUTC = x.UserQuestion.LastReviewed,
Metadata = x.UserQuestion.Metadata
})
.ToList();
在 Select 的最后一个 x.UserQuestion 处,我得到
Nullable value type may be null
UserQuestion 这里是 UserQuestionDBO?,但我用 Where() 做了一个空值检查。
我也尝试把它改成下面这样来消除警告
var reviewItems = chapter.ChildContainers
.Where(t => t.TypeId == (int)ContainerTypes.Topic)
.SelectMany(t => t.Questions)
.Select(q => q.UserQuestions.FirstOrDefault(uq =>
uq.UserId == userId && uq.AlgorithmId == algorithmId))
.Where(uq => uq != null)
.Select(uq => new ReviewItem
{
AlgorithmId = uq.AlgorithmId,
DueDateUTC = uq.DueDate,
Success = uq.Success,
UserId = uq.UserId,
Repititions = uq.Repititions,
FlashCardId = uq.QuestionId,
LastReviewedUTC = uq.LastReviewed,
Metadata = uq.Metadata
})
.ToList();
但同样地,在uq的第一行,它告诉我
uq may be null here
尽管在它前面有一个 Where()。
我该如何消除这个警告?
编辑:抱歉,之前把ef-core标签加上来,因为这个问题其实只是关于一个List。尽管使用了EF来获取该列表。
解决方案
第一条 Select() 子句对它查询的每个元素都使用 q.UserQuestions.FirstOrDefault()。这意味着它返回一个可能为null的元素集合。更确切地说,它返回一个 UserQuestionDBO? 的集合。Where() 子句在运行时会过滤掉那些为null的元素,但运行时行为不会影响C#编译器在编译期的静态分析,因此它仍然假设 Where() 子句会返回相同类型的数据,只是元素更少。因此,如果类型相同,仍然会是一个 UserQuestionDBO? 的集合。这也正是为什么第二个 Select() 会警告你在使用可空数据并试图访问其属性时。对可空类型进行解引用总会产生这样的警告。
你可以在这段代码的这部分关闭此类警告,因为你明确知道数据不可能为null,可以通过如下的pragma指令实现:
#pragma warning disable CS8629 // Suppress "Nullable value type may be null"
// your original code
#pragma warning restore CS8629 // Re-enable warning after this block
CS8629是用于对可空值进行解引用的专用警告编号,请参阅此处的空相关警告的完整列表:
但我通常会避免使用pragma指令,因为我认为那是一种“代码异味”(code smell),就像在说“我的代码有一些警告但我没有时间修复它们”。一个更好的解决方案是直接告诉编译器数据不能为null的确切位置,使用被恰如其名的“null-forgiving operator”,它是在你确定不能为null的对象上使用的后缀 !。请注意,它与前缀 !(取反运算符)不同。作为后缀和前缀会有很大差异。所以,你可以把类似 x.UserQuestion.AlgorithmId 的部分改成 x.UserQuestion!.AlgorithmId,编译器就不会再对它纠缠不休。
不过要小心。不要在所有地方都滥用这个空值解除运算符,当编译器给出这样的警告时。警告是有道理的,只有在你百分之百确定数据不能为null且已经采取了对解引用空值的恰当保护时才使用它。
你没有明确提及所使用的C#语言/编译器版本。“ef-core-9.0” 很可能表明你正在使用较新的C# 12 / .NET 8,因此空值解除运算符应该已经可用。但是如果你使用的C#版本低于8,就需要使用显式强制转换来避免警告,那种方法很丑陋,((UserQuestionDBO)x.UserQuestion).AlgorithmId。希望你不需要走到那一步。