C# 12新预览功能介绍
Visual Studio 17.7 Preview 3和.NET 8 Preview 6延续了C# 12的发展。该预览版包含的功能旨在为未来的性能增强奠定基础。对内联数组的轻松访问将允许库在更多地方使用它们,而无需您花费精力。该预览版首次推出了名为拦截器(interceptors)的实验性功能,允许生成器重新路由代码,例如提供上下文特定的优化。
可以通过安装最新的 Visual Studio 预览版或最新版本的 .NET SDK 来获取 C# 12。若要使用 C# 12 功能,需要将项目的语言版本设置为预览:
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
作为一项实验性功能,拦截器需要在项目文件中添加其他标志才可以使用。
nameof
关键字访问实例成员
nameof
关键字现在可以访问成员名称,包括初始值设定项、静态成员和属性:
internal class NameOf
{
public string S { get; } = "";
public static int StaticField;
public string NameOfLength { get; } = nameof(S.Length);
public static void NameOfExamples()
{
Console.WriteLine(nameof(S.Length));
Console.WriteLine(nameof(StaticField.MinValue));
}
[Description($"String {nameof(S.Length)}")]
public int StringLength(string s)
{ return s.Length; }
}
内联数组(Inline Arrays)
InlineArrayAttribute
是在之前的.NET 8 预览版中引入的。这是一项高级功能,主要由编译器、.NET 库和其他一些库使用。该属性标识一个类型,该类型可被视为高效、类型安全、超限安全的可索引/可切片内联数据的连续基元序列。.NET 库使用内联数组提高应用程序和工具的性能。
编译器创建不同的 IL 来访问内联数组。这会导致一些限制,例如不支持列表模式。在大多数情况下,您访问内联数组的方式与其他数组相同。不同的 IL 无需更改代码即可提高性能:
private static void InlineArrayAccess(Buffer10<int> inlineArray)
{
for (int i = 0; i < 10; i++)
{
inlineArray[i] = i * i;
}
foreach (int i in inlineArray)
{
Console.WriteLine(i);
}
}
大多数人会使用内联数组,而不是创建内联数组。但是,了解事情是如何工作的是件好事。内联数组之所以快速,是因为它们依赖于指定长度的精确布局。内联数组是具有单个字段的类型,并标记有InlineArrayAttribute
,该属性指定了数组的长度。在前面示例中使用的类型中,由于属性参数的存在,运行时在 Buffer10<T>
中创建了正好 10 个元素的存储空间:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
}
拦截器(Interceptors)
本次发布的预览版引入了一项叫做interceptors(拦截器)的新功能。这项新功能主要用于一些高级场景,尤其是将会带来更好的AOT编译能力。作为.NET 8的实验性功能,在未来的版本中有可能被修改甚至删除,因此,它不应该在生产环境中使用。
拦截器是一种方法,该方法可以在编译时以声明方式将对可拦截方法的调用替换为对其自身的调用。 通过让拦截器声明所拦截调用的源位置,可以进行这种替换。 此过程可以向编译中(例如在源生成器中)添加新代码,从而提供更改现有代码语义的有限能力。
在源生成器中使用拦截器修改现有编译的代码,而非向其中添加代码。 源生成器将对可拦截方法的调用替换为对拦截器方法的调用。
由于拦截器是一项实验性功能,因此需要在项目文件中显式启用它们:
<PropertyGroup>
<Features>InterceptorsPreview<Features>
</PropertyGroup>
拦截器支持令人兴奋的代码模式。
- 在编译时已知的调用,如带有常量模式的
Regex.IsMatch(@"a+b+")
,可以被拦截,以使用静态生成的代码进行优化,这对AOT是友好的。 - ASP.NET最小API调用,如
app.MapGet("/products", handler: (int? page, int? pageLength, MyDb db) => { ... })
可以被拦截以注册一个静态生成的thunk,该thunk直接调用用户的处理程序,跳过分配和间接寻址。 - 在矢量化中,foreach循环包含对用户方法的调用,编译器可以重写代码,在运行时检查并使用相关的内在函数,但如果这些内在函数不可用,则返回到原始代码。
- 静态依赖关系图解析用于依赖注入,其中
provider.Register<MyService>()
可被拦截。 - 可以拦截对查询提供者的调用,以便在编译时将其翻译为另一种语言(例如SQL),而不是在运行时评估表达式树以进行翻译。
- 序列化器可在编译时根据具体类型的调用(如
Serialize<MyType>()
)生成特定类型的(反)序列化。
大多数程序员不会直接使用拦截器,但我们希望拦截器将在我们的开发过程中发挥重要作用,使您的应用程序运行更快,更易于部署。在C# 12/.NET 8版本中,拦截器预计仍将是试验性的,并可能包含在未来的C#版本中。