条件 ref
表达式
条件表达式可能生成 ref 结果而不是值。 例如,你将编写以下内容以检索对两个数组之一中第一个元素的引用
ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]);
ref struct
ref
结构类型的实例在堆栈上分配,并且不能转义到托管堆,因此不会给 GC 带来任何的压力。相对的,使用中就会有不能逃逸出栈的强制限制。
编译器将 ref
结构类型的使用限制如下:
ref
结构不能是数组的元素类型。ref
结构不能是类或非ref
结构的字段的声明类型。ref
结构不能实现接口。ref
结构不能被装箱为 System.ValueType 或 System.Object。ref
结构不能是类型参数。ref
结构变量不能由 lambda 表达式或本地函数捕获。ref
结构变量不能在async
方法中使用。 但是,可以在同步方法中使用ref
结构变量,例如,在返回 Task 或 Task<TResult> 的方法中。ref
结构变量不能在迭代器中使用。
Span<T> 和 System.ReadOnlySpan<T>
本身是一个 readonly ref struct
,成功的封装出了安全且高性能的内存访问操作,且可在大多数情况下代替指针而不损失任何的性能。
ref struct MyStruct { public int Value { get; set; } } class RefStructGuide { static void Test() { MyStruct x = new MyStruct(); x.Value = 100; Foo(x); // ok Bar(x); // error, x cannot be boxed } static void Foo(MyStruct x) { } static void Bar(object x) { } }
通过 stackalloc
直接在栈上分配内存,并使用 Span<T>
来安全的访问,同样的,这么做可以做到 0 GC 压力。
stackalloc
允许任何的值类型结构,但是要注意,Span<T>
目前不支持 ref struct
作为泛型参数,因此在使用 ref struct
时需要直接使用指针。
static unsafe void RefStructAlloc() { MyStruct* x = stackalloc MyStruct[10]; for (int i = 0; i < 10; i++) { *(x + i) = new MyStruct { Value = i }; } } static void StructAlloc() { Span<int> x = stackalloc int[10]; for (int i = 0; i < x.Length; i++) { x[i] = i; } }
性能敏感时对于频繁调用的函数使用 SkipLocalsInit
C# 为了确保代码的安全会将所有的局部变量在声明时就进行初始化,无论是否必要。一般情况下这对性能并没有太大影响,但是如果你的函数在操作很多栈上分配的内存,并且该函数还是被频繁调用的,那么这一消耗的副作用将会被放大变成不可忽略的损失。
因此你可以使用 SkipLocalsInit
这一特性禁用自动初始化局部变量的行为。
[SkipLocalsInit] unsafe static void Main() { Guid g; Console.WriteLine(*&g); }
上述代码将输出不可预期的结果,因为 g
并没有被初始化为 0。另外,访问未初始化的变量需要在 unsafe
上下文中使用指针进行访问。
表达式体
成员函数、只读属性、构造函数、get
和 set
访问器。
// Expression-bodied constructor public ExpressionMembersExample(string label) => this.Label = label; private string label; // Expression-bodied get / set accessors. public string Label { get => label; set => this.label = value ?? "Default label"; }
throw
表达式
throw
可以用作表达式和语句。 这允许在以前不支持的上下文中引发异常。 这些方法包括条件表达式、null 合并表达式和一些 lambda 表达式。
string arg = args.Length >= 1 ? args[0] : throw new ArgumentException("You must supply an argument"); name = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null"); DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Conversion to a DateTime is not supported.");
ValueTask
从异步方法返回 Task
对象可能在某些路径中导致性能瓶颈。 Task
是引用类型,因此使用它意味着分配对象。
ValueTask
此增强功能对于库作者最有用,可避免在性能关键型代码中分配 Task
。
需要添加 NuGet 包 System.Threading.Tasks.Extensions
才能使用 ValueTask<TResult> 类型。
数字文本语法改进
二进制文本和数字分隔符
public const int OneHundredTwentyEight = 0b1000_0000; public const long BillionsAndBillions = 100_000_000_000; public const double AvogadroConstant = 6.022_140_857_747_474e23;
int binaryValue = 0b_0101_0101;
默认文本表达式
针对默认值表达式的一项增强功能。 这些表达式将变量初始化为默认值。 过去会这么编写:
Func<string, bool> whereClause = default(Func<string, bool>);
现在,可以省略掉初始化右侧的类型:
Func<string, bool> whereClause = default;
增强的泛型约束
可以将类型 System.Enum 或 System.Delegate 指定为类型参数的基类约束。
现在也可以使用新的 unmanaged 约束来指定类型参数必须是不可为 null 的“非托管类型”。
非托管类型 :
• sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal 或 bool
• 任何枚举类型
• 任何指针类型
• 任何用户定义的 struct 类型,只包含非托管类型的字段
public struct Coords<T> where T : unmanaged { public T X; public T Y; } var c = new Coords<int>();
默认接口方法
可以将成员添加到接口,并为这些成员提供实现。
异步流
public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence() { for (int i = 0; i < 20; i++) { await Task.Delay(100); yield return i; } } await foreach (var number in GenerateSequence()) { Console.WriteLine(number); }
索引和范围
0
索引与 sequence[0]
相同。 ^0
索引与 sequence[sequence.Length]
相同。 请注意,sequence[^0]
不会引发异常
对于任何数字 n
,索引 ^n
与 sequence.Length - n
相同。^1
索引最后
[..]表示整个范围,相当于范围 [0..^0]
words[1..4]包括 words[1]
到 words[3]
。 元素 words[4]
不在该范围内
var allWords = words[..]; // contains "The" through "dog". var firstPhrase = words[..4]; // contains "The" through "fox" var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
Null 合并赋值
List<int> numbers = null; int? i = null; numbers ??= new List<int>(); numbers.Add(i ??= 17); numbers.Add(i ??= 20); Console.WriteLine(string.Join(" ", numbers)); // output: 17 17 Console.WriteLine(i); // output: 17