.NET新语法

发布于:2025-04-04 ⋅ 阅读:(15) ⋅ 点赞:(0)


前言

在 ASP.NET Core 开发中,随着 C# 语言的持续更新和 .NET 平台的迭代,开发者可以利用许多新语法特性来简化代码、提高可读性和开发效率。

一、顶级语句(Top-Level Statements)

用途

  1. 简化 Program.cs 的启动代码,无需显式定义 Main 方法。

使用方式

  1. 示例

    var builder = WebApplication.CreateBuilder(args);
    
    // 添加服务到容器
    builder.Services.AddControllers();
    
    var app = builder.Build();
    
    // 配置中间件
    app.UseHttpsRedirection();
    app.MapControllers();
    app.Run();
    

说明

  1. ASP.NET Core 6 开始默认使用此模板。
  2. 隐藏了 namespaceclass Programstatic void Main 的模板代码。

二、全局 using 指令(Global Usings)

用途:

  1. global修饰符添加到using前,这个命名空间就应用到整个项目,不用重复using,减少重复的 using 声明,集中管理全局命名空间引用。
  2. 通常创建一个专门用来编写全局using代码的C#文件。
  3. 如果csproj中其用了<ImplicitUsings>enable</ImplicitUsings>,编译器会自动隐式增加对于SystemSystem.Linq等常用命名空间的引入,不同类型项目引入的命名空间也不一样。

配置方式

  1. 在项目中添加 GlobalUsings.cs 文件:()

    global using Microsoft.AspNetCore.Mvc;
    global using System.ComponentModel.DataAnnotations;
    global using MyProject.Models;
    

效果

  1. 所有文件自动继承这些命名空间,无需单独引用。

