C#9.0特性全⾯总结
⽬录
顶级语句
弃元参数
仅初始化设置器(Init only setters)
记录类型 (Record)
模式匹配增强
Type patterns 类型匹配,判断⼀个变量的类型
Relational patterns 关系匹配
Conjunctive and patterns 逻辑与匹配
Disjunctive or patterns 逻辑或匹配
Negated not patterns 逻辑⾮匹配
Parenthesized patterns 带括号的优先级匹配
新的初始化表达式
⽬标类型条件表达式
GetEnumerator 扩展
在本地函数上添加标记
分部⽅法扩展
静态 lambda 表达式
模块初始化代码
协变返回类型
原⽣整数类型
跳过本地初始化 (SkipLocalInit)
函数指针
顶级语句
顶级语句可以删除程序中不必要的代码,以最简单的 Hello, world! 为例:
using System;
namespace HelloWorld {
class Program {
static void Main(string[] args) {
Console.WriteLine("Hello World!");
}
}
}
writeline函数如果使⽤顶级语句的话,可以简化为:
using System;
Console.WriteLine("Hello World!");
如果不使⽤ using ,还可以更加简化:
System.Console.WriteLine("Hello World!");
顶级语句在很多命令⾏程序、⼩⼯具程序中会⾮常有⽤,对应⽤程序的作⽤域或者复杂程度没有任何限制。
注意,⼀个程序中,只能有⼀个⽂件使⽤顶级语句,并且顶级语句必须位于命名空间或类型定义之前!弃元参数
在 lambda 表达式或者匿名函数中如果要忽略某个参数,可以⽤ _ 代替。
var button = new Button("Click Me!");
button.Click += (_, e) => { /* other code goes here. */ };
仅初始化设置器(Init only setters)
创建只能通过对象初始化进⾏赋值的属性。
public class InitDemo {
public string Start { get; init; }
public string Stop { get; init; }
}
// initDemo.Start = "Now"; // Error
// initDemo.End = "Tomorrow"; // Error
var initDemo = new InitDemo {
Start = "Now",
Stop = "Tomorrow"
};
记录类型 (Record)
记录类型,是⼀种引⽤类型,默认是不可变的。记录类型的相等判断可以通过引⽤或者结构进⾏判断的。// 默认不可变的记录类型
public record Person(string Name, int Age);
// 可变记录类型
public record MutablePerson(string Name, int Age) {
public string Name { get; set; } = Name;
public int Age { get; set; } = Age;
}
var person1 = new Person("Zhimin Zhang", 40);
var person2 = new Person("Zhimin Zhang", 40);
Console.WriteLine(person1 == person2); // True 结构相同
Console.WriteLine(person1.Equals(person2)); // True 结构相同
Console.WriteLine(ReferenceEquals(person1, person2)); // False, 引⽤不同
// 改变默认的记录! --> 创建⼀个新的记录。
var person3 = person1 with { Age = 43 };
Console.WriteLine(person3 == person1); // False 结构不同
// 解构 (Destruct) ⼀个记录,将记录的属性提取为本地变量
var (name, age) = person3;
var person4 = new MutablePerson("Zhimin zhang", 40);
person4.Age = 43;
var person5 = new Citizen("Zhimin Zhang", 40, "China");
Console.WriteLine(person1 == person5);
// 记录类型也可以被继承
public record Citizen(string Name, int Age, string Country) : Person(Name, Age);
var citizen = new Citizen("Zhimin Zhang", 40, "China");
Console.WriteLine(person1 == citizen); // False 类型不同;
优点:记录类型是轻量级的不可变类型,可以减少⼤量的代码,可以按照结构和引⽤进⾏⽐较;
缺点:需要实例化⼤量的对象;
如果要更加深⼊的学习记录类型,请查看微软的官⽅⽂档。
模式匹配增强
C# 9 包含了⼀些新的模式匹配增强:
Type patterns 类型匹配,判断⼀个变量的类型
object obj = new int();
var type = obj switch {
string => "string",
int => "int",
_ => "obj"
};
Console.WriteLine(type); // int
Relational patterns 关系匹配
// Relational patterns
var person1 = new Person("Zhimin Zhang", 40);
var inRange = person1 switch {
(_, < 18) => "less than 18",
(_, > 18) => "greater than 18",
(_, 18) => "18 years old!"
};
Console.WriteLine(inRange); // greater than 18
Conjunctive and patterns 逻辑与匹配
// And pattern
var person1 = new Person("Zhimin Zhang", 40);
var ageInRange = person1 switch {
(_, < 18) => "less than 18",
("Zhang Zhimin", _) and (_, >= 18) => "Zhimin Zhang is greater than 18"
};
Console.WriteLine(ageInRange); // Zhimin Zhang is greater than 18
Disjunctive or patterns 逻辑或匹配
// Or pattern
var person1 = new Person("Zhimin Zhang", 40);
var ageInRange = person1 switch {
(_, < 18) => "less than 18",
(_, 18) or (_, > 18) => "18 or greater"
};
Console.WriteLine(ageInRange); // 18 or greater
Negated not patterns 逻辑⾮匹配
// Not pattern
var person1 = new Person("Zhimin Zhang", 40);
var meOrNot = person1 switch {
not ("Zhimin Zhang", 40) => "Not me!",
_ => "Me :-)"
};
Console.WriteLine(meOrNot); // Me :-)
Parenthesized patterns 带括号的优先级匹配
// Parenthesized patterns
var is10 = new IsNumber(true, 10);
var n10 = is10 switch {
((_, > 1 and < 5) and (_, > 5 and < 9)) or (_, 10) => "10",
_ => "not 10"
};
Console.WriteLine(n10); // 10
注意,如果没有匹配到全部的情况,将会出现异常。
新的初始化表达式
在C#9.0中,当已创建对象的类型已知时,可以在new表达式中省略该类型。public class MyClass {
private List<WeatherObservation> _observations = new();
}
Point p = new(1, 1);
Dictionary<string, int> dict = new();
Point[] points = { new(1, 1), new (2, 2), new (3, 3) };
var list = new List<Point> {
new(1, 1), new(2, 2), new(3, 3)
};
优点:可以让代码更加简洁;
缺点:某些情况下会让代码更难理解;
⽬标类型条件表达式
可以隐式转换 null 值,在 C#9.0 中得到了增强。
void TestMethod(int[] list, uint? u) {
int[] x = list ?? new int[0];
var l = u ?? -1u;
}
GetEnumerator 扩展
可以为任意类型添加⼀个 GetEnumerator<T> 扩展,返回⼀个 IEnumerator<T> 或者 IAsyncEnumerator<T> 实例,从⽽在foreach 循环中使⽤。
public static class Extensions {
public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;
}
IEnumerator<string> enumerator = new Collection<string> {
"A", "B", "C"
}.GetEnumerator();
foreach (var item in enumerator) {
Console.WriteLine(item);
}
在本地函数上添加标记
允许在本地函数上添加标记。
static void Main(string[] args) {
[Conditional("DEBUG")]
static void DoSomething([NotNull] string test) {
Console.WriteLine("Do it!");
}
DoSomething("Doing!");
}
分部⽅法扩展
在C#9.0中,移除了分部⽅法的⼏个限制:
1. 必须具有 void 返回类型。
2. 不能具有 out 参数。
3. 不能具有任何可访问性(隐式 private )。
partial class Doing {
internal partial bool DoSomething(string s, out int i);
}
partial class Doing {
internal partial bool DoSomething(string s, out int i) {
i = 0;
return true;
}
}
静态 lambda 表达式
从 C#9.0 开始,可以将 static 修饰符添加到 lambda 表达式或匿名⽅法。静态 lambda 表达式类似于 static 局部函数:静态lambda或匿名⽅法⽆法捕获局部变量或实例状态。所述 static 可以防⽌意外捕获其他变量。
lambda 表达式会捕获上下⽂的变量,不仅会有性能的问题,⽽且还可能出现错误,⽐如:
int number = 0;
Func<string> toString = () => number.ToString(); // number 被⾃动捕获进 toString 函数中
可以在 lambda 表达式前添加 static 关键字,来解决这个问题:
int number = 0;
Func<string> toString = static () => number.ToString(); // 这⾥⽆法再使⽤ number ;
模块初始化代码
可以使⽤ ModuleInitializerAttribute 为组件 (assembly) 定义初始化代码,当初始化/加载时执⾏,可以类⽐类的静态构造函数,但是是组件级别的,要求如下:
必须是静态的、⽆参数的、⽆返回值的⽅法;
不能是范型⽅法,也不能包含在范型类中;
不能是私有函数,必须是公开 (public) 或者内部 (internal) 的函数;
协变返回类型
协变返回类型为重写⽅法的返回类型提供了灵活性。覆盖⽅法可以返回从覆盖的基础⽅法的返回类型派⽣的类型。这对于记录和其他⽀持虚拟克隆或⼯⼚⽅法的类型很有⽤。⽐如:
public virtual Person GetPerson() { return new Person(); }
public override Person GetPerson() { return new Student(); }
在 C# 9.0 中,可以在⼦类中返回更加详细的类型:
public virtual Person GetPerson() { return new Person(); }
public Student Person GetPerson() { return new Student(); }
原⽣整数类型
C#9 添加了两个新的整数类型 (nint 和 nunit) ,依赖宿主机以及编译设定。
nint nativeInt = 55;
Console.WriteLine(nint.MaxValue);
// 在 x86 平台上,输出为 2147483647
// 在 x64 平台上,输出为 9223372036854775807
优点:可以更好的兼容原⽣API;
缺点:缺失平台⽆关性;
跳过本地初始化 (SkipLocalInit)
在 C#9.0 中,可以使⽤ SkipLocalsInitAttribute 来告知编译器不要发射 (Emit) .locals init 标记。
[System.Runtime.CompilerServices.SkipLocalsInit]
static unsafe void DemoLocalsInit() {
int x;
// 注意, x 没有初始化,输出结果不确定;
Console.WriteLine(*&x);
}
优点:跳过本地初始化可以提升程序的性能;
缺点:性能的影响通常不⼤,建议只在极端情况下才使⽤这个;
函数指针
使⽤ delegate* 可以声明函数指针。
unsafe class FunctionPointer {
static int GetLength(string s) => s.Length;
delegate*<string, int> functionPointer = &GetLength;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。