前言
C#8.0 C#9.0 和 C#10.0中增加了很多新语法,这里讲解常用、重点的新语法。
部分语法只有VS2022及以上版本才支持,从本节开始使用VS2022+ .net6。
Part4-1 顶级语句(C#9.0)
1.直接在C#文件中直接编写入口方法的代码,不用类,不用Main。经典写法仍然支持。反编译一下了解真相。
2.同一个项目中只能有一个文件具有顶级语句。
3.顶级语句中可以直接使用await语法,也可以声明函数。
原来的Main语法也支持,还可以继续使用。对于代码片段比较简单的情况,推荐缺省Main入口函数,直接写内容。
这其实是语法糖,其实是编译器帮助我们把Main函数给写了。
Part4-2 全局USING指令(C#10.0)
1.将global修饰符添加到using前,这个命名空间就应用到整个项目,不用重复using。(只要有一个地方用了global,全局都不需要再using这个命名空间了。)
2.通常创建一个专门用来编写全局using代码的C#文件。
3.如果csproj中启用了<ImplicitUing>enbale</ImplicitUsing>, 编译器会自动隐式增加对于System、 System.Linq等常用命名空间的引入,不同各类型项目引入的命名空间也不一样。
Part4-3 USING声明(C# 8.0)
USING资源管理的问题
复习: 实现了IDsposible接口的对象可以用using进行管理。
问题: 如果有一段代码中有很多非托管资源需要释放的话,代码中就会存在着多个嵌套的using语句。
using (var conn = new SqlConnection("...")
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "select * from T_Articles";
using(var reader = cmd.ExecuteReader())
{
while(reader.Read())
{
}
}
}
}
USING 声明
在实现了Idispsoable/AAsyncDisposable接口的类型的变量声明前加上using,当代码执行离开变量的作用域时,对象就会被释放。
using SqlConnection conn = new SqlConnection("Data Source=.;Initial Catalog=db1;Integrated Security=True");
conn.Open();
using SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = "select * from T_Articles";
using SqlDataReader reader = cmd.ExecuteReader();
While(reader.Read())
{
string title = reader.GetString(reader.GetOrdinal("Title"));
Console.WriteLine(title);
}
下面我们举一个例子来看看。在while中使用using,离开 {} 就可以自动销毁。
但是,使用过程中也要注意到一些坑。下面是一个例子,先写入文件,再立即读出文件内容,会发现文件被占用,还在继续写,所以报错了。
- 解决方案一:用传统的using大括号,明确声明生命周期。
- 解决方案二:直接用大括号阔起来,看起来怪怪的,但也可以这么用。
Part4-4 文件范围的命名空间声明(C# 10.0)
1.在之前版本的C#中,类型必须定义在namespace中。
2.不用写大括号,直接写在class上面;Teacher这个类还是属于TMS.Admin这个命名空间。
namespace TMS.Admin;
class Teacher
{
public int Id(get;set;)
public int Name(get;set;)
}
Part4-5 可空引用类型 (C# 8.0)
1.复习: C#数据类型分为值类型和引用类型两种,值类型的变量不可以为空,而引用类型变量可以为空。
2.问题: 如果不注意检查引用类型变量是否可控,就有可能造成程序中出现 NullReferenceException 异常。
- csproj中 <Nullable>enable</Nullable>启用可空引用类型检查
- 在引用类型后添加 “?” 修符来声明这个类型是可空的,对于没有添加 “?” 修饰符的引用类型的变量,如果编译器发现存在为这个变量赋值null的可能性的时候,编译器会给出警告信息。
有警告的声明
在定义一个类的属性的时候,如果没有使用 string? Name={get;set;}
这个?方式的方式来声明可空类型的话,那将来就有可能在实例化的时候产生空引用的错误,所以这里会有告警。
Part4-6 记录(Recored)类型(C# 9/0)
1.C#中的==运算默认是判断两个变量指向的是否是同一个对象,即使两个对象内容完全一样,也不相等。可以通过重写Equals方法,重写运算符==等来解决这个问题,不过需要开发人员编写非常多的额外代码。
2.在C#9.0中增加了记录(record)类型的语法,编译器为我们自动生成Equals、GetHashcode等方法。
public record Person(string FirstName, string LastName);
对于很多实体类和对象,如果需要ToString或者比较两个对象是否相等,编译器可以自动来做这个事情。看如下例子:
探究record原理
1.编译器会根据Person类型中的属性定义,自动为Person类型生成包含全部属性的构造方法。注意,默认情况下,编译器会生成一个包含所有属性的构造方法。因此我们编写new Person(), new Person(“Zheng”)这两种写法都是不可以的。也会生成ToString方法和Equals等方法。
2.通过反编译看背后原理。避免反编译器的优化,需要把反编译器生成的代码改成C#8.0语法。结论: record就是普通的一个类。
3. Record数据类型为我们提供了为所有属性赋值的构造方法,所有属性都是只读的,而且对象可以进行值相等性比较,并且提供了可读性强的ToString()返回值。在需要编写一些不可变类并且需要进行对象值比较的对象时候,record可以帮我们把代码的编写难度大大降低。
Record深入
1.可以实现部分属性是只读的,而部分属性可以读写。
2.默认生成的构造方法的行为不能修改,我们可以为类型提供多个构造方法,然后其他构造方法通过this调用默认的构造方法。
3.也推荐使用只读属性的类型。这样的所有属性都为只读的类型叫做“不可变类型”,可以让程序逻辑简单,减少并发访问,状态管理等的麻烦。
自定义record构造函数
internal record Student(int Id, string Name)
{
public string? NickName { get; set; }
public Student(int Id,string Name, string nickName):this(Id,Name)
{
this.NickName = nickName;
}
}
对象的副本
1.record 也是普通类,变量的赋值是引用的传递。这是和结构体不同之处。
2.生成一个对象的副本,这个对象的其他属性值与原对象的相同,只有一个或者少数几个属性改变。麻烦的做法:
User u2 = new User(u1.UserName, "test@example", u1.Age);
3.用with关键字简化,如下。创建的也是拷贝。
User u2 = u1 with {Email="test@example"}
本文版权归个人技术分享站点所有,发布者:chaoqiang,转转请注明出处:https://www.zhengchaoqiang.com/1231.html