• 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文件(文件名随意):

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

    2.using 声明

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

    1. using(var conn=new SqlConnection(connStr))
    2. {
    3. conn.Open();
    4. using(var cmd=conn.CreateCommand())
    5. {
    6. cmd.CommandText="Select * from Students";
    7. using(SqlDataReader reader=cmd.ExecuteReader())
    8. {
    9. ...
    10. }
    11. }
    12. }

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

    1. using var conn=new SqlConnection(connStr)
    2. conn.Open();
    3. using var cmd=conn.CreateCommand()
    4. cmd.CommandText="Select * from Students";
    5. using SqlDataReader reader=cmd.ExecuteReader()
    6. ...

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

    看下面的例子:

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

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

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

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

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

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

    1. namespace TMS.Admin;
    2. class Teacher
    3. {
    4. pulic int Id {get;set;}
    5. public string Name {get;set;}
    6. }

    4.记录类型

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

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

    定义一个record类型很简单:

    public record Person(string FirstName, string LastName);

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

    1. Person p1 = new Person("无忌", "张");
    2. Person p2 = new Person("翠山", "张");
    3. Person p3 = new Person("无忌", "张");
    4. Console.WriteLine(p1);
    5. Console.WriteLine(p2==p1);
    6. Console.WriteLine(p3==p1);
    7. Console.WriteLine(p1.FirstName);

    运行结果如下:

    1. Person { FirstName = 无忌, LastName = 张 }
    2. False
    3. True
    4. 无忌

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

    1. var p4 = new Person();
    2. var p5 = new Person("haha");

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

             Record类型还以灵活定义:

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

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

    1. Student s1 = new Student("张麻子");
    2. Student s2 = new Student("张麻子");
    3. Console.WriteLine(s1 == s2);
    4. s1.Address = "广州";
    5. s1.SayHi();
    6. Console.WriteLine(s1==s2);

    运行结果:

    1. True
    2. Hi, 我是张麻子
    3. False

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

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

    1. public record Student(string Name)
    2. {
    3. public string? Address { get; set; }
    4. public void SayHi() => Console.WriteLine($"Hi, 我是{Name}");
    5. public Student(string name,string? address):this(name)
    6. {
    7. Address=address;
    8. }
    9. }

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

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

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

  • 相关阅读:
    R语言:利用biomod2进行生态位建模
    什么品牌的台灯适合学生用?适合学生党用的台灯推荐
    Go 结构体深度探索:从基础到应用
    神经调节的知识网络图,图神经网络与知识图谱
    Waves 14混音特效插件合集mac/win
    计算机考研计算机组成原理题库
    MyBatis快速入门
    【owt】owt-client-native-p2p-e2e-test vs2017构建2 :测试单元构建及运行
    警钟:SBP持有的MogaFX外汇储备暴跌9.56亿美元,达到79.6亿美元
    如何下载并安装jdk13版本
  • 原文地址:https://blog.csdn.net/q__y__L/article/details/127570877