排名靠前的.NET 5.0新API

2020-09-23 02:51:33

当一个新的主要.NET版本发布候选版本时,就可以使用NDepend代码审查更改功能来浏览添加了哪些新API。

这是相当直截了当的。启动VisualNDepend.exe;单击“比较代码库的两个版本”;然后选择这两个文件夹中的所有程序集:

单击OK,一分钟后,在分析了两个BCL版本之后,您就可以查看更改和新内容了。我们准备了一个代码查询来匹配新的.NET5.0类型。

匹配了129种类型。在帖子的末尾可以看到它们与代码查询详细信息一起列出。这比往常少。例如,.NET Core 3.0引入了359个新类型。.NET 5.0本质上是一个重大举措,目的是:

当然,球队都在忙于实现这些目标。因此,他们添加的新API比往常少。然而,.NET5.0中仍然有一些新的好API。让我们来探索一下吧。

新类型和新方法会定期添加到流行的泛型集合API中。这次我们得到了一个新的IReadOnlySet<;T>;接口,它可以使代码更可靠,因为:

通常在使用集合时,客户端只需检查某些元素是否在集合中。此接口避免了客户端访问添加/删除方法。这是对接口隔离原则(ISP)的很好应用。

设置的实现可能是只读的。这样的类不必使用Add/Remove方法实现Iset<;T>;接口。否则它将抛出NotSupportdException,这将违反Liskov替换原则(LSP)。非常有趣的是,Array实现ICollect<;T>;是对LSP的重大违反。

小离题:通过哈希表(哈希集、字典…)实现集合。是开发人员必须提高性能的最强大的工具之一。能够在恒定的时间内检查集合是否包含元素(与集合的大小无关)近乎神奇。此外,通常的哈希表实现依赖于素数,这很有趣。NDepend充斥着重新考虑的算法,这些算法依赖于它们核心的一些散列集。哈希表恒定时间搜索是以更高的内存消耗为代价的,因此必须仔细调优。但大多数情况下,这都是值得的。

新类ReferenceEqualityCompeller实现IEqualityCompeller<;T>;。在比较两个对象时,它使用引用相等(ReferenceEquals(object,object))而不是值相等(equals(Object))。StackOverflow上关于这类课程的问题只收集了8K的浏览量,10年内增加了60。这暗示着这个新类不会经常有用(正如人们所怀疑的那样),但至少它填补了一个空白。

[编辑:从这篇文档看,这个接口似乎延迟到了.NET 6.0。但是,它仍然在.NET5.0RC1编译的程序集中。]。

这个新的System.Net.Connections API(目前还没有在线文档)是可组合连接建立的抽象。其目标是改进分层分离,并为建立网络连接提供标准的可扩展性模型。例如:

//服务器 Using var listener=new socket(SocketType.Stream,ProtocolType.Tcp); Listener.Bind(new IPEndPoint(IPAddress.IPv6Loopback,0)); Listener.listen(); 使用套接字连接=等待侦听器.AcceptAsync(); Using Stream connectionStream=new NetworkStream(Connection); //客户端 Using var socket=new socket(SocketType.Stream,ProtocolType.Tcp); 等待socket.ConnectAsync(新DnsEndPoint(";contoso.com";,80)); Using var stream=new NetworkStream(套接字);

//服务器 使用IConnectionFactory Factory=new SocketsConnectionFactory(SocketType.Stream,ProtocolType.Tcp); Using IConnectionListener listener=await factory.BindAsync(new IPEndPoint(IPAddress.IPv6Loopback,0)); Using IConnection connection=await listener.AcceptAsync(); Using Stream stream=connection.Stream; //客户端 使用IConnectionFactory Factory=new SocketsConnectionFactory(SocketType.Stream,ProtocolType.Tcp); Using IConnection Connection=await factory.ConnectAsync(new DnsEndPoint(";contoso.com";,80)); Using Stream stream=connection.Stream;

你可以在这里找到GitHub上的API讨论,以及这里关于API设计的长达2小时16小时的视频讨论。Krzysztof Cwalina参与其中。Krzysztof是一位.NET API设计专家,他与人合著了著名的框架设计指南:可重用.NET库的约定、习惯用法和模式。这是.NET早期最激动人心的一本书,至今仍与许多问题相关。

新类型已添加到命名空间System.Text.Json.Serialization。新类型在下面的屏幕截图中以粗体显示(带下划线的类型是已修改实现的类型):

JsonConstructorAttribute:当放置在构造函数上时,指示构造函数应用于在反序列化时创建该类型的实例。

JsonNumberHandlingAttribute:当放置在类型、属性或字段上时,指示序列化或反序列化数字时应使用哪些JsonNumberHandling设置。

新的System.Net.Http.Json为使用System.Text.Json执行自动序列化和反序列化的HttpClient和HttpContent提供扩展方法。这里有一篇Steve Gordon的帖子说明了这种新API的用法。

“我们正在转向支持WinRT API的新模型,将其作为.NET 5.0的一部分。这包括调用API(任意方向;CLR<;==>;WinRT)、两个类型系统之间的数据封送处理,以及要作为单个实例的类型的统一(即“投影类型”)。作为.NET 5.0的一部分,我们将从.NET运行时(以及任何其他相关组件)中删除现有的WinRT互操作系统。“。

