C#新语法

一、知识来源

Part4-1:C#新语法1_哔哩哔哩_bilibili
Part4-2:C#新语法2_哔哩哔哩_bilibili
Part4-3:C#新语法3可空引用类型_哔哩哔哩_bilibili
Part4-4:C#新语法4record基础_哔哩哔哩_bilibili

二、说明

三、顶级语句(语法糖)


1、主程序中不需要写命名空间、类名,只用写关键语句即可。
2、但是要是把命名空间、类名加上它也不会报错,仍然支持。



3、通过反编译解析底层源码:编译器还是帮我添加了命名空间、类名的代码


4、异步调用也可以直接用


四、全局using指令


1、假设namespace P4_1中有一个IOHelper函数


2、我需要在其它命名空间中使用IOHelper函数,正常情况下需要先using P4_1才能使用IOHelper



3、常规的using非常麻烦,对于某一个方法可能需要在多处进行调用,每次调用都要在当前命名空间去做using,引入 globle using,就using一次就行。其它的地方自动识别,不需要二次using。

  • 总结:项目中任意一个文件using了 namespace A,且使用了global关键字,那么在当前项目其它所有项目文件中在使用namespace A就不需要二次using。



4、csproj启用<ImpliccitUsings>enable</ImpliccitUsings>,常用的包不需要主动using,系统自动引入。

五、可空引用类型


1、新版vs,例如vs2022默认新建项目都是启用了可空引用类型检查。但是强烈建议启用,可以提高代码健壮性。




2、一般情况下类的string类型字段如果不加?修饰为可为空,那么编译器就会报警


3、也可以通过构造函数消除警告

  • 因为string类型字段一定会被赋值


  • 如果构造函数的参数加了?修饰为可为空,那么相应字段又会继续报警


  • 同时有多个未被修饰?的字段,如果构造函数只对其中一个字段赋值,也会告警


4、通过判断消除警告

  • 第一次打印手机号,会有警告,因为PhoneNum是?修饰,可为空
  • 第二次打印手机号,没有警告,因为加了一层if判断。


5、强制让警告消

  • 以上消除警告的方式主要是通过修饰为 可空类型、if判断

  • 假设:虽然Student类中PhoneNum是设置的为空,但是我知道在Console.WriteLine(s1.PhoneNum.ToLower());的时候是没有任何问题的。

  • 通过感叹号(!)强制消除警告

  • 但是强制消除警告还是不能解决底层问题,假设新建的Student类,PhoneNum确实是空值,那么就会报异常


  • 综上:一般不建议使用感叹号强制消除告警。

六、init语法

1、在 C# 9.0 中引入了 init 初始化器语法,它可以用于初始化对象的属性,并且只能在对象被创建时进行赋值,之后便无法修改。这种语法特别适合于不可变对象的初始化。
2、init 关键字用于指示属性只能在对象的构造函数中进行初始化,一旦对象被创建,属性便不能再被修改。

  • 所以对象的某个属性被设定了init,那么在外部更改已创建对象的被设定了init的属性,是会报错

  • 所以对象的某个属性被设定了init,那么在内部非构造函数的其它函数内更改当前对象的init属性,是会报错

3、代码图示




4、代码例子

public class Person
{
    public string Name { get; init; }
    public int Age { get; init; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    void InnerSet()
    {
        // 下面的代码将会导致编译错误
        this.Age = 100;
    }
}

class Program
{
    static void Main()
    {
        Person person = new Person("张三", 25);
        // 下面的代码将会导致编译错误
        person.Name = "李四";
    }
}

七、record类型


1、提高了在DDD模式中进行代码编写的效率

2、record初体验

  • 会自动生成构造函数,无需开发者显性定义


  • 自动重写ToString()、==运算符,无需开发者显性重写


3、record原理


  • record只是C#玩儿的一种语法糖,提高了编码效率,最终还是会被编译器编译成一个普通的类。
  • 通过反编译验证record只是语法糖


4、record深入


  • 一般情况下,使用record时,就只推荐使用最简单的语法,详见2中的演示。不推荐使用其它更复杂的用法,因为违背了record本意:所有的属性的值在构造函数中完成赋值,在其它地方对对象的属性只能读不能写,这里使用了init限制了只能读不能写,可以参考六中对inti语法的演示。

  • 特殊用法:部分属性只读、部分属性可读可写。如图所示,ToString(),和==符号依然能够考虑单独定义的可读可写属性。

  • 特殊用法:自定义构造函数。如图所示,构造了两个构造函数



5、对象的副本


// 引用传递
Person pl = new Person(3, "zack", 18);
Person p2 = pl;
Console.WriteLine(Object.ReferenceEquals(pl, p2)); // true

// 赋值传递(创建对象副本):方法一(常规方法)
Person p3 = new Person(pl.Id, pl.Name, pl.Age);
Console.WriteLine(Object.ReferenceEquals(p3, pl)); // false

// 赋值传递(创建对象副本):方法二(语法糖,推荐)
Person p4 = pl with { }; // 创建p1的副本:p4,p4和p1内容完全一致,但不是同一个对象
Console.WriteLine(p4.ToString());
Console.WriteLine(pl == p4); // true
Console.WriteLine(Object.ReferenceEquals(pl, p4)); // false

//赋值传递(创建对象副本 方法二),但是内容上不完全复制(内容默认全部复制),部分属性的值复制
Person p5 = pl with {Age=19 };
Console.WriteLine(p5.ToString()); // Age是19,其它和p1一样
Console.WriteLine(pl == p5); // false
Console.WriteLine(Object.ReferenceEquals(pl, p5)); // false

public record Person(int Id,string Name)
{
    public int Age { get; set; }
    public Person(int Id, string Name,int Age)
        :this(Id,Name)
    {
        this.Age = Age;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容