维国 發表於 2024-8-20 10:31:00

.NET 9 优化,抢先体验 C# 13 新特性

<h2 data-tool="mdnice编辑器"><strong>前言</strong></h2>
<p>微软即将在 2024年11月12日发布 .NET 9 的最终版本,而08月09日发布的.NET 9 Preview 7 是最终发布前的最后一个预览版。这个版本将与.NET Conf 2024一同亮相,并已与 Visual Studio 2022 17.12 预览版1一同发布,可以直接通过Visual Studio安装。同时Visual Studio Code 和 C# Dev Kit 扩展也支持.NET 9。</p>
<p>C# 13 作为 .NET 9 的一部分,将带来一系列新特性,提升开发灵活性和性能,让编程体验更加流畅。尽管C# 13 尚未正式发布,但我们可以在 .NET 9 Preview 7 中尝试这些新特性,需要下载最新的 Visual Studio 2022 17.11 预览版。</p>
<p><strong>注意:</strong>目前 C# 13 尚未正式发布,因此功能细节可能会有所调整。</p>
<p><img src="https://img2024.cnblogs.com/blog/576536/202408/576536-20240819214135941-1976242139.png" width="700" loading="lazy" style="display: block; margin-left: auto; margin-right: auto"></p>
<h2>新特性</h2>
<p class="md-end-block md-p"><span class="md-pair-s md-expand"><strong>1、params 集合增强,以提高灵活性</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">在 C# 13 中,params关键字的使用已经扩展到不仅仅是数组,还可以应用于任何可识别的集合类型,包括System.Span<span class="md-tag md-raw-inline">&lt;T&gt;<span class="md-plain">、System.ReadOnlySpan<span class="md-tag md-raw-inline">&lt;T&gt;<span class="md-plain">和实现了System.Collections.Generic.IEnumerable<span class="md-tag md-raw-inline">&lt;T&gt;<span class="md-plain">的类型。</span></span></span></span></span></span></span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong>2、锁对象</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">.NET 9 运行时引入了System.Threading.Lock类型,提供了改进的线程同步机制。Lock类型通过其 API 支持更高效的线程同步操作,例如Lock.EnterScope()方法可以进入一个独占作用域</span></p>
<p class="md-end-block md-p"><span class="md-pair-s "><strong>3、索引器改进</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">索引器的使用变得更加直观和灵活,能够更高效地操作集合。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong>4、转义序列\e</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">使用 \e 的好处是它可以避免与十六进制转义序列混淆。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s "><strong>5、部分属性</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">部分属性的引入使得属性的定义和实现可以分布在不同的文件中,提高了代码的组织性和可维护性。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s "><strong>6、方法组自然类型改进</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">方法组的自然类型得到了改进,使得调用变得更简单,减少了不必要的转换。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong>7、ref 和 unsafe 在 async 方法和迭代器中的使用</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">现在 async 方法和迭代器可以使用ref变量和不安全代码,可以在更多情况下使用这些特性,尽管仍然有一些限制。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s "><strong>8、关于扩展类型(Extension Types)的更新</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">C# 13 中一个非常重大的特性,它允许向现有类添加新的方法、属性、甚至静态成员,而无需修改原始类代码。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong>9、LINQ 新方法</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">新增了CountBy和AggregateBy方法,允许按键聚合状态而无需通过GroupBy分配中间分组,这为数据聚合提供了更灵活的方式</span></p>
<p class="md-end-block md-p"><span class="md-pair-s "><strong>10、Foreach 支持 Index</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">引入了Index<span class="md-tag md-raw-inline">&lt;TSource&gt;<span class="md-plain">(IEnumerable<span class="md-tag md-raw-inline">&lt;TSource&gt;<span class="md-plain">),使得在 foreach 循环中可以快速提取可枚举项的索引</span></span></span></span></span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong>11、序列化改进</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">System.Text.Json在 .NET 9 中进行了改进,提供了新的选项用于 JSON 序列化,并引入了 JsonSerializerOptions.Web 单例,简化了使用 Web 默认值进行序列化的过程。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s "><strong>12、性能改进</strong></span></p>
<p class="md-end-block md-p"><span class="md-plain">.NET 9 在异常处理、环路性能、动态 PGO(按配置文件优化)、RyuJIT 编译器以及 Arm64 指令集支持方面进行了优化,显著提升了应用程序的性能。</span></p>
<h3 class="md-end-block md-heading"><span class="md-plain">Params 集合</span></h3>
<p class="md-end-block md-p"><span class="md-plain">params关键字允许方法接受一个参数列表,这个列表可以是任何实现了IEnumerable<span class="md-tag md-raw-inline">&lt;T&gt;<span class="md-plain">接口的集合类型。</span></span></span></p>
<p class="md-end-block md-p"><span class="md-plain">意味着可以使用方法参数来传递数组、列表、元组等集合,而不必显式地创建集合实例。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">以下是一个使用 params关键字的简单示例:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Collections.Generic;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Linq;

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Program
{
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这个方法可以接受任意数量的字符串参数</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> PrintNames(<span style="color: rgba(0, 0, 255, 1)">params</span> <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] names)
    {
      Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Names provided:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> name <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> names)
      {
            Console.WriteLine(name);
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Main()
    {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 直接传递字符串参数</span>
      PrintNames(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Alice</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Bob</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Charlie</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用数组</span>
      <span style="color: rgba(0, 0, 255, 1)">string</span>[] namesArray = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">string</span>[] { <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Dave</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Eve</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Frank</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> };
      PrintNames(namesArray);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用列表</span>
      List&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>&gt; namesList = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>&gt; { <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Grace</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Heidi</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Ivan</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> };
      PrintNames(namesList);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用 LINQ 表达式</span>
      <span style="color: rgba(0, 0, 255, 1)">var</span> query = <span style="color: rgba(0, 0, 255, 1)">from</span> person <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;Person&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 0, 255, 1)">new</span> Person(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Judy</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Walker</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">),
            </span><span style="color: rgba(0, 0, 255, 1)">new</span> Person(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Kevin</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Smith</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
      }
      </span><span style="color: rgba(0, 0, 255, 1)">select</span><span style="color: rgba(0, 0, 0, 1)"> person.FirstName;
      PrintNames(query);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用从集合中选择的属性</span>
      <span style="color: rgba(0, 0, 255, 1)">var</span> persons = <span style="color: rgba(0, 0, 255, 1)">new</span> List&lt;Person&gt;<span style="color: rgba(0, 0, 0, 1)">
      {
            </span><span style="color: rgba(0, 0, 255, 1)">new</span> Person(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Leonard</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Nimoy</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">),
            </span><span style="color: rgba(0, 0, 255, 1)">new</span> Person(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Morgan</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Freeman</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
      };
      PrintNames(</span><span style="color: rgba(0, 0, 255, 1)">from</span> p <span style="color: rgba(0, 0, 255, 1)">in</span> persons <span style="color: rgba(0, 0, 255, 1)">select</span><span style="color: rgba(0, 0, 0, 1)"> p.FirstName);
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Person
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> FirstName { <span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">; }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> LastName { <span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">; }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> Person(<span style="color: rgba(0, 0, 255, 1)">string</span> firstName, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> lastName)
    {
      FirstName </span>=<span style="color: rgba(0, 0, 0, 1)"> firstName;
      LastName </span>=<span style="color: rgba(0, 0, 0, 1)"> lastName;
    }
}</span></pre>
</div>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">在这个示例中,PrintNames方法使用params关键字来接受任意数量的字符串参数。可以使用多种方式调用这个方法:</span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">直接传递字符串字面量。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">传递一个字符串数组。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">传递一个字符串列表。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">使用 LINQ 查询来传递查询结果。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">使用 LINQ 从Person对象的集合中选择FirstName属性。</span></p>
</li>
</ul>
<p class="md-end-block md-p"><span class="md-plain">这个示例展示了params集合的灵活性,允许以多种不同的集合类型传递参数,而方法内部的实现保持不变。</span></p>
<h3>锁对象</h3>
<p>众所周知,lock 是一种功能,通过监视器用于线程同步。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">object</span> lockObject = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">lock</span><span style="color: rgba(0, 0, 0, 1)"> (lockObject)
{
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关键区</span>
}</pre>
</div>
<p>但是,这个功能的开销其实很大,会影响性能。为了解决这个问题,C# 13 实现了锁对象。要使用此功能,只需用 System.Threading.Lock 替换被锁定的对象即可:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Threading;

Lock lockObject </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Lock();
</span><span style="color: rgba(0, 0, 255, 1)">lock</span><span style="color: rgba(0, 0, 0, 1)"> (lockObject)
{
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关键区</span>
}</pre>
</div>
<p>这样就可以轻松提高性能了。</p>
<h3><strong>索引器改进</strong></h3>
<p class="md-end-block md-p md-focus"><span class="md-plain">对索引器的改进,其中包括在对象初始化器中使用”尾部索引"(也称为“从末尾开始的索引”)的能力。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain">这种索引方式允许从集合的末尾开始计数,使用 ^ 符号来指定元素的位置。</span></p>
<p class="md-end-block md-p"><span class="md-plain md-expand">以下是 C# 13 中索引器改进的示例:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Demo
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Main()
    {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义一个可索引的类型</span>
      <span style="color: rgba(0, 0, 255, 1)">var</span> data = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> IndexedData
      {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用传统的索引器初始化</span>
            Items = { [<span style="color: rgba(128, 0, 128, 1)">2</span>] = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Second</span><span style="color: rgba(128, 0, 0, 1)">"</span>, [<span style="color: rgba(128, 0, 128, 1)">3</span>] = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Third</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> },

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用尾部索引初始化</span>
            [^<span style="color: rgba(128, 0, 128, 1)">1</span>] = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">First</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从末尾开始的第一个元素</span>
            [^<span style="color: rgba(128, 0, 128, 1)">2</span>] = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fourth</span><span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 从末尾开始的第二个元素</span>
<span style="color: rgba(0, 0, 0, 1)">      };

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打印初始化后的数据</span>
      <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; data.Items.Length; i++<span style="color: rgba(0, 0, 0, 1)">)
      {
            Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Index {i}: {data.Items}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      }
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> IndexedData
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>[] Items { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">string</span>[<span style="color: rgba(128, 0, 128, 1)">5</span><span style="color: rgba(0, 0, 0, 1)">];
}</span></pre>
</div>
<p class="md-end-block md-p md-focus"><span class="md-plain">在这个示例中,IndexedData 类有一个名为 Items 的字符串数组属性。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain"><span class="md-softbreak"> <span class="md-plain">在初始化 data 对象时,我们使用了两种索引方式:</span></span></span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">传统的索引器,通过指定索引位置(例如 和 )来初始化数组元素。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">尾部索引器,使用 ^ 符号后跟数字来指定从数组末尾开始的位置(例如 <span class="md-footnote" data-ref="1"><span class="md-text">1<span class="md-plain"> 和 <span class="md-footnote" data-ref="2"><span class="md-text">2<span class="md-plain">)。</span></span></span></span></span></span></span></p>
</li>
</ul>
<p class="md-end-block md-p"><span class="md-plain md-expand">当运行Main方法时,它将打印出数组中每个元素的索引和值,包括使用尾部索引初始化的元素。</span></p>
<p class="md-end-block md-p"><span class="md-plain md-expand"><span class="md-softbreak"><span class="md-plain">输出结果将是:</span></span></span></p>
<div class="cnblogs_code">
<pre>Index <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">:
Index </span><span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">:
Index </span><span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">: Second
Index </span><span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">: Third
Index </span><span style="color: rgba(128, 0, 128, 1)">4</span>: First</pre>
</div>
<p class="md-end-block md-p md-focus"><span class="md-plain">请注意,尾部索引 <span class="md-footnote" data-ref="1"><span class="md-text">1<span class="md-plain"> 被分配给了数组的最后一个位置(索引4),而 <span class="md-footnote" data-ref="2"><span class="md-text">2<span class="md-plain">被分配给了倒数第二个位置(索引3),这是因为它们是从末尾开始计数的。这种特性在初始化数组或集合时特别有用,尤其是当你需要在已知末尾元素的情况下进行初始化时。<span class="md-softbreak"> <span class="md-plain">转义序列 \e在 Unicode 字符串中,可以使用\e 来代表 ESCAPE 字符,它等同于传统的\u001b 或\x1b。</span></span></span></span></span></span></span></span></span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">\u001b 是一个 Unicode 转义序列,其中 \u 后跟的四位十六进制数代表一个 Unicode 点。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">\x1b 是一个十六进制转义序列,\x 后面跟的两位十六进制数代表一个 ASCII 字符。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">\e 直接表示 ESCAPE 字符,它避免了可能的混淆。</span></p>
</li>
</ul>
<p class="md-end-block md-p"><span class="md-plain md-expand">推荐使用 \e 是因为它提供了一种清晰无歧义的方式来表示 ESCAPE 字符。例如,\x1b 后如果紧跟数字可能会造成混淆,如 \x1b3 可能被误解为单一的转义序列。使用 \e 就可以清楚地表达 ESCAPE 字符,避免了这种混淆。</span></p>
<h3><strong>部分属性</strong></h3>
<p class="md-end-block md-p"><span class="md-plain">在 C# 13 之前,属性不支持使用partial修饰符,这意味着属性的声明和实现必须在同一个位置完成。这在自动生成代码或分离关注点时可能会带来限制。</span></p>
<p class="md-end-block md-p"><span class="md-plain">C# 13 改进了这一点,允许属性跨越多个部分进行声明和实现。特性特别适用于与源代码生成器等工具结合使用的场景,可以更灵活地生成和管理属性代码。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">以下是 C# 13 中属性支持partial的示例:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DemoModel
{
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">声明部分属性</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">int</span> MyProperty { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DemoModel
{
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 部分属性的实现</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">partial</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> MyProperty
    {
      </span><span style="color: rgba(0, 0, 255, 1)">get</span> { <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> GetValue(); }
      </span><span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)"> { SetValue(value);   }
    }
}</span></pre>
</div>
<p>这种方式可以专注于属性的业务逻辑部分,而将具体的实现细节留给自动化工具处理,从而提高开发效率并减少重复性编码工作。</p>
<h3>方法组自然类型</h3>
<p>方法组的自然类型改进允许编译器更精确地确定方法的自然类型,特别是在重载解析时。这意味着编译器可以更有效地识别应该使用哪个重载版本,尤其是在涉及委托和方法组的情况下。</p>
<p>以下是一个示例,展示了 C# 13 中方法组自然类型的改进:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Program
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Main()
    {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 声明一个委托类型,它指向一个接受 Action 作为参数的方法</span>
      Action&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>&gt; action =<span style="color: rgba(0, 0, 0, 1)"> PrintMessage;

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 调用 PrintMessage 方法,使用方法组作为参数</span>
      action(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello, World!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    }

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这是原始的重载版本</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> PrintMessage(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> message)
    {
      Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Original: {message}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    }
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> C# 13 允许更精确的自然类型推断</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> PrintMessage(Action&lt;<span style="color: rgba(0, 0, 255, 1)">string</span>&gt; messagePrinter, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> message)
    {
      messagePrinter(message);
      Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Improved natural type inference in C# 13.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    }
}</span></pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">在这个示例中,PrintMessage方法有两个重载。第一个重载接受一个string参数,而第二个重载接受一个Action<span class="md-tag md-raw-inline">&lt;string&gt;<span class="md-plain">和一个string参数。</span></span></span></p>
<p class="md-end-block md-p"><span class="md-plain">在 C# 13 之前,如果尝试使用方法组调用action委托,编译器可能会在重载解析时产生模糊性,因为它需要确定使用哪个重载。</span></p>
<p class="md-end-block md-p"><span class="md-plain">C# 13 中的方法组自然类型改进允许编译器更准确地推断出应该使用第一个 PrintMessage 重载,因为它更匹配传递的参数类型(一个字符串)。第二个重载虽然也能接受字符串,但它期望的是一个Action<span class="md-tag md-raw-inline">&lt;string&gt;<span class="md-plain">类型的参数,这在方法组调用中是不匹配的。</span></span></span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain">请注意,这个示例仅用于说明 C# 13 中方法组自然类型改进的概念。在实际代码中,可能需要根据具体情况调整方法签名和调用方式。</span></p>
<h3><span class="md-pair-s md-expand">ref 和 unsafe 在 async 方法和迭代器中的使用</span></h3>
<p class="md-end-block md-p"><span class="md-plain">在 C# 13 之前,ref 和 unsafe 关键字在异步方法(使用 async和 await 修饰的方法)和迭代器中有一些限制。</span></p>
<p class="md-end-block md-p"><span class="md-plain md-expand">然而,C# 13 放宽了这些限制,可以在这些上下文中使用 ref 和 unsafe。</span></p>
<p class="md-end-block md-p"><span class="md-plain">以下是一些示例,展示在 C# 13 中如何在异步方法和迭代器中使用 ref 和 unsafe:</span></p>
<p class="md-end-block md-p md-focus"><span class="md-pair-s"><strong>1、在异步方法中使用ref</strong></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">async</span><span style="color: rgba(0, 0, 0, 1)"> Task RefInAsyncMethod()
{
    </span><span style="color: rgba(0, 0, 255, 1)">int</span> value = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> Task.Yield();
    </span><span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> local = <span style="color: rgba(0, 0, 255, 1)">ref</span> ModifyValue(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> value);
    local</span>++; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 修改原始变量的值</span>
    Console.WriteLine(value); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 输出修改后的值</span>
<span style="color: rgba(0, 0, 0, 1)">}

</span><span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> ModifyValue(<span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> x)
{
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> x;
}</span></pre>
</div>
<p class="md-end-block md-p md-focus"><span class="md-plain">在这个示例中,ModifyValue方法返回对传入引用的引用。在异步方法RefInAsyncMethod中,我们使用await Task.Yield();来切换到另一个上下文,然后通过ref返回的引用来修改原始变量的值。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong>2、在迭代器中使用ref</strong></span></p>
<div class="cnblogs_code">
<pre>IEnumerable&lt;<span style="color: rgba(0, 0, 255, 1)">int</span>&gt;<span style="color: rgba(0, 0, 0, 1)"> GetNumbers()
{
    </span><span style="color: rgba(0, 0, 255, 1)">int</span> number = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">yield</span> <span style="color: rgba(0, 0, 255, 1)">return</span> number; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回第一个值</span>
    number++;            <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 修改状态</span>
    <span style="color: rgba(0, 0, 255, 1)">yield</span> <span style="color: rgba(0, 0, 255, 1)">return</span> number; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 返回修改后的值</span>
<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用迭代器</span>
<span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">ref</span> <span style="color: rgba(0, 0, 255, 1)">int</span> num <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> GetNumbers())
{
    Console.WriteLine(num);
}</span></pre>
</div>
<p>&nbsp;</p>
<p class="md-end-block md-p md-focus"><span class="md-plain">在这个示例中,迭代器GetNumbers使用yield return来返回序列中的值。</span></p>
<p class="md-end-block md-p"><span class="md-plain">在两次yield调用之间,迭代器的状态(number 变量)被保持,允许在第二次迭代时返回修改后的值。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s"><strong>3、在异步方法中使用unsafe</strong></span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">async</span><span style="color: rgba(0, 0, 0, 1)"> Task UnsafeInAsyncMethod()
{
    </span><span style="color: rgba(0, 0, 255, 1)">unsafe</span><span style="color: rgba(0, 0, 0, 1)">
    {
      </span><span style="color: rgba(0, 0, 255, 1)">int</span>* p = <span style="color: rgba(0, 0, 255, 1)">stackalloc</span> <span style="color: rgba(0, 0, 255, 1)">int</span>[<span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">];
      </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; <span style="color: rgba(128, 0, 128, 1)">10</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
      {
            p </span>=<span style="color: rgba(0, 0, 0, 1)"> i;
      }

      </span><span style="color: rgba(0, 0, 255, 1)">await</span> Task.Yield(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 切换上下文

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 继续使用 p</span>
      <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; <span style="color: rgba(128, 0, 128, 1)">10</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
      {
            Console.WriteLine(p);
      }
    }
}</span></pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">在这个示例中,unsafe上下文被用在异步方法UnsafeInAsyncMethod中。我们使用stackalloc在栈上分配内存,并在await之前和之后访问这个内存。</span></p>
<p class="md-end-block md-p"><span class="md-plain">这展示了即使在异步方法中,也可以执行不安全操作。</span></p>
<p class="md-end-block md-p"><span class="md-pair-s "><strong>4、注意事项</strong></span></p>
<ul class="ul-list" data-mark="-">
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">在异步方法中使用 ref和 unsafe需要谨慎,因为await会导致方法的执行上下文被挂起和恢复,这可能会影响对 ref 局部变量和 unsafe 代码的预期行为。</span></p>
</li>
<li class="md-list-item">
<p class="md-end-block md-p"><span class="md-plain">确保在使用 ref 和 unsafe代码时,遵守 C# 的安全和并发规则。</span></p>
</li>
</ul>
<p class="md-end-block md-p md-focus"><span class="md-plain">C# 13 的这些改进提供了更大的灵活性,可以在异步编程和迭代器中使用ref和unsafe代码,但同时也需要更多的注意来确保代码的正确性和安全性。</span></p>
<h2>总结</h2>
<p>C# 13 带来的新特性和改进,如扩展类型的灵活性、params 关键字的增强、在异步方法中使用ref 和unsafe的能力,以及对序列化性能的优化等,都极大地提升了我们开发效率,解决了很多实际开发中遇到的问题。</p>
<p>对 .NET 9 和 C# 13 的正式发布充满期待,相信将为社区带来更加强大和便捷的工具,进一步推动技术的更新和发展。下载最新的 Visual Studio 2022-17.11 预览版,可以亲自体验这些新特性。</p>
<h2><strong>下载地址</strong></h2>
<p><strong>下载.NET 9.0</strong></p>
<p><strong>Visual Studio 2022 预览版</strong></p>
<h2>参考链接</h2>
<p>《C# 13: Explore the latest preview features》</p>
<p>《提高 C# 的生产力:C# 13 更新完全指南》</p>
<h2>最后</h2>
<p>如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号<strong></strong>&nbsp;社区,与其他热爱技术的同行一起交流心得,共同成长!</p>
<p><img src="https://img2024.cnblogs.com/blog/576536/202408/576536-20240814113403514-910171896.png" alt="" style="display: block; margin-left: auto; margin-right: auto"></p><br><br>
来源:https://www.cnblogs.com/1312mn/p/18367817
頁: [1]
查看完整版本: .NET 9 优化,抢先体验 C# 13 新特性