目录
顶级语句(C#9.0)
using
全局using指令(C#10.0)
using资源管理问题
using声明(C#8.0)
using声明陷阱
错误写法
正确写法
文件范围的命名空间声明(C#10.0)
可空引用类型(C#8.0)
使用
使用!抑制警告
init
record记录类型(C#9.0)
record的原理及深入
record深入
with拷贝
顶级语句(C#9.0)
- 直接在C#文件中直接编写入口方法的代码,不用类,不用Main。经典写法仍然支持。
- 同一个项目中只能有一个文件具有顶级语句。
- 顶级语句中可以直接使用await语法,也可以声明函数
using
全局using指令(C#10.0)
- 将 global 修饰符添加到 using 前,这个命名空间就应用到整个项目,不用重复using。
- 通常创建一个专门用来编写全局using代码的C#文件。
- 如果csproj中启用了<ImplicitUsings>enable</ImplicitUsings>,编译器会自动隐式增加对于System、System.Linq等常用命名空间的引入,不同各类型项目引入的命名空间也不一样。
using资源管理问题
实现了IDisposible接口的对象可以用using进行管理。
问题:如果一段代码中有很多非托管资源需要被释放的话,代码中就会存在着多个嵌套的using语句。
using声明(C#8.0)
在实现了Idisposable/IAsyncDisposable接口的类型的变量声明前加上using,当代码执行离开变量的作用域时,对象就会被释放。
using声明陷阱
错误写法
using var outSteam = File.OpenWrite("e:/1.txt");
using var writer = new StreamWriter(outSteam);
writer.WriteLine("Hello");
string s = File.ReadAllText("e:/1.txt");
Console.WriteLine(s);
正确写法
using (var outSteam = File.OpenWrite("e:/1.txt"))
using (var writer = new StreamWriter(outSteam))
{writer.WriteLine("Hello");
}
string s = File.ReadAllText("e:/1.txt");
Console.WriteLine(s);{using var outSteam = File.OpenWrite("e:/1.txt");using var writer = new StreamWriter(outSteam);writer.WriteLine("Hello");
}
string s = File.ReadAllText("e:/1.txt");
Console.WriteLine(s);
文件范围的命名空间声明(C#10.0)
在之前版本的C#中,类型必须定义在namespace中。
namespace NewGrammar;
class Book
{public int Id { get; set; }public string Name { get; set; }
}
可空引用类型(C#8.0)
C#数据类型分为值类型和引用类型两种,值类型的变量不可以为空,而引用类型变量可以为空。
问题:如果不注意检查引用类型变量是否可空,就有可能造成程序中出现NullReferenceException异常。
使用
- csproj中<Nullable>enable</Nullable>启用可空引用类型检查。
- 在引用类型后添加“?”修饰符来声明这个类型是可空的。对于没有添加“?”修饰符的引用类型的变量,如果编译器发现存在为这个变量赋值null的可能性的时候,编译器会给出警告信息。
class Book
{public int Id { get; set; }public string? Name { get; set; }
}
使用!抑制警告
如果程序员确认被访问的变量、成员确实不会出现为空的情况,也可以在访问可空的变量、成员的时候加上!来抑制编译器的警告。不要滥用。
init
仅在对象构造期间为属性或索引器元素赋值,一旦初始化对象,将无法更改。
class Pig
{public int id { get; init; }
}
record记录类型(C#9.0)
C#中的==运算符默认是判断两个变量指向的是否是同一个对象,即使两个对象内容完全一样,也不相等。可以通过重写Equals方法、重写==运算符等来解决这个问题,不过需要开发人员编写非常多的额外代码。
2、在C#9.0中增加了记录(record)类型的语法,编译器会为我们自动生成Equals、GetHashcode等方法。
namespace SelfReferecingOrganizationalStructureTree;record Book(int Id, string Name);class Program
{static void Main(string[] args){Book b1=new Book(1,"a1");Book b2=new Book(1,"a1");Book b3=new Book(2,"a1");Console.WriteLine(b1.ToString());Console.WriteLine(b1==b2);Console.WriteLine(b2==b3);Console.WriteLine(object.ReferenceEquals(b1, b2));}
}
record的原理及深入
- 编译器会根据Book类型中的属性定义,自动为Book类型生成包含全部属性的构造方法。注意,默认情况下,编译器会生成一个包含所有属性的构造方法,因此,我们编写new Book()、new Book(“a1”)这两种写法都是不可以的。也会生成ToString方法和Equals等方法。
- 通过反编译看背后原理。避免反编译器的优化,需要把反编译器生成的代码改成C#8.0的语法。结论:record就是普通的一个类。
- record数据类型为我们提供了为所有属性赋值的构造方法,所有属性都是只读的,而且对象可以进行值相等性比较,并且提供了可读性强的ToString()返回值。在需要编写一些不可变类并且需要进行对象值比较的对象时候,record可以帮我们把代码的编写难度大大降低。
record深入
- 可以实现部分属性是只读的、而部分属性是可以读写。
record Book(int Id, string Name) {public string? Author { get; set; } }class Program {static void Main(string[] args){Book b1 = new Book(1, "计算机网络");b1.Author = "小明";Console.WriteLine(b1.ToString());} }
- 默认生成的构造方法的行为不能修改,我们可以为类型提供多个构造方法,然后其他构造方法通过this调用默认的构造方法。
record Book(int Id, string Name) {public string? Author { get; set; }public Book(int Id, string Name, string Author) : this(Id, Name){this.Author = Author;} }
- 也推荐使用只读属性的类型。这样的所有属性都为只读的类型叫做“不可变类型”,可以让程序逻辑简单,减少并发访问、状态管理等的麻烦。
with拷贝
- record也是普通类,变量的赋值是引用的传递。这是和结构体不同之处。
- 生成一个对象的副本,这个对象的其他属性值与原对象的相同,只有一个或者少数几个属性改变。
- 麻烦的做法:Book b1 = new Book(1, "计算机网络", "小明");
用with关键字简化:Book b2 = b1 with { Id = 2, Name = "操作系统" }; 创建的也是拷贝。
static void Main(string[] args)
{Book b1 = new Book(1, "计算机网络", "小明");Book b2 = b1 with { Id = 2, Name = "操作系统" };Console.WriteLine(b1);Console.WriteLine(b2);
}