为什么此代码会给出“可能的空引用返回”编译器警告?

2023-12-28

考虑以下代码:

using System;

#nullable enable

namespace Demo
{
    public sealed class TestClass
    {
        public string Test()
        {
            bool isNull = _test == null;

            if (isNull)
                return "";
            else
                return _test; // !!!
        }

        readonly string _test = "";
    }
}

当我构建这个时,该行标记为!!!给出编译器警告:warning CS8603: Possible null reference return..

我觉得这有点令人困惑,因为_test是只读的并初始化为非空。

如果我将代码更改为以下内容,警告就会消失:

        public string Test()
        {
            // bool isNull = _test == null;

            if (_test == null)
                return "";
            else
                return _test;
        }

谁能解释这种行为?


我可以做出合理的guess至于这里发生了什么,但这有点复杂:)它涉及到规范草案中描述的空状态和空跟踪 https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/nullable-reference-types-specification#null-state-and-null-tracking。从根本上来说,在我们想要返回的地方,如果表达式的状态是“maybe null”而不是“not null”,编译器会发出警告。

这个答案是某种叙述性的形式,而不仅仅是“这是结论”......我希望这样更有用。

我将通过删除字段来稍微简化示例,并考虑具有这两个签名之一的方法:

public static string M(string? text)
public static string M(string text)

在下面的实现中,我为每个方法指定了不同的编号,这样我就可以明确地参考具体示例。它还允许所有实现出现在同一个程序中。

在下面描述的每种情况下,我们都会做各种事情,但最终会尝试返回text- 所以这是空状态text这很重要。

无条件退货

首先,我们尝试直接返回它:

public static string M1(string? text) => text; // Warning
public static string M2(string text) => text;  // No warning

到目前为止,就这么简单。如果该参数的类型为“可能为空”,则该方法开始时参数的可为空状态为“可能为空”string?如果它是类型,则为“not null”string.

简单的条件返回

现在让我们检查一下是否为 nullif语句条件本身。 (我会使用条件运算符,我相信它会产生相同的效果,但我想更真实地回答这个问题。)

public static string M3(string? text)
{
    if (text is null)
    {
        return "";
    }
    else
    {
        return text; // No warning
    }
}

public static string M4(string text)
{
    if (text is null)
    {
        return "";
    }
    else
    {
        return text; // No warning
    }
}

太好了,所以它看起来像在一个if语句,其中条件本身检查无效性,即每个分支内变量的状态if声明可以不同:在else块中,两段代码中的状态均为“非空”。因此,特别是在 M3 中,状态从“可能为空”变为“不为空”。

带局部变量的条件返回

现在让我们尝试将该条件提升到局部变量:

public static string M5(string? text)
{
    bool isNull = text is null;
    if (isNull)
    {
        return "";
    }
    else
    {
        return text; // Warning
    }
}

public static string M6(string text)
{
    bool isNull = text is null;
    if (isNull)
    {
        return "";
    }
    else
    {
        return text; // Warning
    }
}

BothM5和M6发出警告。因此,我们不仅没有得到 M5 中状态从“可能为空”到“不为空”变化的积极影响(就像我们在 M3 中所做的那样)……我们得到了oppositeM6 中的效果,其中状态从“不为空”变为“可能为空”。这真的让我很惊讶。

所以看起来我们已经了解到:

  • 围绕“如何计算局部变量”的逻辑不用于传播状态信息。稍后会详细介绍。
  • 引入 null 比较可以警告编译器,它之前认为不为 null 的内容最终可能为 null。

忽略比较后无条件返回

让我们通过在无条件返回之前进行比较来看看第二个要点。 (所以我们完全忽略比较的结果。):

public static string M7(string? text)
{
    bool ignored = text is null;
    return text; // Warning
}

public static string M8(string text)
{
    bool ignored = text is null;
    return text; // Warning
}

请注意 M8 感觉它应该与 M2 等效 - 两者都有一个非空参数,它们无条件返回 - 但引入与 null 的比较将状态从“非空”更改为“可能为空”。我们可以通过尝试取消引用来获得进一步的证据text条件之前:

public static string M9(string text)
{
    int length1 = text.Length;   // No warning
    bool ignored = text is null;
    int length2 = text.Length;   // Warning
    return text;                 // No warning
}

