C# 什么是空的!声明是什么意思?

C# 什么是空的!声明是什么意思?,c#,c#-8.0,nullable-reference-types,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 fir

我最近看到了以下代码:

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

事实上,已经有很多关于它的文章和信息了。它相当好
但是我没有找到关于这个新语句的任何信息
null
有人能给我解释一下吗
我为什么要用这个

line1
line2
之间有什么区别呢?

这是理解
null意味着理解
运算符。您以前可能将其用作“not”运算符。然而,由于C#8.0及其新特性,操作符得到了第二个含义。它可以用于类型来控制可空性,然后称为


典型用法 假设这一定义:

班级人员
{
//不是每个人都有中间名。我们将“无中间名”表示为“空”
公共字符串?中间名;
}
其用途是:

void LogPerson(个人)
{
Console.WriteLine(person.MiddleName.Length);//警告:可能为空
Console.WriteLine(person.MiddleName!.Length);//无警告
}
此运算符基本上会关闭此用法的编译器null检查

技术说明 零安全 C#8.0试图帮助您管理
null
-值。默认情况下,它们不允许您将
null
分配给所有内容,而是将内容翻转过来,现在要求您显式地标记所有您希望能够保留
null
值的内容

这是一个非常有用的功能,它允许您通过强制做出决定并强制执行来避免
NullReferenceException
s

工作原理 当谈到空安全性时,变量可以处于两种状态

  • 可为null-可以为null
  • 不可为空-不能为空
由于C#8.0,所有引用类型在默认情况下都不可为空。 自C#2.0以来,值类型一直不可为null

“可空性”可由2个新的(类型级别)运算符修改:

  • =从
    可为空
    不可为空
  • =从
    不可空
    可空
这些运营商是相互对应的。 编译器使用这些运算符定义的信息来确保空安全性

例子
操作员用法。 此运算符告诉编译器变量可以包含空值

  • 可空
    字符串?x

    • x
      是引用类型-因此默认情况下不可为空
    • 我们应用
      运算符-这使它可以为null
    • x=null
      工作正常
  • 不可为空的
    字符串y

    • y
      是引用类型-因此默认情况下不可为空
    • y=null
      会生成警告,因为您将null值分配给不应该为null的对象
很高兴知道:使用
string?
System.Nullable的语法糖

操作员用法。
此运算符告诉编译器可以为null的内容是可以安全访问的在这种情况下,您表示“不关心”空安全。

string x;
string? y;
  • x=y
    • 非法
      警告:“y”可能为空
    • 赋值的左侧不可为null,但右侧可为null
    • 所以它不起作用,因为它在语义上是不正确的
  • x=y
    
    • 合法
    • y
      是一个应用了
      类型修饰符的引用类型,因此如果没有其他证明,它可以为null
    • 我们应用
      y
      ,该覆盖其可空性设置,使其不可空
    • 赋值的右侧和左侧不可为空。这在语义上是正确的
警告
operator仅在类型系统级别关闭编译器检查-在运行时,该值可能仍然为null

小心使用! 您应该尝试避免使用空原谅运算符,因为会抵消编译器保证的空安全性的影响,因此使用空原谅运算符可能是系统中存在设计缺陷的征兆。

string x;
string? y;
推理 使用
运算符将创建很难找到的bug。如果您有一个标记为不可为空的属性,您将假定您可以安全地使用它。但在运行时,您突然遇到
NullReferenceException
并抓伤您的头。因为在使用
绕过编译器检查后,值实际上变为null

那么,为什么存在这个操作符呢? 在适当的使用情况下,存在有效的用例(在下面详细列出)。然而,在99%的情况下,您最好选择另一种解决方案。请不要打几十个
,只是为了使警告静音

  • 在某些(边缘)情况下,编译器无法检测到可为null的值实际上是不可为null的
  • 更容易的遗留代码库迁移
  • 在某些情况下,您只是不在乎某些内容是否为空
  • 使用单元测试时,您可能希望在出现
    null
    时检查代码的行为

好啊但是什么是
null平均值?
它告诉编译器
null
不是
可为null的值。听起来很奇怪,不是吗?

它与
y相同来自t
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!);
}
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
}
public class MyUnitTests
{
    IDatabaseRepository _repo = null!;

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