您的问题似乎不完整,无法确定您具体想要了解的内容。如果您是想询问如何提高C语言编程能力,以下是一些建议:1. **基础知识**:确保您对C语言的基础知识有扎实的理解,包括变量、数据类型、运算符、控制结构(如if语句、循环)等。2. **实践编程**:通过编

摘要:前言 预计在 2024 年 11 月,C# 13 将与 .NET 9 一起正式发布。今年的 C# 更新主要集中在 ref struct 上进行了许多改进,并添加了许多有助于进一步提高生产力的便利功能。 本文将介绍预计将在 C# 13 中添加
前言 预计在 2024 年 11 月,C# 13 将与 .NET 9 一起正式发布。今年的 C# 更新主要集中在 ref struct 上进行了许多改进,并添加了许多有助于进一步提高生产力的便利功能。 本文将介绍预计将在 C# 13 中添加的功能。 注意:目前 C# 13 还未正式发布,因此以下内容可能会发生变化。 在迭代器和异步方法中使用 ref 和 ref struct 在使用 C# 进行编程时,你是否经常使用 ref 变量和 Span 等 ref struct 类型?然而,这些不能在迭代器和异步方法中使用,于是必须使用局部函数等来避免在迭代器和异步方法中直接使用 ref 变量 ref struct 类型,这非常不方便。 这个缺点在 C# 13 中得到了改善,现在迭代器和异步方法也可以使用 ref 和 ref struct 了! 在迭代器中使用 ref 和 ref struct 的例子: IEnumerable<float> GetFloatNumberFromIntArray(int[] array) { for (int i = 0; i < array.Length; i++) { Span<int> span = array.AsSpan(); // 进行一些处理... ref float v = ref Unsafe.As<int, float>(ref array[i]); yield return v; } } 在异步方法中使用 ref struct 的例子: async Task ProcessDataAsync(int[] array) { Span<int> span = array.AsSpan(); // 进行一些处理... ref int element = ref span[42]; element++; await Task.Yield(); } 为了展示功能,我使用了不适当且含糊不清的“一些处理”,不过重要的是现在可以使用 ref 和 ref struct 了! 但是,有一点需要注意,ref 变量和 ref struct 类型的变量不能超出 yield 和 await 的边界使用。例如,以下示例将导致编译错误。 async Task ProcessDataAsync(int[] array) { Span<int> span = array.AsSpan(); // 进行一些处理... ref int element = ref span[42]; element++; await Task.Yield(); element++; // 错误:对 element 的访问超出了 await 的边界 } 虽然我们已经说到这里,但我想可能有人会疑惑,到底 ref 和 ref struct 是什么,所以我稍微解释一下。 在 C# 中,可以使用 ref 来获取变量的引用。这样,就可以通过引用来更改原始变量。以下是一个例子: void Swap(ref int a, ref int b) // ref 表示引用 { int temp = a; a = b; b = temp; // 到这里,a 和 b 已经交换了 } int x = 1; int y = 2; Swap(ref x, ref y); // 获取 x 和 y 的引用,调用 Swap 来交换 x 和 y 另一方面,ref struct 是用于定义只能存在于堆栈上的值类型的。这是为了避免垃圾收集的开销。然而,由于 ref struct 只能存在于堆栈上,所以在 C# 13 之前,它不能在迭代器和异步方法等地方使用。 顺便一提,ref struct 之所以带有 ref,是因为 ref struct 的实例只能存在于堆栈上,其遵循的生命周期规则与 ref 变量相同。 allows ref struct 泛型约束 在以前,ref struct 不能作为泛型类型参数使用,因此,考虑到代码的可重用性,引入了泛型,但最终 ref struct 不能使用,必须为 Span 或 ReadOnlySpan 重新编写相同的处理,于是就很麻烦。 在 C# 13 中,泛型类型也可以使用 ref struct 了: using System; using System.Numerics; Process([1, 2, 3, 4], Sum); // 10 Process([1, 2, 3, 4], Multiply); // 24 T Process<T>(ReadOnlySpan<T> span, Func<ReadOnlySpan<T>, T> method) { return method(span); } T Sum<T>(ReadOnlySpan<T> span) where T : INumberBase<T> { T result = T.Zero; foreach (T value in span) { result += value; } return result; } T Multiply<T>(ReadOnlySpan<T> span) where T : INumberBase<T> { T result = T.One; foreach (T value in span) { result *= value; } return result; } 为什么像 ReadOnlySpan<T> 这样的 ref struct 类型可以作为 Func 的类型参数呢?为了调查这个问题,我查看了 .NET 的 源代码,发现 Func 类型的泛型参数是这样定义的: public delegate TResult Func<in T, out TResult>(T arg) where T : allows ref struct where TResult : allows ref struct; 如果在泛型参数上添加 allow ref struct 约束,那么就可以将 ref struct 类型传递给该参数。 这确实是一个方便的功能。 ref struct 也可以实现接口 在 C# 13 中,ref struct 可以实现接口。 如果将此功能与 allows ref struct 结合使用,那么也可以通过泛型类型传递引用: using System; using System.Numerics; int a = 10; // 使用 Ref<int> 保存 a 的引用 Ref<int> aRef = new Ref<int>(ref a); // 传递 Ref<int> Increase<Ref<int>, int>(aRef); Console.WriteLine(a); // 11 void Increase<T, U>(T data) where T : IRef<U>, allows ref struct where U : INumberBase<U> { ref U value = ref data.GetRef(); value++; } interface IRef<T> { ref T GetRef(); } // 为 Ref<T> 这样的 ref struct 实现接口 ref struct Ref<T> : IRef<T> { private ref T _value; public Ref(ref T value) { _value = ref value; } public ref T GetRef() { return ref _value; } } 这样一来,编写 ref struct 相关的代码就变得更容易了。另外,也能给各种 ref struct 实现的枚举器实现 IEnumerator 之类的接口了。 集合类型和 Span 也可以使用 params 在以前,params 只能用于数组类型,但从 C# 13 开始,它也可以用于其他集合类型和 Span。 params 是一种功能,允许在调用方法时直接指定任意数量的参数。 例如, Test(1, 2, 3, 4, 5, 6); void Test(params int[] values) { } 如上所示,可以直接指定任意数量的 int 参数。 从 C# 13 开始,除了数组类型外,其他集合类型、Span、ReadOnlySpan 类型以及与集合相关的接口也可以添加 params: Test(1, 2, 3, 4, 5, 6); void Test(params ReadOnlySpan<int> values) { } // 或者 Test(1, 2, 3, 4, 5, 6); void Test(params List<int> values) { } // 接口也可以 Test(1, 2, 3, 4, 5, 6); void Test(params IEnumerable<int> values) { } 这也很方便! field 关键字 在实现 C# 的属性时,经常需要定义一大堆字段,如下所示... partial class ViewModel : INotifyPropertyChanged { // 定义字段 private int _myProperty; public int MyProperty { get => _myProperty; set { if (_myProperty != value) { _myProperty = value; OnPropertyChanged(); } } } } 因此,从 C# 13 开始,field 关键字将派上用场! partial class ViewModel : INotifyPropertyChanged { public int MyProperty { // 只需使用 field get => field; set { if (field != value) { field = value; OnPropertyChanged(); } } } } 不再需要自己定义字段,只需使用 field 关键字,字段就会自动生成。 这也非常方便! 部分属性 在编写 C# 时,常见的问题之一是:属性不能添加 partial 修饰符。 在 C# 中,可以在类或方法上添加 partial,以便分别进行声明和实现。此外,还可以分散类的各个部分。它的主要用途是在使用源代码生成器等自动生成工具时,指定要生成的内容。 例如: partial class ViewModel { // 这里只声明方法,实现部分由工具自动生成 partial void OnPropertyChanged(string propertyName); } 然后自动生成工具会生成以下代码: partial class ViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; partial void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new(propertyName)); } } 开发者只需要声明 OnPropertyChanged,其实现将全部由自动生成,从而节省了开发者的时间。 从 C# 13 开始,属性也支持 partial: partial class ViewModel { // 声明部分属性 public partial int MyProperty { get; set; } } partial class ViewModel { // 部分属性的实现 public partial int MyProperty { get { // ... } set { // ... } } } 这样,属性也可以由工具自动生成了。 锁对象 众所周知,lock 是一种功能,通过监视器用于线程同步。 object lockObject = new object(); lock (lockObject) { // 关键区 } 但是,这个功能的开销其实很大,会影响性能。 为了解决这个问题,C# 13 实现了锁对象。要使用此功能,只需用 System.Threading.Lock 替换被锁定的对象即可: using System.Threading; Lock lockObject = new Lock(); lock (lockObject) { // 关键区 } 这样就可以轻松提高性能了。 初始化器中的尾部索引 索引运算符 ^ 可用于表示集合末尾的相对位置。从 C# 13 开始,初始化器也支持此功能: var x = new Numbers { Values = { [1] = 111, [^1] = 999 // ^1 是从末尾开始的第一个元素 } // x.Values[1] 是 111 // x.Values[9] 是 999,因为 Values[9] 是最后一个元素 }; class Numbers { public int[] Values { get; set; } = new int[10]; } ESCAPE 字符 在 Unicode 字符串中,可以使用 \e 代替 \u001b 和 \x1b。\u001b、\x1b 和 \e 都表示 ESCAPE 字符。它们通常用于表示控制字符。 \u001b 表示 Unicode 转义序列,\u 后面的 4 位十六进制数表示 Unicode 代码点 \x1b 表示十六进制转义序列,\x 后面的 2 位十六进制数表示 ASCII 代码 \e 表示 ESCAPE 字符本身 推荐使用 \e 的原因是,可以避免在十六进制中的混淆。 例如,如果 \x1b 后面跟着 3,则变为 \x1b3,由于 \x1b 和 3 之间没有明确的分隔,因此不清楚应该分别解释成 \x1b 和 3,还是放在一起解释。 如果使用 \e,则可以避免混淆。 其他 除了上述功能外,方法组中的自然类型和方法重载中的优先级也有一些改进,但在本文中省略。如果想了解更多信息,请参阅文档。 结语 C# 正在年复一年地进化,对我来说 C# 13 的更新中实现了许多非常实用且方便的功能,解决了不少实际的痛点。期待 .NET 9 和 C# 13 的正式发布~