请注意如何return声明现在没有警告:国家after执行text.Length是“not null”(因为如果我们成功执行该表达式,它就不可能为null)。所以text参数由于其类型而开始为“not null”,由于 null 比较而变为“maybe null”,然后再次变为“not null”text2.Length.

哪些比较会影响状态?

所以这是一个比较text is null...类似的比较有什么效果?这里还有另外四种方法,全部以不可为空的字符串参数开头:

public static string M10(string text)
{
    bool ignored = text == null;
    return text; // Warning
}

public static string M11(string text)
{
    bool ignored = text is object;
    return text; // No warning
}

public static string M12(string text)
{
    bool ignored = text is { };
    return text; // No warning
}

public static string M13(string text)
{
    bool ignored = text != null;
    return text; // Warning
}

所以尽管x is object现在是推荐的替代品x != null,它们没有相同的效果:只是比较与空(与任何is, == or !=) 将状态从“not null”更改为“maybe null”。

为什么提升条件会产生效果?

回到我们之前的第一个要点,为什么 M5 和 M6 不考虑导致局部变量的条件?这并不让我感到惊讶,但似乎让其他人感到惊讶。将这种逻辑构建到编译器和规范中需要大量工作,而且收益相对较小。这是另一个与可空性无关的示例,其中内联某些内容会产生影响:

public static int X1()
{
    if (true)
    {
        return 1;
    }
}

public static int X2()
{
    bool alwaysTrue = true;
    if (alwaysTrue)
    {
        return 1;
    }
    // Error: not all code paths return a value
}

虽然we我知道alwaysTrue将始终为真,它不满足规范中使代码之后的要求if声明不可达,这正是我们所需要的。

这是另一个关于明确赋值的例子:

public static void X3()
{
    string x;
    bool condition = DateTime.UtcNow.Year == 2020;
    if (condition)
    {
        x = "It's 2020.";
    }
    if (!condition)
    {
        x = "It's not 2020.";
    }
    // Error: x is not definitely assigned
    Console.WriteLine(x);
}

