.NET 7 预览版 6。.NET 7 的这个预览版包括对类型转换器的改进、JSON 合同自定义、System.Formats.Tar API 更新、对 .NET 模板创作的约束以及 CodeGen 领域的性能增强。
.NET 7 Preview 6 已与 Visual Studio 17.3 Preview 3 一起测试。如果您想在 Visual Studio 系列产品中试用 .NET 7,我们建议您使用预览频道版本。如果您使用的是 macOS,我们建议使用最新的Visual Studio 2022 for Mac 预览版。现在,让我们了解一下此版本中的一些最新更新。
类型转换器
现在有用于新添加的原始类型DateOnly、TimeOnly、Int128、UInt128和的公开类型转换器Half。
namespace System.ComponentModel
{
public class DateOnlyConverter : System.ComponentModel.TypeConverter
{
public DateOnlyConverter() { }
}
public class TimeOnlyConverter : System.ComponentModel.TypeConverter
{
public TimeOnlyConverter() { }
}
public class Int128Converter : System.ComponentModel.BaseNumberConverter
{
public Int128Converter() { }
}
public class UInt128Converter : System.ComponentModel.BaseNumberConverter
{
public UInt128Converter() { }
}
public class HalfConverter : System.ComponentModel.BaseNumberConverter
{
public HalfConverter() { }
}
}
使用示例
TypeConverter dateOnlyConverter = TypeDescriptor.GetConverter(typeof(DateOnly));
// produce DateOnly value of DateOnly(1940, 10, 9)
DateOnly? date = dateOnlyConverter.ConvertFromString("1940-10-09") as DateOnly?;
TypeConverter timeOnlyConverter = TypeDescriptor.GetConverter(typeof(TimeOnly));
// produce TimeOnly value of TimeOnly(20, 30, 50)
TimeOnly? time = timeOnlyConverter.ConvertFromString("20:30:50") as TimeOnly?;
TypeConverter halfConverter = TypeDescriptor.GetConverter(typeof(Half));
// produce Half value of -1.2
Half? half = halfConverter.ConvertFromString(((Half)(-1.2)).ToString()) as Half?;
TypeConverter Int128Converter = TypeDescriptor.GetConverter(typeof(Int128));
// produce Int128 value of Int128.MaxValue which equal 170141183460469231731687303715884105727
Int128? int128 = Int128Converter.ConvertFromString("170141183460469231731687303715884105727") as Int128?;
TypeConverter UInt128Converter = TypeDescriptor.GetConverter(typeof(UInt128));
// produce UInt128 value of UInt128.MaxValue which equal 340282366920938463463374607431768211455
UInt128? uint128 = UInt128Converter.ConvertFromString("340282366920938463463374607431768211455") as UInt128?;
JSON合约定制
在某些情况下,序列化或反序列化 JSON 的开发人员发现他们不想或不能更改类型,因为它们要么来自外部库,要么会严重污染代码,但需要进行一些影响序列化的更改,例如删除属性、更改数字的方式获得序列化,如何创建对象等。他们经常被迫编写包装器或自定义转换器,这不仅麻烦而且使序列化速度变慢。
JSON 合约自定义允许用户更好地控制类型序列化或反序列化的内容和方式。
选择定制
开发人员可以通过两种基本方式“插入”自定义,它们最终都分配并需要分配解析器:JsonSerializerOptions.TypeInfoResolver
开发者可以使用DefaultJsonTypeInfoResolver和添加他们的修饰符,所有的修饰符都会被串行调用:
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
{
Modifiers =
{
(JsonTypeInfo jsonTypeInfo) =>
{
// your modifications here, i.e.:
if (jsonTypeInfo.Type == typeof(int))
{
jsonTypeInfo.NumberHandling = JsonNumberHandling.AllowReadingFromString;
}
}
}
}
};
Point point = JsonSerializer.Deserialize<Point>(@"{""X"":""12"",""Y"":""3""}", options);
Console.WriteLine($"({point.X},{point.Y})"); // (12,3)
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
通过实现编写自己的自定义解析器。System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver
当未处理类型时,代码应返回null。
IJsonTypeInfoResolver可以与其他人组合成有效的解析器,它将返回第一个非空答案。例如.JsonTypeInfoResolver.Combine(new MyResolver(), new DefaultJsonTypeInfoResolver())
定制
IJsonTypeInfoResolver工作是JsonTypeInfo为任何Type序列化器请求提供 - 每个选项每个类型只会发生一次。 将确定开发人员可以更改哪些旋钮,并根据转换器确定,该转换器基于提供给选项的转换器确定。例如,可以添加/修改手段,而意味着不能保证使用任何旋钮 - 当类型具有自定义转换器时可能会发生这种情况。JsonTypeInfo.KindJsonTypeInfoKind.ObjectPropertiesJsonTypeInfoKind.None
JsonTypeInfo要么由DefaultJsonTypeInfoResolver来自 ie 自定义属性的预填充旋钮创建,要么可以由用户从头开始创建: - 从头开始意味着用户还需要设置.JsonTypeInfo.CreateJsonTypeInfoJsonTypeInfo.CreateObject
自定义属性
属性仅在将被预填充时才相关。它们可以通过使用和添加到属性列表来修改或创建,即假设您从单独的库中获得了一个类,该类具有您无法更改的奇怪设计的 API:JsonTypeInfo.Kind == JsonTypeInfoKind.ObjectDefaultJsonTypeInfoResolverJsonTypeInfo.CreateJsonPropertyInfo
class MyClass
{
private string _name = string.Empty;
public string LastName { get; set; }
public string GetName() => _name;
public void SetName(string name)
{
_name = name;
}
}
在此功能存在之前,您需要包装类型层次结构或为该类型创建自己的自定义转换器。现在您可以简单地修复它:
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
{
Modifiers = { ModifyTypeInfo }
}
};
MyClass obj = new()
{
LastName = "Doe"
};
obj.SetName("John");
string serialized = JsonSerializer.Serialize(obj, options); // {"LastName":"Doe","Name":"John"}
static void ModifyTypeInfo(JsonTypeInfo ti)
{
if (ti.Type != typeof(MyClass))
return;
JsonPropertyInfo property = ti.CreateJsonPropertyInfo(typeof(string), "Name");
property.Get = (obj) =>
{
MyClass myClass = (MyClass)obj;
return myClass.GetName();
};
property.Set = (obj, val) =>
{
MyClass myClass = (MyClass)obj;
string value = (string)val;
myClass.SetName(value);
};
ti.Properties.Add(property);
}
属性的条件序列化
在某些使用场景中,要求某些默认值不被序列化。即,您不想0在 JSON 中显示某些属性。通过将JsonIgnoreAttribute与. 当您的默认值不是并且它有所不同时,就会出现问题,即,或者它取决于某些外部设置。JsonIgnoreCondition.WhenWritingDefault0-1
现在可以ShouldSerialize使用您想要的任何条件设置您自己的谓词。即说你string有财产,你不想出现在 JSON 中:N/A
// string property you'd like to customize
JsonPropertyInfo property = ...;
property.ShouldSerialize = (obj, val) =>
{
// in this specific example we don't use parent but it's available if needed
MyClass parentObj = (MyClass)obj;
string value = (string)val;
return value != "N/A";
};
示例:忽略具有特定名称或类型的属性
var modifier = new IgnorePropertiesWithNameOrType();
modifier.IgnorePropertyWithType(typeof(SecretHolder));
modifier.IgnorePropertyWithName("IrrelevantDetail");
JsonSerializerOptions options = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
{
Modifiers = { modifier.ModifyTypeInfo }
}
};
ExampleClass obj = new()
{
Name = "Test",
Secret = new SecretHolder() { Value = "MySecret" },
IrrelevantDetail = 15,
};
string output = JsonSerializer.Serialize(obj, options); // {"Name":"Test"}
class ExampleClass
{
public string Name { get; set; }
public SecretHolder Secret { get; set; }
public int IrrelevantDetail { get; set; }
}
class SecretHolder
{
public string Value { get; set; }
}
class IgnorePropertiesWithNameOrType
{
private List<Type> _ignoredTypes = new List<Type>();
private List<string> _ignoredNames = new List<string>();
public void IgnorePropertyWithType(Type type)
{
_ignoredTypes.Add(type);
}
public void IgnorePropertyWithName(string name)
{
_ignoredNames.Add(name);
}
public void ModifyTypeInfo(JsonTypeInfo ti)
{
JsonPropertyInfo[] props = ti.Properties.Where((pi) => !_ignoredTypes.Contains(pi.PropertyType) && !_ignoredNames.Contains(pi.Name)).ToArray();
ti.Properties.Clear();
foreach (var pi in props)
{
ti.Properties.Add(pi);
}
}
}
System.Formats.Tar API 更新
在 Preview 4 中,引入了该程序集。它提供了用于操作 TAR 档案的 API。System.Formats.Tar
在 Preview 6 中,进行了一些更改以涵盖一些特殊情况:
全局扩展属性专用类
最初的设计假设只有 PAX TAR 档案可以在第一个位置包含单个全局扩展属性 (GEA) 条目,但发现 TAR 档案可以包含多个 GEA 条目,这会影响所有后续条目,直到遇到新的 GEA条目或存档的结尾。
还发现 GEA 条目不应仅出现在仅包含 PAX 条目的档案中:它们可以出现在混合不同格式条目的档案中。因此添加了一个新类来描述 GEA 条目:
+ public sealed partial class PaxGlobalExtendedAttributesTarEntry : PosixTarEntry
+ {
+ public PaxGlobalExtendedAttributesTarEntry(IEnumerable<KeyValuePair<string, string>> globalExtendedAttributes) { }
+ public IReadOnlyDictionary<string, string> GlobalExtendedAttributes { get { throw null; } }
+ }
条目格式,而非存档格式
由于还发现不同格式的条目可以混合在单个 TAR 存档中,因此将TarFormat枚举重命名为TarEntryFormat:
-public enum TarFormat
+public enum TarEntryFormat
{
...
}
并添加了一个新属性TarEntry以公开条目的格式:
public abstract partial class TarEntry
{
...
+ public TarEntryFormat Format { get { throw null; } }
...
}
写作和阅读的变化
该Format属性已被删除,TarReader因为不希望存档具有单一格式的所有条目。
由于 GEA 条目现在使用它们自己的专用类进行描述,并且可以在单个存档中找到多个此类条目,因此TarReader也删除了来自 的 dictionary 属性:
public sealed partial class TarReader : IDisposable
{
...
- public TarFormat Format { get { throw null; } }
- public IReadOnlyDictionary<string, string>? GlobalExtendedAttributes { get { throw null; } }
...
}
专门的 GEA 类的添加也影响了TarWriter:
删除了用于获取单个首位 GEA 条目的字典的构造函数。
添加了一个只接受流和leaveOpen布尔值的新构造函数。
采用 的构造函数TarFormat被保留,但枚举被重命名,并且默认值设置为Pax. 该方法的文档已更改为解释指定的格式参数仅适用于从文件添加条目的方法。TarWriter.WriteEntry
public sealed partial class TarWriter : IDisposable
{
...
- public TarWriter(Stream archiveStream, IEnumerable<KeyValuePair<string, string>>? globalExtendedAttributes = null, bool leaveOpen = false) { }
+ public TarWriter(Stream archiveStream, bool leaveOpen = false) { }
- public TarWriter(Stream archiveStream, TarFormat archiveFormat, bool leaveOpen = false) { }
+ public TarWriter(Stream archiveStream, TarEntryFormat format = TarEntryFormat.Pax, bool leaveOpen = false) { }
public void WriteEntry(string fileName, string? entryName) { }
...
}
模板创作
约束
预览版 6向 .NET 模板引入了约束的概念。约束允许您定义允许模板的上下文——这可以帮助模板引擎确定它应该在命令中显示哪些模板,例如. 对于此版本,我们添加了对三种约束的支持:dotnet new list
操作系统——根据用户的操作系统限制模板
模板引擎主机——它根据哪个主机执行模板引擎来限制模板——这通常是 .NET CLI 本身,或者像 Visual Studio/Visual Studio for Mac 中的新项目对话框这样的嵌入式场景。
已安装的工作负载 – 要求在模板可用之前安装指定的 .NET SDK 工作负载
在所有情况下,描述这些约束就像constraints在模板的配置文件中添加一个新部分一样简单:
"constraints": {
"web-assembly": {
"type": "workload",
"args": "wasm-tools"
},
}
这些模板可以命名,我们将在通知用户为什么他们不能调用您的模板时使用该名称。
目前,.NET CLI 支持这些约束,我们正在与 Visual Studio 团队中的合作伙伴合作,将它们整合到您已经知道的项目和项目创建体验中。
我们希望此功能将为 SDK 用户带来更一致的体验,无论他们选择何种编辑器,更容易引导用户了解必要的模板先决条件,并帮助我们整理常见场景的模板列表,例如. 在 .NET 7 的未来预览版中,我们计划添加对基于通用 MSBuild 属性的约束的支持!dotnet new list
有关更多示例,请参阅约束文档,有关新类型约束的讨论,请加入模板引擎存储库中的讨论。
多选参数
预览版 6 还为choice参数添加了一项新功能——用户可以在单个选择中指定多个值。这可以像使用Flags-style 枚举一样使用。此类参数的常见示例可能是:
web在模板上选择多种形式的身份验证
在maui模板中一次选择多个目标平台(ios、android、web)
选择加入此行为就像在模板配置中添加参数定义一样简单。完成后,您将可以访问许多用于模板内容的辅助函数,以帮助检测用户选择的特定值。“allowMultipleValues”: true
有关该功能的完整说明,请参阅多选参数文档。
退出代码统一和报告
Preview 6 还统一了模板引擎报告的退出代码。这应该有助于在他们选择的 shell 中依赖脚本的用户获得更一致的错误处理体验。此外,.NET CLI 报告的错误现在包含一个链接,可用于查找有关每个退出代码的详细信息:
➜ dotnet new unknown-template
No templates found matching: ‘unknown-template’.
To list installed templates, run:
dotnet new list
To search for the templates on NuGet.org, run:
dotnet new search unknown-template
For details on the exit code, refer to https://aka.ms/templating-exit-codes#103
代码生成
社区 PR(非常感谢 JIT 社区贡献者!)
@am11 在https://github.com/dotnet/runtime/pull/68111中为 {M}IBC 添加了词汇表。
@aromaa 贡献了https://github.com/dotnet/runtime/pull/70655。在某些情况下,内联可以揭示一个固定的本地是指一个堆栈位置;此更改会检测到这种情况并删除 pin,因为它是不必要的并且会阻止一些优化。
@huoyaoyuan 删除了三元变通方法,以便在https://github.com/dotnet/runtime/pull/63095中内联 string.IsNullOrEmpty 时删除冗余代码。
@singleaccretion 在预览版 6 期间做出了 41 项 PR 贡献(https://github.com/dotnet/runtime/pulls?q=is%3Apr+is%3Aclosed+label%3Aarea-CodeGen-coreclr+closed%3A2022-05-24。 .2022-06-22+author%3Asingleaccretion)。很多工作是简化 JIT IR 并改进 JIT 表示结构值的方式,特别是在调用和调用参数方面。— 在 Windows x64 ( https://github.com/dotnet/runtime/pull/70777 ) 和 Windows x86 ( https://github.com/dotnet/runtime/pull/70779 )上启用 TYP_STRUCT LCL_VAR/LCL_FLD 调用参数显示很多当结构作为参数传递时,在许多情况下提高了代码质量 — 从 LCL_FLD 和 VNF_PtrToArrElem 中删除字段序列https://github.com/dotnet/runtime/pull/68986这简化了 JIT 的 IR,并极大地提高了代码质量和吞吐量。对于某些结构副本的情况,这极大地改进了代码生成。
@skiFoD 进行了优化以删除https://github.com/dotnet/runtime/pull/70222中不必要的范围检查(例如,对于字节 x,x <= 255)并将“~x + 1”转换为“- x”在https://github.com/dotnet/runtime/pull/69600中。
@Wraith2 改进了 JIT 以删除 https://github.com/dotnet/runtime/pull/69041 中许多不必要的无条件跳转,并在https://中为模式 XOR(x, x – 1) 添加了 blsmsk xarch 指令github.com/dotnet/runtime/pull/66561。
动态 PGO
https://github.com/dotnet/runtime/pull/68703增加了对委托调用的保护去虚拟化的支持。启用动态 PGO 后,当 JIT 确定这可能有利可图时,它允许 JIT 专门化和内联委托调用。这可以大大提高性能,如下面的微基准所示,动态 PGO 现在比没有 PGO 快大约 5 倍(之前大约是 2.5 倍)。
目前只支持绑定到实例方法的委托。我们预计对静态方法的支持将出现在 .NET 8 的早期预览版中。
public class Benchmark
{
private readonly long[] _nums;
public Benchmark()
{
_nums = Enumerable.Range(0, 100000).Select(i => (long)i).ToArray();
}
[Benchmark]
public long Sum() => _nums.Sum(l => l * l);
}
方法 工作 工具链 意思是 错误 标准差 比率
和 作业-QWNDLL \nopgo\corerun.exe 406.65 我们 0.718 我们 0.560 我们 1.00
和 工作-PNPEDU \tieredpgo_no_delegate_gdv\corerun.exe 172.77 我们 0.819 我们 0.766 我们 0.42
和 工作-KFFWQK \tieredpgo_delegate_gdv\corerun.exe 91.38 我们 0.263 我们 0.219 我们 0.22
我们开始实现冷热分离,https://github.com/dotnet/runtime/pull/69763是它的第一部分。
ARM64 上的热/冷分离已在 JIT ( PR ) 中实现。这项工作主要包括为热/冷部分之间的分支生成长伪指令,以及从数据部分加载常量。
我们还添加了对带有异常处理 ( PR ) 的函数的热/冷拆分的支持。在没有 PGO 数据的情况下,我们的启发式算法将所有异常处理函数移动到冷段,并将“finally”块复制到热段;我们是在异常很少发生的假设下运行的,但是无论是否存在异常,都会执行 finally 块。
在运行各种 SuperPMI 集合时,JIT 将约 14% 的低端函数(无 PGO 数据)和约 26% 的高端函数(有 PGO 数据)拆分。在此处查看更多指标。
Arm64
https://github.com/dotnet/runtime/pull/70600在 Windows Arm64 中启用了 LSE 原子。它将锁相关的操作性能提高了 78%。
图片
https://github.com/dotnet/runtime/pull/70749为 Arm64 上的 gc 类型启用了寻址模式,以获得高达 45% 的性能。
https://github.com/dotnet/runtime/pull/71044对齐 16 字节 SIMD16 的 arm64 数据部分。
https://github.com/dotnet/runtime/pull/70599优化 i % 2 并提供高达 17% 的吞吐量提升。
循环优化
由类型测试驱动的循环克隆:https 😕/github.com/dotnet/runtime/pull/70377启用基于循环不变类型测试的循环克隆,例如 GDV 添加的那些。这有效地允许快速路径循环将类型检查提升到循环之外,从而提高性能。例如:
图片
开始从https://github.com/dotnet/runtime/pull/68061中的多级嵌套循环中提升不变量。
图片
一般优化
PR https://github.com/dotnet/runtime/pull/68874改进了 JIT 中向量常量的处理,包括对值编号、常量传播和其他常量已经可用的其他优化的支持。
贡献者聚光灯:Pent Ploompuu
Pent 早在成为 Microsoft 的 Microsoft Teams 员工之前就为 .NET 做出了贡献。无论是清理 CacheEntry还是修复整数格式的回归,我们都要感谢 Pent 多年来对 .NET 的贡献。谢谢彭特!
图片 pentploompuu jpg
用彭特自己的话来说:
我在 7 岁时学会了编程,并觉得它很有趣。从简单的基于文本的玩具应用程序开始,现在致力于全球规模的大型后端系统。
我在 2000 年底 beta 1 发布时开始使用 .NET Framework,此后我创建了无数基于它的应用程序。我发现它是结合了优秀语言的最全面和最高质量的平台。
有一次,我对电能计量和计费应用程序中的 System.Decimal 性能感到沮丧,因此我开始为 .NET Core 做出贡献以改进这一点。多年来,我从阅读和贡献代码库中学到了很多东西,贡献工作流程很棒(与大多数较小/内部的 repos 相比)。
请关注Proposed .NET Breaking Changes GitHub 问题。