9. 反射改进
.NET 5 中引入了函数指针,但当时未添加对反射的相应支持。 对函数指针使用 typeof 或反射时(例如分别使用 typeof (delegate*<void>()) 或 FieldInfo.FieldType),返回了 IntPtr。 从 .NET 8 开始,将改为返回 System.Type 对象。 此类型提供对函数指针元数据的访问,包括调用约定、返回类型和参数。
新功能目前仅在 CoreCLR 运行时和 MetadataLoadContext 中实现。已将新的 API 添加到 System.Type(例如 IsFunctionPointer)以及 System.Reflection.PropertyInfo、System.Reflection.FieldInfo 和 System.Reflection.ParameterInfo。 以下代码演示如何使用一些新 API 进行反射。
// Sample class that contains a function pointer field. public unsafe class UClass { public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp; } // ... FieldInfo fieldInfo = typeof(UClass).GetField(nameof(UClass._fp)); // Obtain the function pointer type from a field. Type fpType = fieldInfo.FieldType; // New methods to determine if a type is a function pointer. Console.WriteLine($"IsFunctionPointer: {fpType.IsFunctionPointer}"); Console.WriteLine($"IsUnmanagedFunctionPointer: {fpType.IsUnmanagedFunctionPointer}"); // New methods to obtain the return and parameter types. Console.WriteLine($"Return type: {fpType.GetFunctionPointerReturnType()}"); foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes()) { Console.WriteLine($"Parameter type: {parameterType}"); } // Access to custom modifiers and calling conventions requires a "modified type". Type modifiedType = fieldInfo.GetModifiedFieldType(); // A modified type forwards most members to its underlying type. Type normalType = modifiedType.UnderlyingSystemType; // New method to obtain the calling conventions. foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions()) { Console.WriteLine($"Calling convention: {callConv}"); } // New method to obtain the custom modifiers. foreach (Type modreq in modifiedType.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers()) { Console.WriteLine($"Required modifier for first parameter: {modreq}"); }
输出:
IsFunctionPointer: True IsUnmanagedFunctionPointer: True Return type: System.Void Parameter type: System.Int32& Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition Calling convention: System.Runtime.CompilerServices.CallConvCdecl Required modifier for first parameter: System.Runtime.InteropServices.InAttribute
10. 配置绑定源生成器
.NET 8 引入了一个源生成器,用于在 ASP.NET Core 中提供 AOT 和适合剪裁的配置。 该生成器是现有的基于反射的实现的替代方法。
源生成器探测 Configure(TOptions)、Bind 和 Get 调用来从中检索类型信息。 在项目中启用生成器后,编译器将隐式选择生成的方法,而非预先存在的基于反射的框架实现。
无需更改源代码即可使用生成器。 AOT Web 应用中默认启用该生成器。 对于其他项目类型,源生成器默认关闭,但你可通过在项目文件中将 EnableConfigurationBindingGenerator 属性设置为 true 来选择使用它:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
以下代码演示了调用绑定器的示例:
using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); IConfigurationSection section = builder.Configuration.GetSection("MyOptions"); // !! Configure call - to be replaced with source-gen'd implementation builder.Services.Configure<MyOptions>(section); // !! Get call - to be replaced with source-gen'd implementation MyOptions options0 = section.Get<MyOptions>(); // !! Bind call - to be replaced with source-gen'd implementation MyOptions options1 = new MyOptions(); section.Bind(options1); WebApplication app = builder.Build(); app.MapGet("/", () => "Hello World!"); app.Run(); public class MyOptions { public int A { get; set; } public string S { get; set; } public byte[] Data { get; set; } public Dictionary<string, string> Values { get; set; } public List<MyClass> Values2 { get; set; } } public class MyClass { public int SomethingElse { get; set; } }
11. 针对 Android 应用的 AOT 编译
为了减小应用大小,面向 Android 的 .NET 和 .NET MAUI 应用在发布模式下构建时使用分析的预先 (AOT) 编译模式。 与常规 AOT 编译相比,分析的 AOT 编译所影响的方法更少。 .NET 8 引入了 <AndroidStripILAfterAOT> 属性,你可使用它进一步对 Android 应用进行 AOT 编译,从而更进一步减少应用大小。
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>
默认情况下,将 AndroidStripILAfterAOT 设置为 true 会替代默认的 AndroidEnableProfiledAot 设置,从而允许剪裁已 AOT 编译的(几乎)所有方法。 还可通过将两个属性都显式设置为 true 来结合使用分析的 AOT 和 IL 条带化:
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
12. 代码分析
.NET 8 包括几个新的代码分析器和修复程序,可帮助验证是否正确且高效地使用 .NET 库 API。 下表总结了新的分析器。
|规则 ID|类别|说明|
|---|---|---|
|CA1856|性能|未在参数上正确应用 ConstantExpectedAttribute 属性时触发。|
|CA1857|性能|当参数使用 ConstantExpectedAttribute 添加批注但提供的参数不是常量时触发。|
|CA1858|性能|若要确定字符串是否以给定前缀开头,最好调用 String.StartsWith,而不是调用 String.IndexOf,然后将结果与零进行比较。|
|CA1859|性能|此规则建议尽可能将特定局部变量、字段、属性、方法参数和方法返回类型从接口或抽象类型升级到具体类型。 使用具体类型可生成更高质量的代码。|
|CA1860|性能|若要确定集合类型是否具有任何元素,最好使用 Length、Count 或 IsEmpty,而不是调用 Enumerable.Any。|
|CA1861|性能|重复调用时,不会重复使用作为参数传递的常量数组,这意味着每次都会创建一个新数组。 若要提高性能,请考虑将数组提取到静态只读字段。|
|CA1865-CA1867|性能|对于单字符串,char 重载的性能更好。|
|CA2021|可靠性|Enumerable.Cast(IEnumerable) 和 Enumerable.OfType(IEnumerable) 需要兼容的类型才能正常运行。 泛型类型不支持扩大转换和用户定义的转换。|
|CA1510-CA1513|可维护性|在构造新的异常实例方面,引发帮助程序比 if 块更简单、更高效。 这四个分析器是为以下例外情况创建的:ArgumentNullException、ArgumentException、ArgumentOutOfRangeException 和 ObjectDisposedException。|
13.Core .NET 库
13.1 时间抽象
新的 TimeProvider 类和 ITimer 接口添加了时间抽象功能,让你可以在测试方案中模拟时间。 此外,还可以使用时间抽象,通过 Task.Delay 和 Task.WaitAsync 来模拟依赖于时间进度的 Task 操作。 时间抽象支持以下基本时间操作:
- 检索本地和 UTC 时间
- 获取用于测量性能的时间戳
- 创建计时器
以下代码片段演示了一些使用情况示例。
// Get system time. DateTimeOffset utcNow = TimeProvider.System.GetUtcNow(); DateTimeOffset localNow = TimeProvider.System.GetLocalNow(); // Create a time provider that works with a // time zone that's different than the local time zone. private class ZonedTimeProvider : TimeProvider { private TimeZoneInfo _zoneInfo; public ZonedTimeProvider(TimeZoneInfo zoneInfo) : base() { _zoneInfo = zoneInfo ?? TimeZoneInfo.Local; } public override TimeZoneInfo LocalTimeZone => _zoneInfo; public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) => new ZonedTimeProvider(zoneInfo); } // Create a timer using a time provider. ITimer timer = timeProvider.CreateTimer(callBack, state, delay, Timeout.InfiniteTimeSpan); // Measure a period using the system time provider. long providerTimestamp1 = TimeProvider.System.GetTimestamp(); long providerTimestamp2 = TimeProvider.System.GetTimestamp(); var period = GetElapsedTime(providerTimestamp1, providerTimestamp2);
13.2UTF8 改进
如果要启用将类型的类似字符串的表示形式写出到目标范围,请在类型上实现新的 IUtf8SpanFormattable 接口。 此新接口与 ISpanFormattable 密切相关,但面向 UTF8 和 Span<byte>,而不是 UTF16 和 Span<char>。
IUtf8SpanFormattable 已在所有基元类型(以及其他)上实现,无论是面向 string、Span<char> 还是 Span<byte>,其共享逻辑完全一致。 它完全支持所有格式(包括新的 “B” 二进制说明符)和所有区域性。 这意味着现在可以从 Byte、Complex、Char、DateOnly、DateTime、DateTimeOffset、Decimal、Double、Guid、Half、IPAddress、IPNetwork、Int16、Int32、Int64、Int128、IntPtr、NFloat、SByte、Single、Rune、TimeOnly、TimeSpan、UInt16、UInt32、UInt64、UInt128、UIntPtr 和 Version 直接格式化为 UTF8。
新的 Utf8.TryWrite 方法向现有 MemoryExtensions.TryWrite 方法(基于 UTF16)提供基于 UTF8 的对应方法。 可以使用内插字符串语法将复杂表达式直接格式化为 UTF8 字节范围,例如:
static bool FormatHexVersion( short major, short minor, short build, short revision, Span<byte> utf8Bytes, out int bytesWritten) => Utf8.TryWrite( utf8Bytes, CultureInfo.InvariantCulture, $"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}", out bytesWritten);
13.3 加密
.NET 8 添加了对 SHA-3 哈希基元的支持。 (目前,具有 OpenSSL 1.1.1 或更高版本和 Windows 11 Build 25324 或更高版本的 Linux 支持 SHA-3。)可在其中使用 SHA-2 的 API 现在提供对 SHA-3 的补充。 对于哈希,这包括 SHA3_256、SHA3_384 和 SHA3_512;对于 HMAC,这包括 HMACSHA3_256、HMACSHA3_384 和 HMACSHA3_512;对于其中可配置算法的哈希,这包括 HashAlgorithmName.SHA3_256、HashAlgorithmName.SHA3_384 和 HashAlgorithmName.SHA3_512;对于 RSA OAEP 加密,这包括 RSAEncryptionPadding.OaepSHA3_256、RSAEncryptionPadding.OaepSHA3_384 和 RSAEncryptionPadding.OaepSHA3_512。
以下示例演示如何使用 API(包括 SHA3_256.IsSupported 属性)来确定平台是否支持 SHA-3。
// Hashing example if (SHA3_256.IsSupported) { byte[] hash = SHA3_256.HashData(dataToHash); } else { // ... } // Signing example if (SHA3_256.IsSupported) { using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256); byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256); } else { // ... }
13.4 基于流的 ZipFile 方法
.NET 8 包含 ZipFile.CreateFromDirectory 的新重载,通过它可以收集目录中包含的所有文件并压缩这些文件,然后将生成的 zip 文件存储到提供的流中。 同样,通过新的 ZipFile.ExtractToDirectory 重载,可提供包含压缩文件的流,并将其内容提取到文件系统中。 下面是新的重载:
namespace System.IO.Compression; public static partial class ZipFile { public static void CreateFromDirectory(string sourceDirectoryName, Stream destination); public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, CompressionLevel compressionLevel, bool includeBaseDirectory); public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding); public static void ExtractToDirectory(Stream source, string destinationDirectoryName) { } public static void ExtractToDirectory(Stream source, string destinationDirectoryName, bool overwriteFiles) { } public static void ExtractToDirectory(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { } public static void ExtractToDirectory(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { } }
参考资料:
https://learn.microsoft.com/zh-cn/dotnet/core/whats-new/dotnet-8#networking