在LINQ中,可能为null的警告不正确

编程语言 2026-07-07
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是用于对可空值进行解引用的专用警告编号,请参阅此处的空相关警告的完整列表:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/nullable-warnings

但我通常会避免使用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。希望你不需要走到那一步。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章