为什么在映射到 null 类型和从 null 类型映射时 null 不会导致 NullReferenceException?

Why doesn't a null cause a NullReferenceException when mapping to and from a null type?

我基本上是在尝试遍历大量数据并将 returned 数据查询转换为视图模型中更有限的对象。

我没有编写大量代码,而是在列表上调用 .ForEach(),然后将新条目添加到视图模型的列表中。

这很好用,但是,有一个 属性(地址)是可选的。

当我到达可选项目时,如果数据库中的项目没有条目,我会得到 NullReferenceException

代码示例是:

    var tmp = _context.Person.Include(x => x.Address).ToList();

    tmp.ForEach(x => vm.List.Add(new IndexListItem()
    {
        Name = x.Name,
        Address = x.Address.FirstLine + " " + x.Address.SecondLine,
        ID = x.ID

    }));

我从这个网站上的不同答案中发现,如果我更改地址行,使其显示为:

        Address = x.Address?.FirstLine + " " + x.Address?.SecondLine,

当我在 tmp 中输入一个空条目时,代码现在可以工作了。

我不明白这一点,因为 tmp 上的地址 属性 已经允许空值,而视图模型上的地址 属性 允许空值,那么,为什么要更改行突然不return出错了?

此外,我不必执行 x.Address?.FirstLine? 的原因是因为那是一个字符串并且字符串已经可以为 null 了吗?

空引用异常在这种特殊情况下 是在您尝试访问父对象本身为空的属性 时引起的。

x.Address.FirstLine

即在您的情况下地址为空。

这与您尝试设置的内容无关(即目标视图模型)。

这样做的原因:

x.Address?.FirstLine 

..是因为 'in the background' 它首先检查 Address 是否为空。如果不是,则返回 returns FirstLine,如果是,则返回 null。它在语义上等同于:

if (x.Address == null)
{
    return null;
}
else
{
    return x.Address.FirstLine;
}

这里有一篇博客 post 关于 ? 的介绍。 C# 中的运算符用于一些背景阅读:https://blogs.msdn.microsoft.com/jerrynixon/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator/

您的问题不是 Address 为 null,而是您试图将其分配给另一个允许 null 的 属性,而是您试图访问 .FirstLine在空的东西中。

如果 Address 为 null,那么您尝试使用 .FirstLine 执行的操作等同于 null.FirstLine,但这是行不通的。什么都装不下。

你正在使用的 ? 符号只影响 Address,基本上是说如果 AddressNOT null 给我.FirstLine的值,如果为null,则给我null。

I do not understand this as the Address property on tmp was already allowing nulls, and the Address property on the view model allows nulls, so, why does changing the line suddenly not return an error?

您混淆了保存数据和加载数据。当你把数据保存到数据库时,null是可以接受的,但是当你尝试使用数据时,null是不行的。

null conditional 运算符 (?.) 允许您 "shorten" 一个 if 语句,它类似于:

Address = x.Address?.FirstLine + " " + x.Address?.SecondLine,

string Address = "";
if (x.Address != null)
{
    Address += x.Address.FirstLine;
}
// ....

此外,虽然与您的问题无关,但您使用的代码非常低效,您正在加载 2 个表以获取一些属性,而您可以直接从数据库中获取这些属性:

var vm = _context.Person
    .Select(x => new IndexListItem
    {
        Name = x.Name,
        Address = x.Address?.FirstLine + " " + x.Address?.SecondLine,
        ID = x.ID
    })
    .ToList();

x.Address?.FirstLine 其中 ? 是空传播运算符,这意味着如果 x.Addressnull,则为 FirstLine 设置 null

空传播等效代码

if (x.Address == null)
   return null
else 
   return x.Address.FirstLine

所有引用类型变量都可以为空。因此,将 null 分配给引用类型始终有效。

string 在您的示例中是引用类型。因此你不会得到错误,因为 string x = null 是有效的