C#新语法
NET6新特性以及C#新语法
1.顶级语句(C#9.0)
(1):直接在C#文件中直接编写入口方法的代码,不用类,不用Main。经典写法仍然支持,反编译可以查看到,编译器依旧为我们生成了一个"Main$"与Main差不多的方法。实际是语法糖而已
(2):同一个项目中只能有一个文件具有顶级语句。(由于本质是有Main()函数的,所以只能有一个)
(3):顶级语句中可以直接使用await语法,也可以声明函数。(以前写的写法是如果需要用异步的方法)
//反编译如下代码
[CompilerGenerated]
internal class Program
{
private static void <Main>$(string[] args)
{
using (TestDbContext ctx = new TestDbContext())
{
ctx.Books.Where("Price > 0 and Price < 66").Select("new (Title,AuthorName)").ToDynamicArray();
}
}
}
//之前如果需要调用异步方法 Main方法需要修改成这样 顶级语句不需要
static async Task Main(string[] args)
{
}
2.全局Using指令(C#10)
(1):将global修饰符添加到using前,这个命名空间就应用到整个项目,不用重复using.
(2):通常创建一个专门用来编写全局using代码的C#文件
(3):如果csproj中启用了
3.Using资源管理的问题
(1):之前实现了IDisposable接口的对象可以用using进行管理。
(2):如果一段代码中有很多非托管资源需要被释放的话,代码中就会存在着多个嵌套的using语句
(3):在实现了IDisposable, IAsyncDisposable接口的类型的变量声明前加上using,当代码执行离开变量的作用域时,对象就会被释放。
//如下这样写即可 不用用()包含了
using var ctx = new TestDbContext();
//但是这样写也会有坑 比如
using var outStream = File.OpenWrite(@"d:/1.txt");
using var writer = new StreamWriter(outStream);
writer.WriteLine("测试打印ssssssssss");
string s = File.ReadAllText(@"d:/1.txt");
Console.WriteLine(s);
//此时资源并没有得到释放 可以将代码修改成如下
//用{ }决定作用域
using var outStream = File.OpenWrite(@"d:/1.txt");
{
using var writer = new StreamWriter(outStream);
writer.WriteLine("测试打印ssssssssss");
}
string s = File.ReadAllText(@"d:/1.txt");
Console.WriteLine(s);
4.文件范围的命名空间声明(C#10)
(1):之前的版本的C#中,类型必须定义在namespace中
(2):现在
namespace TMS.Admin;
class Teacher
{
public int Id { get; set; }
public string Name { get; set; }
}
5.可空的引用类型(C#8)
知识点
(1):C#数据类型分为值类型和引用类型两种,值类型的变量不可以为空,而引用类型变量可以为空。
(2):如果不注意检查引用类型变量是否可空,就有可能造成程序中出现 NullReferenceException异常
VS2022
(1):csproj中
(2):在引用类型后添加“?”修改符来声明这个类型是可空的,对于没有添加“?”修饰符的引用类型的变量,如果编译器发现存在为这个变量赋值null的可能性的时候,编译器会给出警告信息
(3):可以在警告对应的地方后面加上“!”告诉编译器不会为空,这样可以消除警告,不推荐使用。推荐对对象进行null判断。
6.记录(record)类型(C#9)//重要
(1):C#中的“= =”运算符默认是判断两个变量指向的是否同一个对象,即使两个对象的内容完全一样,也不相等。可以通过重写Equals方法,重写“= =”运算符等来解决这个问题,不过需要开发人员编写非常多的额外代码。
(2):在C#9中增加了记录(record)类型的语法,编译器会为我们自动生成Equals、GetHashcode等方法。
(3):编译器会根据Person类型中的属性定义,自动为Person类型生成包含全部属性的构造方法。注意,默认情况下,编译器会生成一个包含所有属性的构造方法,因此,因此我们编写new Person()、new Person(“yang”)这两种写法都是不可以的,也会生出ToString方法和Equals等方法。
(4):通过反编译看背后原理。避免反编译器的优化,需要把反编译器生成的代码改成C#8.0的语法,结论:record就是一个普通的类。
(5):record数据类型为我们提供了为所有属性赋值的构造方法,所有属性都是只读的,而且对象可以进行值相等的比较,并且提供了可读性强的ToString()返回值(打印所有的属性和值,不再是默认的打印类名)。在需要编写一些不可变类并且需要进行对象值比较的对象时候,record可以帮我们把代码的编写难度大大降低。
(6):可以实现部分属性是只读的、而部分属性是可以读写的。不推荐使用
(7):默认生成的构造方法的行为不能修改,我们可以为类型提供多个构造方法,然后其他构造方法通过this调用默认的构造方法。
(8):也推荐使用只读属性的类型。这样的所有属性都为只读的类型叫做“不可变类型’,可以让程序逻辑简单,减少并发访问,状态管理等的麻烦。
(9):record也是普通类,变量的赋值是引用的传递。这是和结构体不同之处。
(10):生成一个对象的副本(引用不同,值相同),这个对象的其他属性值与原对象的相同,只有一个或者少数几个属性改变
//麻烦的写法:
User u2 = new User(u1.UserName,"test@example",u1.Age);
//with 关键字简化
User u2 = u1 with{Email = "test@example"};//创建的也是拷贝
定义一个record Person
internal record Person(string Id,string Name,string NickName);
Program:
Person p = new Person("1","first","sao");
Person p1 = p;
Console.WriteLine(p.ToString());
Console.WriteLine(p == p1);
Console.WriteLine(Object.ReferenceEquals(p,p1));
Person p2 = p with { };
Console.WriteLine(p2.ToString());
Console.WriteLine(p == p2);
Console.WriteLine(Object.ReferenceEquals(p,p2));
Console.WriteLine("hello");
结果如图:
反编译结果
// Person
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
internal class Person : IEquatable<Person>
{
protected virtual Type EqualityContract
{
[CompilerGenerated]
get
{
return typeof(Person);
}
}
public string Id { get; set/*init*/; }
public string Name { get; set/*init*/; }
public string NickName { get; set/*init*/; }
public Person(string Id, string Name, string NickName)
{
this.Id = Id;
this.Name = Name;
this.NickName = NickName;
base..ctor();
}
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("Person");
stringBuilder.Append(" { ");
if (PrintMembers(stringBuilder))
{
stringBuilder.Append(' ');
}
stringBuilder.Append('}');
return stringBuilder.ToString();
}
protected virtual bool PrintMembers(StringBuilder builder)
{
RuntimeHelpers.EnsureSufficientExecutionStack();
builder.Append("Id = ");
builder.Append((object)Id);
builder.Append(", Name = ");
builder.Append((object)Name);
builder.Append(", NickName = ");
builder.Append((object)NickName);
return true;
}
[System.Runtime.CompilerServices.NullableContext(2)]
public static bool operator !=(Person left, Person right)
{
return !(left == right);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public static bool operator ==(Person left, Person right)
{
return (object)left == right || ((object)left != null && left.Equals(right));
}
public override int GetHashCode()
{
return ((EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Id)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(NickName);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public override bool Equals(object obj)
{
return Equals(obj as Person);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public virtual bool Equals(Person other)
{
return (object)this == other || ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(Id, other.Id) && EqualityComparer<string>.Default.Equals(Name, other.Name) && EqualityComparer<string>.Default.Equals(NickName, other.NickName));
}
public virtual Person <Clone>$()
{
return new Person(this);
}
protected Person(Person original)
{
Id = original.Id;
Name = original.Name;
NickName = original.NickName;
}
public void Deconstruct(out string Id, out string Name, out string NickName)
{
Id = this.Id;
Name = this.Name;
NickName = this.NickName;
}
}
7.init 代表只能初始化的时候赋值
public string Id { get; set/*init*/; }