三、Using 资源管理的问题

  1. 实现了IDisposable接口的对象可以用using进行管理。

  2. 如果一段代码中有很多非托管资源需要被释放的情况下,代码中就会存在多个嵌套的using语句。

    using(var conn = new SqlConnection("Server=....."))
    {
    	conn.Open();
    	using(var cmd = conn.CreateCommand())
    		{
    			cmd.CommandText = "select * from T_Houses";
    			using(var reader = cmd.ExecuteReader())
    			{
    				while(reader.Read())
    				{
    					.....
    				}
    			}
    		}
    	}
    	```
    

对比 using声明

  1. 在实现了IDisposable或者IAsyncDisposable接口类型的变量声明前加上using,当代码执行离开变量的作用域时,对象就会被释放。

    using SqlConnection conn = new SqlConnection("Server....");
    conn.Open();
    using SqlCommand cmd = conn.CreateCommand();
    cmd.CommandText = "select * from T_Houses";
    using SqlDataReader reader = cmd.ExecuteReader();
    while(reader.Read())
    {
    	string name = reader.GetString(reader.GetOrdinal("Name"));
    	Console.WriteLine(name);
    }
    

四、文件范围的命名空间声明

  1. 在之前的C#版本中,类型必须定义在namespace中。

    namespace EFCore4.Entity
    {
        internal class House
        {
            public long Id { get; set; }
            public string Name { get; set; }
            public string Owner { get; set; }
    
            public double Price { get; set; }
            public byte[] RowVersion { get; set; }
    
            public override string ToString()
            {
                return $"Id={Id},Name={Name},Owner={Owner},Price={Price},RowVersion={RowVersion}";
            }
        }
    
  2. 新版本中可以去掉大括号

    namespace EFCore4.Entity;
    
        internal class House
        {
            public long Id { get; set; }
            public string Name { get; set; }
            public string Owner { get; set; }
    
            public double Price { get; set; }
            public byte[] RowVersion { get; set; }
    
            public override string ToString()
            {
                return $"Id={Id},Name={Name},Owner={Owner},Price={Price},RowVersion={RowVersion}";
            }
        }
    

五、可空的引用类型

  1. C#数据类型分为值类型和引用类型两种,值类型的变量不可以为空,而引用类型的变量可以为空。
  2. 如果不注意检查引用类型变量是否为空,就可能造成程序中出现NullReferenceException异常。
  3. csproj中<Nullable>enable</Nullable>启用可空引用类型检查。
  4. 在引用类型后添加“?”修饰符来声明这个类型是可空的。对于没有添加“?”修饰符的引用类型的变量,如果编译器发现存在为这个变量赋值null的可能性的时候,编译器会给出警告信息。

六、记录类型(Record Types)

  1. C#中的 = =运算符默认是判断两个变量指向的是否是同一个对象,即使两个对象内容完全一样,也不相等。可以通过重写**Equals()**方法、重写 ==运算符等来解决这个问题,不过需要开发人员编写额外的代码。
  2. C#新增了记录(Record)类型的语法,编译器会为我们自动生成Equals()、**GetHashcode()**等方法。

用途

  1. 定义不可变的 DTO(数据传输对象)或 API 请求/响应模型。

使用

  1. 编译器会根据Person类型中的属性定义,自动为Person类型生成包含全部属性的构造方法。注意,默认情况下,编译器会生成一个包含所有属性的构造方法,因此,我们编写new Person()、**new Person(“张三”)这两种写法都是不可以的。也会生成ToString()方法和Equals()**等方法。

  2. record数据类型为我们提供了所有属性赋值的构造方法,所有属性都是只读的,而且对象可以进行值相等性比较,并且提供了可读性强的**ToString()**返回值。在需要编写一些不可变类型并且需要进行对象值比较的对象的时候,record可以帮我们把代码的编写难度大大降低。

  3. record可以实现部分属性是只读的、而部分属性是可以读写。

  4. 默认生成的构造方法的行为不能修改,我们可以为类型提供多个构造方法,然后其他构造方法通过this调用默认的构造方法。

  5. 也推荐使用只读属性的类型。这样的所有属性都为只读的类型叫做“不可变类型”,可以让程序逻辑简单,减少并发访问、状态管理等麻烦。

  6. record默认支持值相等性比较,支持 with 表达式创建副本。

  7. record也是普通类,变量的赋值是引用的传递,这是和结构体的不同支持。

  8. 示例:

    internal record Person(long Id, string name,int Age)
    {
        public string? NickName { get; set; }//可读写属性
    	public Person(long Id,string Name,string nickName)//额外的构造方法
    	    :this(Id,Name,Age)
    	{
    		this.NickName = nickName;
    	}
    	
    }
    Person person = new Person(1,"爆米花",12);
    Person person2 = new Person(1, "爆米花",12);
    person.NickName = "Test";
    person2.NickName = "红中";
    Console.WriteLine(person==person2);
    person2.NickName = "Test";
    Console.WriteLine(person == person2);
    Console.WriteLine(person.ToString());
    
    Person p1=new Person(1,"张三",13);
    Person p3= p1 with{};//with创建p1的副本,内容完全一样,但p1和p3指向的不是同一个对象
    Console.WriteLine(Object.ReferenceEquals(p1,p3));
    Person p2=new Person(p1.Id,"李四",p1.Age);//可用With关键字简化
    Person p2 = p1 with{Name="李四"};//
    

七、字符串插值优化

用途

  1. 高性能字符串拼接(减少内存分配)。

  2. 示例:

    // 使用 $"" 字符串插值
    var name = "Alice";
    var message = $"Hello, {name}!"; // 自动优化为 String.Format
    
    // 高性能场景使用插值字符串处理器(C# 10+)
    var handler = new DefaultInterpolatedStringHandler(2, 1);
    handler.AppendLiteral("Hello, ");
    handler.AppendFormatted(name);
    handler.AppendLiteral("!");
    var message = handler.ToStringAndClear();
    

总结

通过合理运用这些新语法,可以显著提升 ASP.NET Core 项目的开发效率和代码质量。建议结合项目需求选择合适特性,并注意版本兼容性(如 .NET 6+ 支持 C# 10/11)。