首页 > 编程语言 >提高 C# 的生产力:C# 13 更新完全指南

提高 C# 的生产力:C# 13 更新完全指南

时间:2024-07-28 22:17:42浏览次数:13  
标签:指南 13 struct C# int Copy ref

提高 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 的例子:

Copy
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 的例子:

Copy
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 的边界使用。例如,以下示例将导致编译错误。

Copy
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 来获取变量的引用。这样,就可以通过引用来更改原始变量。以下是一个例子:

Copy
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 了:

Copy
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 类型的泛型参数是这样定义的:

Copy
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 结合使用,那么也可以通过泛型类型传递引用:

Copy
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 是一种功能,允许在调用方法时直接指定任意数量的参数。

例如,

Copy
Test(1, 2, 3, 4, 5, 6);
void Test(params int[] values) { }

如上所示,可以直接指定任意数量的 int 参数。

从 C# 13 开始,除了数组类型外,其他集合类型、SpanReadOnlySpan 类型以及与集合相关的接口也可以添加 params

Copy
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# 的属性时,经常需要定义一大堆字段,如下所示...

Copy
partial class ViewModel : INotifyPropertyChanged
{
    // 定义字段
    private int _myProperty;

    public int MyProperty
    {
        get => _myProperty;
        set
        {
            if (_myProperty != value)
            {
                _myProperty = value;
                OnPropertyChanged();
            }
        }
    }
}

因此,从 C# 13 开始,field 关键字将派上用场!

Copy
partial class ViewModel : INotifyPropertyChanged
{
    public int MyProperty
    {
        // 只需使用 field
        get => field;
        set
        {
            if (field != value)
            {
                field = value;
                OnPropertyChanged();
            }
        }
    }
}

不再需要自己定义字段,只需使用 field 关键字,字段就会自动生成。

这也非常方便!

部分属性#

在编写 C# 时,常见的问题之一是:属性不能添加 partial 修饰符。

在 C# 中,可以在类或方法上添加 partial,以便分别进行声明和实现。此外,还可以分散类的各个部分。它的主要用途是在使用源代码生成器等自动生成工具时,指定要生成的内容。

例如:

Copy
partial class ViewModel
{
    // 这里只声明方法,实现部分由工具自动生成
    partial void OnPropertyChanged(string propertyName);
}

然后自动生成工具会生成以下代码:

Copy
partial class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    partial void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new(propertyName));
    }
}

开发者只需要声明 OnPropertyChanged,其实现将全部由自动生成,从而节省了开发者的时间。

从 C# 13 开始,属性也支持 partial

Copy
partial class ViewModel
{
    // 声明部分属性
    public partial int MyProperty { get; set; }
}

partial class ViewModel
{
    // 部分属性的实现
    public partial int MyProperty
    {
        get
        {
            // ...
        }
        set
        {
            // ...
        }
    }
}

这样,属性也可以由工具自动生成了。

锁对象#

众所周知,lock 是一种功能,通过监视器用于线程同步。

Copy
object lockObject = new object();
lock (lockObject)
{
    // 关键区
}

但是,这个功能的开销其实很大,会影响性能。

为了解决这个问题,C# 13 实现了锁对象。要使用此功能,只需用 System.Threading.Lock 替换被锁定的对象即可:

Copy
using System.Threading;

Lock lockObject = new Lock();
lock (lockObject)
{
    // 关键区
}

这样就可以轻松提高性能了。

初始化器中的尾部索引#

索引运算符 ^ 可用于表示集合末尾的相对位置。从 C# 13 开始,初始化器也支持此功能:

Copy
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 的正式发布~

标签:指南,13,struct,C#,int,Copy,ref
From: https://www.cnblogs.com/sexintercourse/p/18328953

