提高 C# 的生产力: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];
}
转义字符
在 Unicode 字符串中,可以使用 \e
代替 \u001b
和 \x1b
。\u001b
、\x1b
和 \e
都表示转义字符。它们通常用于表示控制字符。
\u001b
表示 Unicode 转义序列,\u
后面的 4 位十六进制数表示 Unicode 代码点\x1b
表示十六进制转义序列,\x
后面的 2 位十六进制数表示 ASCII 代码\e
表示转义字符本身
推荐使用 \e
的原因是,可以避免在十六进制中的混淆。
例如,如果 \x1b
后面跟着 3
,则变为 \x1b3
,由于 \x1b
和 3
之间没有明确的分隔,因此不清楚应该分别解释成 \x1b
和 3
,还是放在一起解释。
如果使用 \e
,则可以避免混淆。
其他
除了上述功能外,方法组中的自然类型和方法重载中的优先级也有一些改进,但在本文中省略。如果想了解更多信息,请参阅文档。
结语
C# 正在年复一年地进化,对我来说 C# 13 的更新中实现了许多非常实用且方便的功能,解决了不少实际的痛点。期待 .NET 9 和 C# 13 的正式发布~
提高 C# 的生产力:C# 13 更新完全指南的更多相关文章
- 解决IntelliJ IDEA 13更新FindBugs 0.9.993时JRE版本过低导致启动失败问题
今晚更新FindBugs 0.9.992(FindBugs 2)至FindBugs 0.9.993(FindBugs 3)后,按要求重启IntelliJ IDEA 13.本想看看更新后多了哪些功能,结 ...
- ABP入门教程13 - 更新菜单
点这里进入ABP入门教程目录 菜单更新 在展示层(即JD.CRS.Web.Mvc)的Startup下打开CRSNavigationProvider.cs //用以存放菜单相关信息 修改如下 using ...
- [JZOJ100043] 【NOIP2017提高A组模拟7.13】第K小数
Description 有两个正整数数列,元素个数分别为N和M.从两个数列中分别任取一个数相乘,这样一共可以得到N*M个数,询问这N*M个数中第K小数是多少. Input 输入文件包含三行. 第一行为 ...
- 【NOIP2016提高A组集训第13场11.11】最大匹配
题目 mhy12345学习了二分图匹配,二分图是一种特殊的图,其中的点可以分到两个集合中,使得相同的集合中的点两两没有连边. 图的"匹配"是指这个图的一个边集,里面的边两两不存在公 ...
- JZOJ100045 【NOIP2017提高A组模拟7.13】好数
题目 题目大意 首先有一个定义: 对于一个数,如果和它互质的数可以组成一个等差数列,那么这个数叫"好数". 现在给你一个数列,有三种操作: 1.询问一段区间内的好数的个数. 2.将 ...
- 【JZOJ4887】【NOIP2016提高A组集训第13场11.11】最大匹配
题目描述 mhy12345学习了二分图匹配,二分图是一种特殊的图,其中的点可以分到两个集合中,使得相同的集合中的点两两没有连边. 图的"匹配"是指这个图的一个边集,里面的边两两不存 ...
- 【JZOJ4886】【NOIP2016提高A组集训第13场11.11】字符串
题目描述 某日mhy12345在教同学们写helloworld,要求同学们用程序输出一个给定长度的字符串,然而发现有些人输出了一些"危险"的东西,所以mhy12345想知道对于任意 ...
- 个人安装GO1.13.6版本指南手册之搭建环境
因好奇而走进go语言,让你不在只闻其声,不见其形. https://golang.org/doc/install:这里是go语言的官网文档.吃不透英文,终究会被限制在有限的区域,一词词的吃透. 安装包 ...
- iOS比较常用的第三方及实例(不断更新中)
把平时看到或项目用到的一些插件进行整理,文章后面分享一些不错的实例,若你有其它的插件欢迎分享,不断的进行更新: 一:第三方插件 1:基于响应式编程思想的oc 地址:https://github.com ...
- 13个小技巧帮你征服Xcode
本文由CocoaChina翻译组成员唧唧歪歪(博客)翻译自David McGraw的博客原文:13 Xcode Tips That Will Help You Conquer Xcode当谈论到iOS ...
随机推荐
- QGIS开发笔记(三):Windows安装版二次开发环境搭建(下):将QGis融入QtDemo,添加QGis并加载tif遥感图的Demo
前言 使用QGis的目的是进行二次开发,或者说是融入我们的应用(无人车.无人船.无人机),本片描述搭建QGis二次基础开发环境,由于实在是太长了,进行了分篇: 上半部分:主要是安装好后,使用QtC ...
- OSGQt编译安装
OSGQt编译安装 效果演示 1.准备工作 最新版的osg中不附带osgQt源码,所以需要单独下载编译 在编译osgQt前需要先编译osg源码,osg编译安装看这里 编译osgQt的环境与之前编译os ...
- ReplayKit 启动录制按钮 RPSystemBroadcastPickerView 的使用
一.RPSystemBroadcastPickerView 介绍 ReplayKit12自从iOS11公布以来,提供了iOS系统级别的录屏能力(也就是录制自身App以外,手机屏幕内容)的能力,有一个非 ...
- 算法学习笔记(45): 快速沃尔什变换 FWT
遗憾的是 math 里面一直没有很好的讲这个东西--所以这次细致说说. FWT 的本质 类似于多项式卷积中,利用 ntt 变换使得卷积 \(\to\) 点乘,fwt 也是类似的应用. 定义某种位运算 ...
- 带你了解磁盘驱动程序(xv6)
磁盘驱动程序 本文来聊聊磁盘驱动程序,驱动程序是硬件的接口,操作系统通过这个接口来控制硬件工作,所以驱动程序就好比是硬件和系统之间的桥梁.这是百科上给出的解释,可能看起来还是云里雾里,我来做做注解. ...
- k8s配置文件管理
1.为什么要用configMap ConfigMap是一种用于存储应用所需配置信息的资源类型,用于保存配置数据的键值对,可以用来保存单个属性,也可以用来保存配置文件. 通过ConfigMap可以方便的 ...
- jwt 加密和解密demo
jwt 加密和解密demo JSON Web Token(JWT)是一个非常轻巧的规范.这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息.导入jar <dependency&g ...
- 判断日期是否为周六周日,BigDecimal比较大小
判断日期是否为周六周日,BigDecimal比较大小 package com.example.core.mydemo.date; import java.math.BigDecimal; import ...
- tempcode排序
package com.hsy;import com.alibaba.fastjson.JSON;import org.springframework.util.CollectionUtils;imp ...
- .NET 中使用RabbitMQ初体验
在.NET Core中使用RabbitMQ 前言 逛园子的时候看到一篇.NET 学习RabbitMq的文章(视频地址和文章地址放在文章底部了),写的不错,我也来实现一下. 我是把RabbitMQ放在服 ...