什么是null!声明的意思?

isx*_*ker 56 c# c#-8.0 nullable-reference-types

我最近看到了以下代码:

public class Person
{
    //line 1
    public string FirstName { get; }
    //line 2
    public string LastName { get; } = null!;
    //assign null is possible
    public string? MiddleName {get; } = null;

    public Person(string firstName, string lastName, string middleName)
    {
        FirstName = firstName;
        LastName = lastName;
        MiddleName = middleName;
    }

    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
        MiddleName = null;
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上我试着深入研究新的c#8功能.其中之一是NullableReferenceTypes.实际上已经有很多关于它的文章和信息.例如,这篇文章非常好.但是我找不到关于这个新陈述的任何信息null! 有人可以向我提供解释吗?为什么我需要使用它?又什么区别line1line2

Pat*_*eck 72

null!在类型上使用时操作符是什么?

!当在类型上使用时,运算符称为Null Forgiving Operator [ docs ].它是在C#8.0中引入的


技术解释

典型用法

假设这个定义:

class Person
{
  public string? MiddleName;
}
Run Code Online (Sandbox Code Playgroud)

用法是:

void LogPerson(Person person)
{
    Console.WriteLine(person.MiddleName.Length);  // WARNING: may be null
    Console.WriteLine(person.MiddleName!.Length); // No warning
}
Run Code Online (Sandbox Code Playgroud)

该运算符基本上关闭编译器空检查.

内部工作

使用此运算符告诉编译器可以安全访问的内容.在此实例中,您表达了"不关心"null安全性的意图.

在谈论无效安全时,变量可以有两种状态.

  • 可以为空- 可以为空.
  • 不可为空 - 不能为空.

由于C#8.0默认情况下所有引用类型都是非可空的.

这两个新的类型运算符可以修改"可空性":

  • !=从!!
  • Nullable=从Non-Nullable?

这些运营商基本上是彼此的对应物.编译器使用您使用这些运算符定义的信息来确保空安全性.

Non-Nullable 操作员用法.

  1. 可空 Nullable

    • ? 是一个引用类型 - 所以默认情况下不可为空.
    • 我们应用string? x;运算符 - 这使其可以为空.
    • x 工作良好.
  2. 非空的 ?

    • x = null 是一个引用类型 - 所以默认情况下不可为空.
    • string y; 生成警告,因为您将空值分配给不应为null的内容.

y 操作员用法.

string x;
string? y = null;
Run Code Online (Sandbox Code Playgroud)
  1. y = null

    • 非法! - !
    • 赋值的右侧不可为空,但左侧可以为空.
  2. x = y

    • 法律!
    • 赋值的右侧和左侧不可为空.
    • Works自Warning: "y" may be null应用x = y!运算符以y!使其不可为空.

警告!操作者仅关闭编译器检查在一个类型的系统级-在运行时,仍然值可以为空.

这是一种反模式.

你应该尝试避免使用y空赦的运营商.

有一些有效的用例(下面详细介绍),例如单元测试,这个操作符适合使用.在99%的情况下,您最好使用替代解决方案.请不要!在你的代码中打几十个,只是为了使警告静音.想想你的事业是否真的值得使用.

使用 - 但要小心.如果没有具体目的/用例不喜欢不使用它.

它否定了编译器保证的null安全性的影响.

使用!运算符将很难找到错误.如果您有一个标记为不可空的属性,您将假设您可以安全地使用它.但是在运行时,你会突然陷入!困境.由于在绕过编译器检查后,值实际上变为null !.

为什么这个运营商存在呢?

  • 在某些边缘情况下,编译器无法检测到可空值实际上是不可为空的.
  • 更简单的遗留代码库迁移.
  • 在某些情况下,你只是不在乎某些东西是否为空.
  • 使用单元测试时,您可能需要检查代码的行为NullReferenceException.

具体回答你的问题.

!意味着什么?

它告诉编译器null不是null!值.听起来很奇怪,不是吗?

它与null上面的例子相同.由于您将运算符应用于null文字,因此它看起来很奇怪.但这个概念是一样的.

挑选正在发生的事情.

public string LastName { get; } = null!;
Run Code Online (Sandbox Code Playgroud)

该行定义了一个名为y!type 的非可空类属性null.因为它是不可空的,所以从技术上讲,你不能为它指定null - 显然.

但是你通过使用LastName运算符来做到这一点.因为string不是null - 只要编译器关注null安全性.

