文章目录
前言
在 ASP.NET Core 开发中,随着 C# 语言的持续更新和 .NET 平台的迭代,开发者可以利用许多新语法特性来简化代码、提高可读性和开发效率。
一、顶级语句(Top-Level Statements)
用途
- 简化 Program.cs 的启动代码,无需显式定义 Main 方法。
使用方式
示例
var builder = WebApplication.CreateBuilder(args); // 添加服务到容器 builder.Services.AddControllers(); var app = builder.Build(); // 配置中间件 app.UseHttpsRedirection(); app.MapControllers(); app.Run();
说明
- 从 ASP.NET Core 6 开始默认使用此模板。
- 隐藏了 namespace、class Program 和 static void Main 的模板代码。
二、全局 using 指令(Global Usings)
用途:
- 将global修饰符添加到using前,这个命名空间就应用到整个项目,不用重复using,减少重复的 using 声明,集中管理全局命名空间引用。
- 通常创建一个专门用来编写全局using代码的C#文件。
- 如果csproj中其用了<ImplicitUsings>enable</ImplicitUsings>,编译器会自动隐式增加对于System、System.Linq等常用命名空间的引入,不同类型项目引入的命名空间也不一样。
配置方式
在项目中添加 GlobalUsings.cs 文件:()
global using Microsoft.AspNetCore.Mvc; global using System.ComponentModel.DataAnnotations; global using MyProject.Models;
效果
- 所有文件自动继承这些命名空间,无需单独引用。
三、Using 资源管理的问题
实现了IDisposable接口的对象可以用using进行管理。
如果一段代码中有很多非托管资源需要被释放的情况下,代码中就会存在多个嵌套的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声明
在实现了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); }
四、文件范围的命名空间声明
在之前的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}"; } }
新版本中可以去掉大括号
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}"; } }
五、可空的引用类型
- C#数据类型分为值类型和引用类型两种,值类型的变量不可以为空,而引用类型的变量可以为空。
- 如果不注意检查引用类型变量是否为空,就可能造成程序中出现NullReferenceException异常。
- csproj中<Nullable>enable</Nullable>启用可空引用类型检查。
- 在引用类型后添加“?”修饰符来声明这个类型是可空的。对于没有添加“?”修饰符的引用类型的变量,如果编译器发现存在为这个变量赋值null的可能性的时候,编译器会给出警告信息。
六、记录类型(Record Types)
- C#中的 = =运算符默认是判断两个变量指向的是否是同一个对象,即使两个对象内容完全一样,也不相等。可以通过重写**Equals()**方法、重写 ==运算符等来解决这个问题,不过需要开发人员编写额外的代码。
- C#新增了记录(Record)类型的语法,编译器会为我们自动生成Equals()、**GetHashcode()**等方法。
用途
- 定义不可变的 DTO(数据传输对象)或 API 请求/响应模型。
使用
编译器会根据Person类型中的属性定义,自动为Person类型生成包含全部属性的构造方法。注意,默认情况下,编译器会生成一个包含所有属性的构造方法,因此,我们编写new Person()、**new Person(“张三”)这两种写法都是不可以的。也会生成ToString()方法和Equals()**等方法。
record数据类型为我们提供了所有属性赋值的构造方法,所有属性都是只读的,而且对象可以进行值相等性比较,并且提供了可读性强的**ToString()**返回值。在需要编写一些不可变类型并且需要进行对象值比较的对象的时候,record可以帮我们把代码的编写难度大大降低。
record可以实现部分属性是只读的、而部分属性是可以读写。
默认生成的构造方法的行为不能修改,我们可以为类型提供多个构造方法,然后其他构造方法通过this调用默认的构造方法。
也推荐使用只读属性的类型。这样的所有属性都为只读的类型叫做“不可变类型”,可以让程序逻辑简单,减少并发访问、状态管理等麻烦。
record默认支持值相等性比较,支持 with 表达式创建副本。
record也是普通类,变量的赋值是引用的传递,这是和结构体的不同支持。
示例:
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="李四"};//
七、字符串插值优化
用途
高性能字符串拼接(减少内存分配)。
示例:
// 使用 $"" 字符串插值 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)。