Csharp-新语法

Csharp-新语法

顶级语句

在 C# 9.0 及更高版本中,引入了顶级语句(Top-Level Statements)的概念

这使得编写简单的控制台应用程序变得更加简洁

顶级语句允许你在程序的入口点(通常是 Main 方法)中直接编写代码,而不需要显式地定义 Main 方法

注:在一个项目中只能有一个文件有顶级语句

注:在顶级语句中可以直接使用await关键字调用异步方法


全局using

在 C# 10.0 及更高版本中,引入了全局 using 指令

这使得你可以在一个项目文件中声明所有常用的命名空间,而无需在每个文件中重复这些 using 指令

这不仅可以减少代码冗余,还可以提高代码的可读性和维护性


GlobalUsings.cs

通常我们单独编辑一个文件用来全局using命名空间

global using System;
global using System.Threading.Tasks;
global using Microsoft.EntityFrameworkCore;

ImplicitUsings

当我们在csproj中启用了隐式增加命名空间后,则System、System.Linq等常用命名空间的引用将会被隐藏

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

</Project>

资源释放

在 C# 中,using 声明是一种方便的方式,用于在代码块结束时自动释放资源

using 声明通常用于那些实现了 IDisposable 接口的对象,如数据库连接、文件流等

这可以确保即使在发生异常的情况下,资源也能被正确释放

从 C# 8.0 开始,using 声明有了一个新的语法形式,允许你在没有大括号的情况下声明变量

这种新的 using 声明语法使得代码更加简洁和易读,尤其是在单行操作或简单的资源管理场景中

新的 using 声明语法允许你在没有大括号的情况下声明变量,资源的生命周期会持续到当前作用域结束


文件级命名空间

从 C# 10.0 开始,你可以在单个文件中使用文件级命名空间(File-level namespaces)

这使得你可以在文件的顶部声明命名空间,而无需使用大括号 {} 包围整个文件的内容

注意使用分号结束


可空的引用类型

从 C# 8.0 开始,引入了可空的引用类型(Nullable Reference Types, NRTs)这一特性

可空的引用类型允许你在编译时捕获潜在的 null 引用问题,从而减少运行时的 NullReferenceException 异常

这一特性通过在编译器层面提供更多的静态分析来帮助开发者编写更安全的代码

实体类的默认属性是不可以为null的,可以使用?标识此属性为可以为null

标识后的属性在后面使用时,编译器会提醒你做非空检查


Nullable

当我们在csproj中启用了可空引用类型检查后

对于没有添加?的引用类型变量,如果编译器发现存在为这个变量赋值为null的可能性,则编译器会给出警告信息

<Nullable>enable</Nullable>

Record

从 C# 9.0 开始,引入了 record 类型,这是一种不可变的引用类型,特别适合用于表示不可变数据

record 类型具有许多内置的功能,使得它们在某些场景下比传统的类更加方便和安全

进行ToString方法输出对象时,我们也只能输出对象的类名

在进行对象的比较时,我们不能简单地使用==进行判断

如果我们想要实现这些功能,需要重写ToString和Equals等方法

而record中,编译器帮我们重写了这些方法


定义方法

在 C# 中,record 类型的定义方法可以使用小括号(())或大括号({}

这是因为 C# 9.0 引入了两种不同的记录声明方式:

  • 位置记录(Positional Records)
  • 普通记录(Regular Records)

这两种方式各有特点,适用于不同的场景

但无论是位置记录的属性,还是普通记录的属性,都会被生成在ToString和Equals等方法中

但需要注意的是,普通记录的属性不会被默认生成在构造函数中,但普通记录的属性可以控制读写性


作为普通类使用

当不使用位置记录时,record和class无明显区别,且默认生成这些方法


拷贝

record的编译结果仍然是一个class,即引用类型

结构体在拷贝时直接拷贝,而引用类型在拷贝时仅仅改变了引用指向,而不是复制一个新的对象

我们可以通过with关键字,完全拷贝一个对象出来

public record Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

public class Program
{
    public static void Main()
    {
        Person person = new Person { FirstName = "John", LastName = "Doe" };
        // 完全拷贝
        Person person1 = person with { };
        // 拷贝的同时将FirstName更改为Jane
        Person person2 = person with { FirstName = "Jane" };
    }
}