  • @canbax Nullabilty检查仅受c#8及更高版本支持 (4认同)
  • “你应该尽量不要使用 !Null-Forgiving-Operator。它会抵消编译器保证的空安全的影响。” 您将如何编写用于参数验证的单元测试?这是我最常使用的!在野田时间。 (3认同)
  • @JonSkeet 我将单元测试视为一种特殊情况 - 因为您有意使用空值。“你应该尽量不要使用”我指的是“正常”代码——使用这个运算符会产生意想不到的结果。即使你声明它不可为空,也有一些东西变成了空。我想告诉人们不要到处打“!” - 只是为了摆脱警告。因为那样你就得不到空安全的好处 - 稍后可能会编辑以更清楚 (3认同)
  • 字符串可能为空。您可以像这样分配空值 string str = null; 我正在使用 c# 6 .net 4.6.1。我认为你的陈述是错误的“这一行定义了一个名为 LastNameof 类型字符串的不可为空的类属性。由于它是不可为空的,你在技术上不能为它分配 null - 显然。” (2认同)
  • @canbax`一个字符串可以为null`。我的意思是如果您使用`c#8`并启用`NullableReferenceTypes`功能。如果您尝试将`nul`l分配给`string`,VS会立即警告您。但是另一方面,您可以引入可为空的字符串(`string?s = null`)。在这种情况下没有警告。 (2认同)

Qwe*_*tie 27

null!用于将 null 分配给不可为 null 的变量,这是一种承诺该变量null在实际使用时不会为 null 的方法。

我将null!在 Visual Studio 扩展中使用,其中属性由 MEF 通过反射初始化:

[Import] // Set by MEF
VSImports vs = null!;
[Import] // Set by MEF
IClassificationTypeRegistryService classificationRegistry = null!; 
Run Code Online (Sandbox Code Playgroud)

(我讨厌变量如何在这个系统中神奇地获取值,但事实就是如此。)

我还在单元测试中使用它来标记由设置方法初始化的变量:

public class MyUnitTests
{
    IDatabaseRepository _repo = null!;

    [OneTimeSetUp]
    public void PrepareTestDatabase()
    {
        ...
        _repo = ...
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

如果在这种情况下使用null!,则每次读取变量时都必须使用感叹号,这将是一种麻烦,没有任何好处。

注意:好主意的情况null!相当罕见。我将其视为最后的手段。

  • 这是真正解释为什么人们实际上想在源代码中写入“null!”的唯一答案。 (5认同)
  • 不,事实并非如此,在这两种情况下,您最好根本不分配变量。没有理由默认将这些成员初始化为 null (2认同)
  • @PatrickHollweck不,未能赋值会导致警告CS8618(或在结构构造函数中,错误CS0843。) (2认同)

Jul*_*eur 11

当打开"可空引用类型"功能时,编译器会跟踪它认为代码中的哪些值可能为null.有时编译器可能没有足够的知识.

例如,你可以使用延迟初始化模式,其中的构造不符合实际的(非空)值初始化所有字段,但你总是叫这保证了域非空的初始化方法.在这种情况下,您将面临一个权衡:

  • 如果您将该字段标记为可为空,编译器很高兴,但是当您使用该字段时,您必须不必检查null,
  • 如果你将字段保留为非可空,编译器会抱怨它没有被构造函数初始化(你可以用它来抑制null!),然后可以使用该字段而不进行空值检查.

请注意,通过使用!抑制运算符,您将承担一些风险.想象一下,您实际上并没有按照您的想法一致地初始化所有字段.然后使用null!初始化字段掩盖了a null正在滑入的事实.一些毫无疑问的代码可以接收a null并因此失败.

更一般地说,你可能有一些领域知识:"如果我检查某个方法,那么我知道某些值不为空":

if (CheckEverythingIsReady())
{
   // you know that `field` is non-null, but the compiler doesn't. The suppression can help
   UseNonNullValueFromField(this.field!);
}
Run Code Online (Sandbox Code Playgroud)

同样,您必须对代码的不变性有信心("我知道更好").