C# 新语法

C#8.0,9.0,10.0中增加了很多新的语法,这些语法中有一些对开发人员帮助很大,这里做一点整理。

目录

1.全局using指令

2.using 声明

3.文件范围的命名空间声明

4.记录类型


1.全局using指令

        C# 10.0中增加了“全局using指令”语法,我们可以将global修饰符添加到任何using关键字前,这样通过using语句引入的命名空间就可以应用到这个项目的所以源代码中,因此同一个项目中的C#代码就不需要再去重复引入这个命名空间了。在实践中,通常专门创建一个来编写全局using代码的文件,然后把项目中经常用到的命名空间声明到这个C#文件。

        例如你的项目中很多类文件都要引用 Microsoft.Data.Sqlite、System.Text.Json这两个命名空间,,那么可以创建一个Using.cs文件(文件名随意):

global using Microsfot.Data.Sqlite;
global using System.Text.Json;

2.using 声明

        C#中的using关键字可以简化非托管资源的释放,当变量离开using作用的范围后,会自动调用对象的Dispose方法,从而完成非托管资源的释放。但是一段代码中如果有很多非托管资源需要被释放,代码就存在多个嵌套using语句。如下例子:

using(var conn=new SqlConnection(connStr))
{
    conn.Open();
    using(var cmd=conn.CreateCommand())
    {
        cmd.CommandText="Select * from Students";
        using(SqlDataReader reader=cmd.ExecuteReader())
        {
            ...
        }
    }
}

上面三层嵌套大大加重了程序的深度。C#8.0后简化了使用,可以避免嵌套。在声明变量时,如果类型实现了IDisposable或IAsyncDisposable接口,那么可以在变量声明前加上using关键字,这样当代码执行离开被using修饰的变量作用域时,变量只想的对象的Dispose方法就会被调用。

using var conn=new SqlConnection(connStr)
conn.Open();
using var cmd=conn.CreateCommand()
cmd.CommandText="Select * from Students";
using SqlDataReader reader=cmd.ExecuteReader()
...

虽然上述办法很完美,但是也会遇到一些问题:

看下面的例子:

using var outStream = File.OpenWrite("e:/1.txt");
using var writter = new StreamWriter(outStream);
writter.WriteLine("HelloWorld");
string s = File.ReadAllText("e:/1.txt");
Console.WriteLine(s);

执行时会报错误:System.IO.IOException:“设备未就绪。 : 'e:\1.txt'”

原因是outStream和writter两个变量在方法执行结束后才被释放资源,程序执行到第4行时,文件仍然被占用,因此第4行抛出了异常。所以解决办法是:手动添加括号,将要提前释放的资源放到单独的作用域中:

{
    using var outStream = File.OpenWrite("e:/1.txt");
    using var writter = new StreamWriter(outStream);
    writter.WriteLine("HelloWorld");
}
string s = File.ReadAllText("e:/1.txt");
Console.WriteLine(s);

3.文件范围的命名空间声明

        从C#10.0开始,C#允许编写独立的namespace代码声明命名空间,文件中所有类型都是这个命名空间下的成员,这种语法能够减少C#源代码文件的嵌套层次。

namespace TMS.Admin;
class Teacher
{
    pulic int Id {get;set;}
    public string Name {get;set;}
}

4.记录类型

        C#中的==运算符默认判断两个变量是否只想同一个对象,如果两个对象是同一种类型,并且所有属性完全相等,但是他们是两个完全不同的对象,导致==运算符的结果为false,当然你可以通过重写Equals方法,重写==运算符等来解决这个问题,不过这会增加不少的额外代码。

        C#9.0中增加了记录 record类型,编译器会自动生成Equals、GetHashCode等方法。

定义一个record类型很简单:

public record Person(string FirstName, string LastName);

接下来我们声明一些person对象并进行判断:

Person p1 = new Person("无忌", "张");
Person p2 = new Person("翠山", "张");
Person p3 = new Person("无忌", "张");
Console.WriteLine(p1);
Console.WriteLine(p2==p1);
Console.WriteLine(p3==p1);
Console.WriteLine(p1.FirstName);

运行结果如下:

Person { FirstName = 无忌, LastName = 张 }
False
True
无忌

编译器会根据定义自动生成一个包含所有属性的构造方法,因此对于Person类型:下面两种写法都是不允许的:

var p4 = new Person();
var p5 = new Person("haha");

利用反编译起编译person类型,你会发现Person本质还是一个类,只是编译器帮你做了很多工作,record类型提供了为所有属性赋值的构造方法,所有属性都是只读的,对象之间可以进行值得相等比较,并且编译器为类型提供了可读性更强的ToString方法。在编写不可变类型,并且需要进行对象值比较是,使用record类型可以把编写代码的难度大大降低。

         Record类型还以灵活定义:

public record Student(string Name)
{
    public string? Address { get; set; }
    public void SayHi() => Console.WriteLine($"Hi, 我是{Name}");
}

这样Address属性就是可写的,而Name是只读的,所以可以这样构造Student对象:

Student s1 = new Student("张麻子");
Student s2 = new Student("张麻子");
Console.WriteLine(s1 == s2);
s1.Address = "广州";
s1.SayHi();
Console.WriteLine(s1==s2);

运行结果:

True
Hi, 我是张麻子
False

可见属性值不一样,两个record就不相等,无论属性是否为只读。

当然我们也可以额外增加构造函数:

public record Student(string Name)
{
    public string? Address { get; set; }
    public void SayHi() => Console.WriteLine($"Hi, 我是{Name}");
    public Student(string name,string? address):this(name)
    {
        Address=address;
    }
}

虽然上面也是可行的,但是这会让record类型变得复杂,有点背离record类型的初衷,我们还是建议将reco类型的属性设置为只读的,也就是默认形式!

        由于record类型是只读的,所以当我们想生成一个副本时,以及对生成的副本略作更改时,可以使用with关键字:

Person p4 = p1 with { LastName = "谢" };
var p5 = p4;
Console.WriteLine(p4);
Console.WriteLine(p5);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值