虽然we知道代码将准确输入其中之一if声明体,规范中没有任何内容可以解决这个问题。静态分析工具很可能能够做到这一点,但试图将其放入语言规范中将是一个坏主意,IMO - 静态分析工具拥有各种可以随时间演变的启发式方法很好,但不是那么多用于语言规范。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么此代码会给出“可能的空引用返回”编译器警告? 的相关文章

  • 如何在 Caliburn.Micro 中使用 Conductor 的依赖注入

    我有时用Caliburn Micro http caliburnmicro com创建应用程序 使用最简单的 BootStrapper 我可以像这样使用 IoC 容器 SimpleContainer private SimpleContai
  • C - 计算文件中的单词、字符和行数。字符数

    我必须用 C 编写一段代码 输出给定文件中的字符数 行数和单词数 任务看起来很简单 但我现在真的不确定出了什么问题 所以 这是代码 include
  • OpenCV SVM 给出奇怪的预测结果

    我对 OpenCV 和支持向量机都很陌生 我想使用 SVM 训练具有两个标签的数据集 然后预测给定集合的标签 我当前的集合包含大约 600 行 具有相等的类分布 1 为 300 行 1 为 300 行 包含 34 列 这是我当前用于设置 O
  • 更改图像颜色与透明背景

    我需要使用 c System Drawings 将透明背景上带有绿色圆圈的图像加载到位图图像中 这是最简单的部分 但是 我需要在将其添加到更大的图像之前更改圆圈的颜色 而不影响周围的透明度 就我而言 我需要将圆圈颜色更改为黄色并将其添加为太
  • 有没有办法找到dll公开的所有函数

    我一直在寻找一种方法来获取映射到 dll 中函数名称的所有字符串 我的意思是您可以调用 GetProcAddress 的所有字符串 如果你对 dll 进行十六进制转储 符号 字符串 就在那里 但我认为必须有一个系统调用来获取这些名称 如果您
  • 将视频上传/保存到数据库或文件系统

    我以前从未尝试过保存视频 所以我对此了解不多 我知道如果视频很小 我可以转换为字节数组并保存到数据库 但是为了提高效率 我想了解如何将任何上传的视频保存到我的服务器文件中 然后只保存该文件的文件路径我的数据库表中的视频 我完全不知道如何开始
  • 如何在Unity Inspector中创建多维数组?

    如何在 Unity Inspector 中创建枚举多维数组并使其可序列化 以便我可以从不同的脚本调用它 public enum colors red blue green yellow cyan white purple public in
  • 如何生成可变参数包?

    给定不相关的输入是否可以生成非类型参数包 我的意思是 我想改变这一点 template
  • _MM_TRANSPOSE4_PS 在 GCC 中导致编译器错误?

    我第一次在 GCC 而不是 MSVC 中编译我的数学库 并经历了所有的小错误 我遇到了一个根本没有意义的错误 Line 284 error lvalue required as left operand of assignment 284号
  • 编译器消息“警告:格式‘%s’需要类型‘char *’,但参数 2 具有类型‘char (*)’”

    我正在尝试运行一个简单的 C 程序 但收到此错误 警告 格式 s 需要类型 char 但参数 2 的类型为 char 20 我在跑步Mac OS X v10 8 https en wikipedia org wiki OS X Mounta
  • Cookie 在 ASP.net 中失去价值

    我有以下设置 cookie 的代码 string locale DropDownList this LoginUser FindControl locale SelectedValue HttpCookie cookie new HttpC
  • 如何解析多态 JSON 数组?

    我有一个 JSON 格式的文件 其中包含个人用户的记录 一些用户的记录中间有一个评论字段 我只想解析顶级项目 全名 贡献者姓名 电子邮件 使用 Newtonsoft JSON 解析器 但我似乎无法让它识别单个对象 当我将整个字符串解析为一个
  • 将旧的 Unity 代码升级到 Unity 5

    在触发按钮上播放动画的代码似乎不起作用 我在 Youtube 上看到了一个视频 内容很简单animation Play 它可以在该视频上运行 但我无法让它在我的计算机上运行 我做错了什么还是团结改变了它 请帮助我在网上找不到解决方案 所有
  • 如何将 Boost Spirit 自动规则与 AST 结合使用?

    编辑 当我想在另一个规则上使用它时 我扩展了 sehe 的示例以显示问题 http liveworkspace org code 22lxL7 http liveworkspace org code 22lxL7 17 我正在尝试提高 Bo
  • 链接到ntdll.lib并调用ntdll.dll内部的函数

    我最近正在对私有 API 进行一些研究 我尝试调用诸如NtOpenFile在 ntdll dll 中LoadLibrary and GetProcAddress在运行时 幸运的是 它成功了 今天早上我在电脑上进行了文件搜索 发现ntdll
  • 使用 DataGridViewCheckboxCell 真正禁用 DataGridView 中的复选框

    有谁知道如何使用 DataGridViewCheckboxCell 禁用 DataGridView 中的复选框 我可以将其设置为只读 并设置背景颜色 但我无法让复选框本身显示为禁用状态 有什么想法吗 Guess 你必须自己画 http so
  • 如何使用实体框架设置连接字符串

    我将 EF6 与 MySQL 结合使用 并有一个用于多个数据库的模型 我希望能够在我的表单中设置连接设置 如何以编程方式设置模型的连接字符串 你应该使用EntityConnectionFactory这就是您所需要的 public strin
  • 从其对象获取结构体字段的名称和类型

    例如 我有一个类似这样的结构 struct Test int i float f char ch 10 我有一个该结构的对象 例如 Test obj 现在 我想以编程方式获取字段名称和类型obj 是否可以 顺便说一句 这是 C 你正在要求C
  • 为什么 INT64_MIN 的定义不同?为什么他们的行为不同?

    The stdint h我公司的标题是 define INT64 MIN 9223372036854775808LL 但在我项目的一些代码中 一位程序员写道 undef INT64 MIN define INT64 MIN 92233720
  • 从 C/C++ 程序进行 Ping

    我想编写一个 C 或 C 程序 给定一个 IP 地址 对其进行 Ping 然后根据 Ping 是否成功执行进一步的操作 这个怎么做 尽情享受Ping 页面 http www ping127001 com pingpage htm 其中有一个

随机推荐