相关文章

  • win平台利用winsw将php-cgi作为系统服务,支持服务的正常启动/停止/重启
    首先,需要有winsw,在GitHub搜索winsw,点击release跳转到下载页面选择版本进行下载或点击此链接 Releases·winsw/winsw(github.com)其次,将winsw复制到php目录,重命名为phpcgi-service.exe并增加配置文件phpcgi-service.xml和stop-cgi.bat,其中 phpcgi-service.xml的......
  • C++ 笔记(一)数据类型(1)
    1简单的变量变量名命名规则如下变量名称可以包含字母、数字和下划线(_)。变量名称的第一个字符必须是字母或下划线。区分大小写,即大写字母和小写字母被认为是不同的字符。不能使用C++的关键字作为变量名。2数据类型2.1整型short、int、long和longlong这四种类型都是......
  • 拼音模糊搜索的AutoCompleteBox
    [WPF]脱机环境实现支持拼音模糊搜索的AutoCompleteBox AutoCompleteBox是一个常见的提高输入效率的组件,很多WPF的第三方控件库都提供了这个组件,但基本都是字符串的子串匹配,不支持拼音模糊匹配,例如无法通过输入ldh或liudehua匹配到刘德华。要实现拼音模糊搜索功能,通常会采用......
  • docker启动MySQL容器演示(centos)
    环境配置vboxcentos7.9docker(已经配置阿里镜像源)首先拉取MySQL镜像dockerpullmysql:版本号版本号不打也可以,会默认下载最新版(latest)下载后使用dockerimages查看镜像如图我下载了MySQL5.7/8.0/latest版本然后执行下面的命令就可以启动(运行)容器了dockerrun-d-......
  • C语言进阶版—扫雷游戏
    文章目录1.打印棋盘2.游戏逻辑3.游戏框架3.1打印菜单3.2do……while实现主逻辑3.3创建棋盘3.4初始化棋盘3.5设置雷3.6排查雷完整游戏代码1.打印棋盘  在正式讲解扫雷游戏之前,我们简单来看一下打印出来的棋盘.  第一步我们要打印每行的框架printf("......
  • javascript(一)
    一、基本语法1.位置(1)JavaScript脚本必须位于<script>与</script>之间(2)<script>标签可以位于<body>或者<head>部分中2.输出语句(1)window.alter()弹出警告框(2)document.write()可以将内容在网页中打印出来,同时也会将原有的内容覆盖(3)console.log()可以将内容在......
  • 使用celery进行异步处理和定时任务(django)
    一、celery的作用    celery是一个简单、灵活且可靠的分布式系统,用于处理大量消息,同时为操作提供一致的接口。它专注于实时操作,但支持任务调度。Celery主要用于异步任务处理,特别是在Web应用环境中,用于执行后台任务,如发送电子邮件、处理图片、视频转码、运行复杂的......
  • CVE-2015-5254
    目录漏洞描述漏洞利用流程如下复现过程漏洞利用思路总结漏洞描述ApacheActiveMQ是由美国Pachitea(Apache)软件基金会开发的开源消息中间件,支持Java消息服务、集群、Spring框架等。影响版本:ApacheActiveMQ5.13.0之前5.x版本,该程序导致的漏洞并不限制可以在......
  • JCR一区级 | Matlab实现SSA-Transformer-LSTM多变量回归预测
    JCR一区级|Matlab实现SSA-Transformer-LSTM多变量回归预测目录JCR一区级|Matlab实现SSA-Transformer-LSTM多变量回归预测效果一览基本介绍程序设计参考资料效果一览基本介绍1.【JCR一区级】Matlab实现SSA-Transformer-LSTM多变量回归预测,麻雀搜索算法(S......
  • C++关键字——inline和auto
    目录一、前言 二、inline关键字(C++11)---多用于内联函数a.概念b.特性三、auto关键字(C++11)a.auto简介b.auto的使用细则c.auto不能推导的场景d.基于范围的for循环(C++11)一、前言C++总计63个关键字,我们先了解inline和auto这两个关键字。asmdoifreturntrycontinue......