GCMemoryInfo通过EgGetGCMemoryInfo()获取的结构公开了以下截图中粗体显示的新数据:

System.GCGenerationInfo:表示GCMemoryInfo中报告的GC进入和退出时世代的大小和碎片。

System.GCKind:指定垃圾收集的类型:Any、Background、临时性(Gen0或Gen1收集)或FullBlock(阻塞Gen2收集)。

微软的诺亚·福尔克(Noah Falk)在GitHub上提出了一些收集GC碎片信息的程序。使用这个新API的最有可能的用例是日志记录/监视。或者,它可以用于预测GC操作。例如,这可用于向装载机平衡器指示机器应停止旋转以请求完全GC。它还可以通过减小高速缓存的大小来避免容器硬限制。

新的System.Half是实现IEEE754的16位浮点数结构。Half的主要优点是它的值只存储在2个字节上。因此,它的主要用途是节省计算结果不需要完全精确存储的存储空间。机器学习、显卡、最新处理器、本地SIMD库都是可以从一半中获益的技术示例。更多关于Half的解释和细节可以在这篇Half简介博客文章中找到。

目前关于这个新API的在线资源很少,以下是其中的一些资源:

.NET Core和.NET 5.0支持独立部署。整个框架与应用程序捆绑在一起。这样做解决了一些版本管理的地狱问题。但这样做也将微软工程师从他们对.NET Framework的可怕的优势兼容性限制中解脱出来:.NET现在可以在没有此限制的情况下自由发展。

这种方法的缺点是应用程序大小会显着增长,因为BCL相当庞大。这就是微软投资于框架修剪的原因:丢弃程序集本身中未使用的类型和成员。对于在页面加载时在浏览器中下载框架的Blazor WebAssembly应用程序来说,这种必要性更加重要。相关帖子有:

修剪是通过一些静态分析器完成的。然而,静态分析器(罗斯林分析器,NDepend Rules…)。不要执行代码。因此,他们对运行时的实际情况了解有限。尤其是在动态解析某些类型(通常通过System.Reflect)时。

CodeAnalysis API中有新类型。当类型或方法动态解析某些代码时,需要它们中的大多数来向静态分析器指示。

Undition tionalSuppressMessageAttribute:禁止报告特定的规则冲突,允许在单个代码工件上多次取消。它与SuppressMessageAttribute的不同之处在于它没有ConditionalAttribute。它始终保存在编译的程序集中。

RequiresUnreference encedCodeAttribute:此属性可以标记构造函数和方法。它指示指定的方法需要动态访问非静态引用的代码,例如,通过System.Reflect。此属性允许工具了解从应用程序中移除未引用的代码时调用哪些方法是不安全的。

DynamicallyAccessedMembersAttribute:该属性可以标记一些成员(字段、方法…)。。它指示指定类型上的某些成员是动态访问的,例如,通过System.Reflect。

DynamicDependencyAttribute:此属性可用于通知工具依赖关系,否则该依赖关系在单纯从元数据和IL中并不明显,例如,通过反射依赖的成员。它可以标记构造函数、方法和字段。

MemberNotNullAttribute:指定用此属性标记的方法或属性,确保列出的字段和属性成员的值不为空。

MemberNotNullWhenAttribute:指定使用此属性标记的方法或属性在使用指定的返回值条件返回时,将确保列出的字段和属性成员具有非空值。

对于任何对静态分析感兴趣的人来说,这些类型都非常有趣。我们注意在NDepend中支持它们。

新类System.Threading.Tasks.Tasks.TaskCompletionSource之于System.Threading.Tasks.Task,就像System.Threading.Tasks.TaskCompletionSource<;Result>;(已存在于.NET Core3中)之于System.Threading.Tasks.Task<;Result>;:任务的生产者端解除与委托的绑定,通过Task属性提供对使用者端的访问。请参阅此堆栈溢出中有关此课程的一些讨论Q/A:应在何时使用TaskCompletionSource<;T>;?

要列出所有新类型,我们必须细化默认的新类型代码查询,因为:

某些类型已从一个程序集移动到另一个程序集。在这种情况下,NDepend会看到一个已删除的类型和一个新类型。因此,我们使用散列集olderTypNames来避免与之匹配。

某些类型定义在程序集System.Private.CoreLib中重复。我们使用newTypesNamesLookup来避免两次匹配。

//<;名称>;新的.NET 5.0类型<;/名称>; //某些类型从一个程序集移动到另一个程序集 让olderTypeNames=codeBase.OlderVersion().Application.Types.Select(t=>;t.FullName).ToHashSetEx() 让newTypes= (从应用程序中的%t开始。类型,其中 !t.IsGeneratedByCompiler //我们想要新类型,从基线开始添加,即.NET Core 3.1 &;&;t.WasAdded() //我们想要公网接口 &;&;T.IsPubliclyVisible //不要将类型从一个程序集匹配到另一个程序集! &;&;!olderTypeNames.Contains(t.FullName) 选择t) 让newTypesNamesLookup=newTypes.ToLookup(t=>;t.FullName) 从newTypes中的%t开始,其中 //避免System.Private.CoreLib中的新类型重复 NewTypesNamesLookup[t.FullName].Count()==1|| T.ParentAssembly.Name!=";System.Private.CoreLib"; 选择新的{t,t.NbILInstructions,AsmName=t.ParentAssembly.Name}