无效是什么!声明是什么意思?

时间:2019-02-16 14:53:45

标签: c# .net c#-8.0 nullablereferencetypes

我最近看过以下代码:

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;
    }
}

基本上,我尝试挖掘c#8的新功能。其中之一是NullableReferenceTypes。实际上,已经有很多文章和信息。例如。 this article很好。 但是我找不到有关此新语句null!的任何信息 有人可以为我提供解释吗?为什么我需要使用这个? line1line2有什么区别?

4 个答案:

答案 0 :(得分:54)

了解null!含义的关键是了解!运算符。您之前可能已将其用作“非”运算符。但是从C#8.0开始,该运算符还可以用于控制Nullabilty的类型

在类型上使用!运算符是什么?

!运算符用于一种类型时,称为Null Forgiving Operator [docs]。它是在C#8.0中引入的


技术说明

典型用法

假设此定义:

class Person
{
  public string? MiddleName;
}

用法为:

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

此运算符基本上会关闭编译器的空检查。

内部运作方式

使用此运算符告诉编译器可以安全地访问的内容可以为空。您表示打算在这种情况下“不关心”空安全性。

在谈论null安全性时,变量可以处于2种状态。

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

自C#8.0起,默认情况下所有引用类型都是不可为空的。

可以通过以下两个新的类型操作符来修改“可空性”:

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

这些运算符基本上是彼此对应的。 编译器使用您使用这些运算符定义的信息来确保null安全。

?操作员用法。

  1. 可为空的string? x;

    • x是引用类型-因此默认情况下不可为空。
    • 我们应用?运算符-使其可以为空。
    • x = null工作正常。
  2. 不可空string y;

    • y是引用类型-因此默认情况下不可为空。
    • y = null生成警告,因为您为不应该为null的对象分配了null值。

!操作员用法。

string x;
string? y = null;
  1. x = y

    • 非法! -Warning: "y" may be null
    • 分配的左侧不可为空,而右侧则可为空。
  2. x = y!

    • 法律!
    • 分配的左右两侧不可为空。
    • y!开始工作,将!运算符应用于y,这使其不可为空。
  

警告 !运算符仅在类型系统级别关闭编译器检查-在运行时,该值仍可能为null。

这是反模式。

您应该尝试尝试避免使用! Null-Forgiving-Operator。

有一些有效的用例(下面将详细介绍),例如适合使用此运算符的单元测试。但是,在99%的情况下,使用替代解决方案会更好。请不要在您的代码中拍打数十个!,只是为了使警告静音。考虑一下您的原因是否真的值得使用。

  

使用-但要小心。如果没有具体目的/用例,请不要使用它。

它抵消了编译器保证的空安全性的影响。

使用!运算符将导致很难发现错误。如果您有一个标记为不可为空的属性,则将假定您可以安全地使用它。但是在运行时,您突然遇到NullReferenceException并挠头。由于使用!绕过编译器检查后,值实际上变为空。

那么为什么这个运算符存在?

  • 在某些情况下,编译器无法检测到可为空的值实际上是不可为空的。
  • 更轻松的传统代码库迁移。
  • 在某些情况下,您根本不在乎某些东西是否为空。
  • 使用单元测试时,您可能希望检查null通过时的代码行为。

专门回答您的问题。

null!是什么意思?

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

与上面示例中的y!相同。 这看起来很奇怪,因为您将运算符应用于null文字。但是概念是相同的。

分清正在发生的事情。

public string LastName { get; } = null!;

此行定义类型为LastName的名称为string的不可为空的类属性。 由于它不可为空,因此从技术上讲,您不能为它分配null。

但是您只是这样做-通过使用null运算符将LastName分配给!。因为null!不为null-就编译器所关心的null安全而言。

答案 1 :(得分:9)

打开“可空引用类型”功能时,编译器会跟踪它认为代码中哪些值可能为null或不是null。有时候编译器可能没有足够的知识。

例如,您可能正在使用延迟初始化模式,在该模式下,构造函数不会使用实际(非空)值初始化所有字段,但是您始终调用一种初始化方法来确保这些字段为非空值。在这种情况下,您将面临一个权衡:

  • 如果将字段标记为可为空,则编译器很高兴,但是在使用该字段时,您不必不必要地检查null,
  • 如果您将该字段保留为不可为空,则编译器将抱怨它不是由构造函数初始化的(可以使用null!禁止它),那么该字段可以不进行空检查而使用。
  • li>

请注意,使用!抑制运算符会带来一些风险。想象一下,您实际上并未像您想象的那样一致地初始化所有字段。然后使用null!初始化字段掩盖了null进入的事实。一些毫无疑问的代码可以收到null并因此失败。

更一般而言,您可能具有一些领域知识:“如果我检查了某种方法,那么我知道某些值不为空”:

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

同样,您必须对代码的不变性充满信心(“我知道得更好”)。

答案 2 :(得分:1)

null! 用于为不可为空的变量赋值为空,这是一种保证变量在实际使用时不会为null的方式。

我使用 null! 和 Entity Framework Core 来标记通过反射初始化的变量:

public class MyDbContext : DbContext
{
    public DbSet<Entity> Entities { get; set; } = null!; // Assigned by EF Core
    public DbSet<Person> People { get; set; } = null!; // Assigned by EF Core
}

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

public class MyUnitTests
{
    IDatabaseRepository _repo = null!;

    [OneTimeSetUp]
    public void PrepareTestDatabase()
    {
        ...
        _repo = ...
        ...
    }
}

如果您在这种情况下使用 null!,则每次读取变量时都必须使用感叹号,这会很麻烦而且没有好处。

答案 3 :(得分:-1)

简而言之,我们在编译器建议存在空变量的任何地方使用它,但我们确定或应该假设它不是。

例如:

if (".js".Equals(Path.GetExtension(@"D:\file.js")))
{
    var directory = Path.GetDirectoryName(includeFile);
    var map = Path.Combine(
        directory, // Compiler: Possible 'null' assignment to nun-nullable entity
        "file.map");
}

纠正方法如下:

if (".js".Equals(Path.GetExtension(@"D:\file.js")))
{
    var directory = Path.GetDirectoryName(includeFile);
    var map = Path.Combine(
        directory!, // The warning is ignored because we are sure the directory variable is not null
        "file.map");
}

还有其他情况,例如在单元测试中我们故意向不接受空参数的函数发送空值
例如:

public class Person
{
    public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));

    public string Name { get; }
}

我们测试:

[TestMethod, ExpectedException(typeof(ArgumentNullException))]
public void NullNameShouldThrowTest()
{
    var person = new Person(null!);
}