自从咱们启动疾速倒退的 .NET 开源和跨平台我的项目以来,.NET 产生了很大变动。咱们从新思考并欠缺了该平台,增加了专为性能和安全性而设计的新低级性能,以及以生产力为核心的高级性能。Span<T>、硬件外在函数和可为空的援用类型都是示例。咱们正在启动一个新的“.NET 设计要点”系列文章,以摸索定义当今 .NET 平台的基础知识和设计抉择,以及它们如何使您当初编写的代码受害。
本系列的第一篇文章全面概述了平台的支柱和设计要点。当您抉择 .NET 时,它在根底级别上形容了“您失去了什么”,旨在成为一个充沛且以事实为核心的框架,您能够应用它来向其他人形容该平台。后续帖子将更具体地介绍这些雷同的主题,因为这篇帖子并没有齐全公正地介绍这些性能中的任何一个。这篇文章不形容工具,如 Visual Studio,也不涵盖更高级的库和应用程序模型,如 ASP.NET Core 提供的那些。
咱们所说的“.NET”是古代的 .NET Core。咱们在 GitHub 上作为开源我的项目于 2014 年启动了这个我的项目。它在 Arm64、x64 和其余芯片架构上的 Linux、macOS 和 Windows 上运行。它在一堆 Linux 发行版中可用。它与 .NET Framework 放弃了很大的兼容性,但又是一个全新的方向和产品。
.NET 设计要点
.NET 平台代表生产力、性能、安全性和可靠性。.NET 在这些价值之间获得的均衡使其具备吸引力。
.NET 的设计要点能够归结为在平安域(所有都高效)和不平安域(存在大量性能)中都无效和高效。.NET 可能是具备最多内置性能的托管环境,同时还提供最低的与内部世界互操作的老本,并且两者之间没有衡量。事实上,许多性能都利用了这种无缝划分,在底层操作系统和 CPU 的原始能力和性能上构建平安的托管 API。
咱们能够进一步扩大设计点:
- 生产力 是跨运行时、库、语言和工具的首要设计思考因素。
- 平安代码 是次要的计算模型,而不平安代码反对额定的手动优化。
- 反对动态和动静代码,反对宽泛的不同场景。
- 本机代码互操作和硬件外在函数 成本低且保真度高(原始 API 和指令拜访)。
- 代码可跨平台(操作系统、芯片架构)移植,而平台定位则反对专业化和优化。
- 通过通用编程模型的专门实现,能够实现 跨编程域(云、客户端、游戏)的适应性。
- OpenTelemetry 和 gRPC 等 行业标准 优于定制解决方案。
.NET 堆栈的支柱
运行时、库和语言是 .NET 堆栈的支柱。更高级别的组件,如 .NET 工具和应用程序堆栈,如 ASP.NET Core,构建在这些支柱之上。这些支柱具备共生关系,由一个团队(Microsoft 员工和开源社区)独特设计和构建,致力于这些组件的多个方面并为其提供信息。
C# 是面向对象的,运行时反对面向对象。C# 须要垃圾收集,运行时提供跟踪垃圾收集器。事实上,将 C#(以其残缺模式)移植到没有垃圾收集的零碎是不可能的。这些库(以及应用程序堆栈)将这些性能塑造成概念和对象模型,使开发人员可能在直观的工作流程中高效地编写算法。
C# 是一种古代的、平安的、通用的编程语言,涵盖了从面向数据的记录等高级性能到函数指针等低级性能。它提供动态类型以及类型和内存平安作为基准性能,同时进步开发人员的工作效率和代码安全性。C# 编译器也是可扩大的,反对插件模型,使开发人员可能通过额定的诊断和编译时代码生成来加强零碎。
许多 C# 性能曾经影响或受最先进的编程语言的影响。例如,C# 是第一个引入 async and await. 同时,C# 借鉴了其余编程语言中首先引入的概念,例如采纳模式匹配和主构造函数等函数式办法。
外围库公开了数千种类型,其中许多类型与 C# 语言集成并为其提供能源。例如,C# 的 foreach 反对枚举任意汇合,基于模式的优化使 List<T> 等汇合可能被简略高效地解决。资源管理可能留给垃圾收集,但能够通过 IDisposable 和 using 中的间接语言反对进行疾速清理。
C# 中的字符串插值既富裕表现力又高效,与 string、StringBuilder 和 Span<T> 等跨外围库类型的实现集成并受其反对。语言集成查问 (LINQ)性能由库中的数百个序列解决例程提供反对,例如 Where、Select 和 GroupBy,具备反对内存中和近程数据源的可扩大设计和实现。从压缩到密码学再到正则表达式,列表还在持续,间接集成到语言中的内容只是作为外围 .NET 库的一部分公开的性能的外表。一个全面的从套接字到 HTTP/3 的网络堆栈是一个独立的畛域。同样,库反对解决有数格局和语言,如 JSON、XML 和 tar。
.NET 运行时最后称为“公共语言运行时 (CLR)”。它持续反对多种语言,一些由 Microsoft 保护(例如 C#、F#、Visual Basic、C++/CLI 和 PowerShell),一些由其余组织保护(例如 Cobol、Java、PHP、Python、Scheme)。许多改良与语言无关,这会引发所有改善。
接下来,咱们将看看它们一起提供的各种平台个性。咱们能够别离具体阐明这些组件中的每一个,但您很快就会看到它们在交付 .NET 设计点方面进行单干。让咱们从类型零碎开始。
类型零碎
.NET 类型零碎提供了显著的广度,大抵等同地满足了安全性、描述性、动态性和本机互操作性。
首先,类型零碎反对面向对象的编程模型。它包含类型、(单个基类)继承、接口(包含默认办法实现)和虚构办法分派,为面向对象容许的所有类型分层提供正当的行为。
泛型是一种广泛的个性,它容许将类专门化为一种或多种类型。例如,List<T> 是一个凋谢的通用类,而像 List<string> 和 List<int> 这样的实例化防止了对独自的 ListOfString 和 ListOfInt 类的须要,或者像 ArrayList 那样依赖 object 和强制转换。泛型还能够跨不同类型创立有用的零碎(并缩小对大量代码的需要),例如 Generic Math。
Delegates 和 lambdas 容许将办法作为数据传递,这使得将内部代码集成到另一个零碎领有的操作流中变得容易。它们是一种“胶水代码”,它们的签名通常是通用的,能够宽泛应用。
app.MapGet("/Product/{id}", async (int id) =>
{if (await IsProductIdValid(id))
{return await GetProductDetails(id);
}
return Products.InvalidProduct;
});
这种对 lambdas 的应用是 ASP.NET Core Minimal APIs 的一部分。它能够间接向路由零碎提供端点实现。在更新的版本中,ASP.NET Core 更宽泛地应用了类型零碎。
与 .NET 的 GC 治理类型相比,值类型和堆栈调配的内存块提供了对数据和本机平台互操作的更间接、低级别的管制。.NET 中的大多数原始类型,如整数类型,都是值类型,用户能够定义本人具备类似语义的类型。
.NET 的泛型零碎齐全反对值类型,这意味着像 List<T> 这样的泛型类型能够提供值类型汇合的平坦、无开销的内存示意。此外,.NET 泛型在替换值类型时提供专门的编译代码,这意味着这些泛型代码门路能够防止低廉的 GC 开销。
byte magicSequence = 0b1000_0001;
Span<byte> data = stackalloc byte[128];
DuplicateSequence(data[0..4], magicSequence);
此代码生成堆栈调配的值。Span<byte> 是 byte* 的平安和更丰盛的版本,提供长度值(带边界查看)和不便的跨度切片。
Ref 类型和变量是一种小型编程模型,它提供对类型零碎数据的较低级别和更轻量级形象。这包含 Span<T>。此编程模型不是通用的,包含保护平安的重要限度。
internal readonly ref T _reference;
这种应用 ref 导致将指针复制到底层存储,而不是复制该指针援用的数据。默认状况下,值类型是“按值复制”。ref 提供“按援用复制”行为,能够提供显著的性能劣势。
主动内存治理
.NET 运行时通过垃圾收集器 (GC) 提供主动内存治理。对于任何语言,其内存治理模型可能是其最具决定性的特色。.NET 语言也是如此。
工程师破费数周甚至数月的工夫来追踪这些问题的状况并不少见。许多语言应用垃圾收集器作为打消这些谬误的用户敌对形式,因为 GC 确保正确的对象生命周期。通常,GC 会分批开释内存以高效运行。这会导致暂停,如果您对提早要求十分严格,这可能不适宜,并且内存使用率会更高。GC 往往具备更好的内存局部性,并且某些 GC 可能压缩堆,使其不易产生内存碎片。
.NET 具备自我调整、跟踪 GC。它旨在个别状况下提供“撒手”操作,同时为更极其的工作负载提供配置选项。GC 是多年投资、改良和从多种工作负载中学习的后果。
▌Bump 指针调配
通过指针递增所需的大小调配对象(而不是在拆散的闲暇块中寻找空间),因而一起调配的对象往往会在一起。因为用户常常一起拜访不同对象,这样做能够实现更好的内存局部性 memory locality,这有利于保障性能。
▌分代收集
对象生命周期遵循分代假如 generational hypothesis 是十分常见的,对象生存周期要么很长,要么很短。因而,对于 GC 来说,如果大部分运行时只收集长期对象占用的内存(称为长期 GC),而不是每次运行时都必须收集整个堆(称为残缺 GC),那么效率就要高得多。
▌压缩
雷同数量的可用空间在面积大而数量少的块中比在面积小和数量多的块中更有用。在压缩 GC 期间,依然存在的对象会被挪动到一起,由此能够造成更大的自由空间。这种行为须要比非挪动 GC 更简单的实现,因为它须要更新对这些挪动对象的援用。.NET GC 被动静调整为仅在确定回收的内存高于 GC 老本时才执行压缩。这意味着长期汇合通常会被压缩。
▌并行
GC 工作能够在单个线程或多个线程上运行。Workstation flavor 在单个线程上进行 GC,而 Server flavor 在多个 GC 线程上进行,这样能够更快完结作业。服务器 GC 还能够适应更大的分配率,因为有多个堆供应用程序调配,因而它对吞吐量适应性也很好。
▌并发
在用户线程暂停时进行 GC 工作称为 Stop-The-World,这样使实现需求更简略,但这些暂停可能对于 GC 来说是不可承受的。.NET 提供 concurrent flavor 来缓解该问题。
▌固定
.NET GC 反对对象固定,它能够实现与本机代码的零拷贝互操作。此性能可实现高性能和高保真度的本机互操作,同时限度 GC。
▌独立 GC
能够应用具备不同机制的独立 GC(通过配置指定并满足 interface requirements)。这样一来,考察和尝试新性能就更容易了。
▌诊断
GC 提供无关内存和汇合的大量信息,这容许您将数据与零碎的其余部分相关联。例如,您能够通过捕捉 GC 事件并将它们与其余事件(如 IO)相关联来评估 GC impact of your tail latency 尾部提早对 GC 的影响,以计算 GC 对其余因素的影响水平,这样您就能够将精力集中在正确的组件上。
平安
.NET 编程平安始终是过来十年的热门话题之一。它是 .NET 等托管环境的固有组件。
平安模式:
- Type safety 类型平安 — 不能应用任意类型代替另一个类型,防止未定义的行为。
- Memory safety 内存平安 — 不能应用任意类型代替另一个类型,防止未定义的行为。
- Concurrency or thread safety 并发或线程平安 — 不能应用任意类型代替另一个类型,防止未定义的行为。
.NET 从最后的设计开始就被设计成一个保障平安的平台。特地须要指出的是,它旨在启用新一代 Web 服务器,这些服务器始终须要在世界上简单的计算环境(Internet)中承受不受信赖的输出的考验。当初普遍认为网络程序应该用平安的语言编写。
类型平安由语言和运行时模块同时强制执行。编译器验证动态不变量,例如调配不同的类型——例如,调配 string 给 Stream——这将导致编译器中产生谬误。运行时验证动静不变量,例如不同类型之间的转换,就将产生 InvalidCastException。
内存平安次要由代码生成器(如 JIT)和垃圾收集器单干实现。变量援用值要么是流动对象,要么是 null,要么超出范围。默认状况下内存是主动初始化的,这样新对象就不会应用未初始化的内存。边界查看禁止拜访数组中有效索引的元素读取未定义的内存——通常由一个单位的谬误偏移引起——这会导致 IndexOutOfRangeException。
Cnull 解决是保障内存平安的一种非凡模式。可空援用类型 Nullable reference types 是一种 C# 语言和编译器性能,可动态标识未平安解决的代码 null。特地是,如果您勾销援用可能为 null 的变量,编译器会收回正告。您还能够禁止 null 赋值,这样编译器会在您可能给变量赋空值时收回正告。运行时具备匹配的动静验证性能,可通过抛出 NullReferenceException 来避免援用被拜访。
C# 性能依赖于库中可为空的属性 nullable attributes。它还依赖于它们在库和应用程序堆栈(咱们曾经实现)中的详尽利用,以便为您的代码提供来自动态剖析工具的精确后果。
.NET 中没有内置的并发平安。相同,开发人员须要遵循模式和约定来防止未定义的行为。.NET 生态系统中还有分析器和其余工具,能够深刻理解并发问题。外围库包含多种能够平安并发应用的类型和办法,例如反对任意数量的并发读取器和写入器而不会冒数据结构损坏危险的 concurrent collections 并发汇合。
运行时公开平安和 unsafe code 不平安的代码模型。平安代码的安全性失去保障,这是默认设置,而开发人员必须抉择应用不平安代码。不平安代码通常用于与底层平台互操作、与硬件交互或对性能要害门路施行手动优化。
沙箱 sandbox 是一种非凡的平安模式,它提供隔离并限度组件之间的拜访。咱们依赖规范的隔离技术,如过程(和 CGroups)、虚拟机和 WebAssembly(具备不同的个性)。
错误处理
异样是 .NET 中的次要错误处理模型。异样的益处是错误信息不须要在办法签名中示意或由每个办法解决。
上面的代码演示了一个典型的模式:
try
{var lines = await File.ReadAllLinesAsync(file);
Console.WriteLine($"The {file} has {lines.Length} lines.");
}
catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException)
{Console.WriteLine($"{file} doesn't exist.");
}
正确的异样解决对于应用程序的可靠性至关重要。能够在用户代码中无意解决预期的异样,否则应用程序就会解体。解体的应用程序比具备未定义行为的应用程序更牢靠。当您想找出问题的根本原因时,它也更容易诊断。
异样从谬误点抛出,并主动收集无关程序状态的附加诊断信息。这些信息可用于交互式调试、应用程序可察看性和预先调试。这些诊断办法中的每一种都依赖于拜访大量的错误信息和应用程序状态来诊断问题。
异样是为常见的状况而设计的。这在肯定水平上是因为它们的性能老本绝对较高。它们不打算用于控制流,即便它们有时以这种形式应用。
异样(有一部分)依赖于勾销。一旦察看到勾销申请,它们就能够无效地进行执行并开展正在进行的调用堆栈。
try
{await source.CopyToAsync(destination, cancellationToken);
}
catch (OperationCanceledException)
{Console.WriteLine("Operation was canceled");
}
.NET 设计模式包含代替模式的错误处理,以应答异样的性能老本过高的状况。例如,int.TryParse 返回胜利时其参数蕴含已解析的无效整数,Dictionary<TKey, TValue>.TryGetValue 提供了一个相似的模型,返回一个无效 TValue 类型作为案例中的参数等。
错误处理和更广泛的诊断是通过低级运行时 API、higher-level libraries 和 tools 实现的。这些性能旨在反对更新的部署选项,例如容器。例如,dotnet-monitor 能够通过内置的面向诊断的 Web 服务器将运行时数据从利用导出到侦听器。
并发
反对同时做多件事是简直所有工作负载的根底,无论是在放弃 UI 响应的同时进行后盾解决的客户端应用程序、解决成千上万同时申请的服务、响应大量同时刺激的设施,还是高驱动的机器并行处理计算密集型操作。操作系统通过线程为这种并发性提供反对,这使得多个指令流可能独立解决,操作系统治理这些线程在机器中任何可用处理器内核上的执行。操作系统还提供对执行 I/O 的反对,提供的机制使 I/O 可能以可扩大的形式执行,并且在任何特定工夫都有许多“运行中”的 I/O 操作。
.NET 通过库和深度集成到 C# 中,在多个形象级别提供此类并发和并行化反对。线程 Thread 类位于层次结构的底部,代表一个操作系统线程,使开发人员可能创立新线程并随后退出它们。线程池 ThreadPool 位于线程之上,容许开发人员思考异步安顿在线程池上运行的工作项,并这些线程的治理(包含从池中增加和删除线程,以及为这些线程调配工作项)放在运行时。Task 而后为任何异步执行的操作提供对立的示意模式,并且能够通过多种形式创立和连贯;例如,Task.Run 容许在 ThreadPool 上运行安顿委托并返回 Task 以示意该工作的最终实现,同时 Socket.ReceiveAsync 返回一个 Task<int>(或 ValueTask<int>)示意异步 I/O 的最终实现,提供了大量的同步原语,用于协调线程和异步操作之间的同步和异步流动,并提供了大量高级 API 以简化常见并发模式的实现,例如,SocketParallel.ForEach 和 Parallel.ForEachAsync 使解决一个线程的所有元素变得更容易实现数据序列并行。
异步编程反对也是 C# 编程语言的一流性能,它提供了 async 和 await 关键字,使编写和组合异步操作变得容易,同时依然享受该语言必须提供的所有控制流构造的全副益处。
反射
反射是一种“程序即数据”范例,它能让程序的一部分依据程序集、类型和成员动静查问和 / 或调用另一部分。它对于前期绑定编程模型和工具特地有用。
以下代码应用反射来查找和调用 type。
foreach (Type type in typeof(Program).Assembly.DefinedTypes)
{if (type.IsAssignableTo(typeof(IStory)) &&
!type.IsInterface)
{IStory? story = (IStory?)Activator.CreateInstance(type);
if (story is not null)
{var text = story.TellMeAStory();
Console.WriteLine(text);
}
}
}
interface IStory
{string TellMeAStory();
}
class BedTimeStore : IStory
{public string TellMeAStory() => "Once upon a time, there was an orphan learning magic ...";
}
class HorrorStory : IStory
{public string TellMeAStory() => "On a dark and stormy night, I heard a strange voice in the cellar ...";
}
此代码动静枚举实现特定接口的所有程序集类型,实例化每个类型的实例,并通过该接口调用对象的办法。代码原本能够动态编写的,因为它只查问它所援用的程序集中的类型,但要这样做,须要将所有实例的汇合(兴许是作为一个 List<IStory>)交给它来解决。如果此算法从加载我的项目录加载任意程序集,则更有可能应用这种前期绑定办法。有这样一种状况:您无奈提前获取程序集和类型,反射通常就被用在这样的场景中。
反射可能是 .NET 中提供的最动静的零碎。它旨在使开发人员可能创立本人的二进制代码加载器和办法分派器,其语义能够与动态代码策略(由运行时定义)相匹配或有所区别。反射公开了一个丰盛的对象模型,它能够间接用于简略的用例,但随着场景变得更加简单,您就须要更深刻地理解 .NET 类型零碎。
反射还启用了一种独自的模式,其中生成的 IL 字节代码能够在运行时进行 JIT 编译,有时用于以专用算法替换通用算法。有了对象模型和其余细节,它通常会被用于序列化器或对象关系映射器中。
编译后的二进制格局
应用程序和库被编译为 PE/COFF 格局的标准化跨平台字节码。二进制散发最重要的是性能特色。它使应用程序可能扩大到越来越多的我的项目。每个库都蕴含一个导入和导出类型的数据库,称为元数据,它对开发操作和运行应用程序都起着重要作用。
编译的二进制文件包含两个次要方面:
- 二进制字节码——简洁而规定的格局,无需在高级语言编译器(如 C#)编译后解析文本源。
- 元数据——形容导入和导出的类型,包含给定办法的字节代码的地位。
例如,对于开发,工具能够无效地读取元数据以确定给定库公开的类型集以及哪些类型实现了某些接口。此过程可放慢编译速度,并使 IDE 和其余工具可能精确出现给定上下文的类型和成员列表。
对于运行时,元数据使库可能提早加载,办法体更是如此。上文探讨过的反射是元数据和 IL 的运行时 API。还有其余更适宜工具的 API。
随着工夫的推移,IL 格局始终放弃向后兼容。最新的 .NET 版本依然能够加载和执行由 .NET Framework 1.0 编译器生成的二进制文件。
共享库通常通过 NuGet 包散发。默认状况下,带有单个二进制文件的 NuGet 包能够在任何操作系统和体系结构上运行,但也能够专门用于在特定环境中提供特定行为。
代码生成
.NET 字节码不是机器可执行的格局,它须要通过某种模式的代码生成器使其可执行。这能够通过提前 (AOT) 编译、即时 (JIT) 编译、解释或转译来实现。事实上,这些都是明天在各种场景中应用的。
.NET 以 JIT 编译而闻名。JIT 在利用程序运行时将办法(和其余成员)编译为本机代码,并且仅在须要时才将其编译,因而得名“及时(just in time,缩写为 JIT)”。例如,一个程序在运行时可能只调用一种类型中几种办法中的一种。JIT 还能够利用仅在运行时可用的信息,如初始化的只读动态变量的值或程序运行的确切 CPU 模型,并且能够屡次编译雷同的办法,以便每次针对不同的指标进行优化,并从以前的编译中吸取教训。
JIT 为给定的操作系统和芯片架构生成代码。.NET 具备反对 Arm64 和 x64 指令集以及 Linux、macOS 和 Windows 操作系统等的 JIT 实现。作为 .NET 开发人员,您不用放心 CPU 指令集和操作系统调用约定之间的差别。JIT 负责生成 CPU 须要的代码。它还晓得如何为每个 CPU 生成疾速代码,操作系统和 CPU 供应商常常帮忙咱们做到这一点。
AOT 相似,只是代码是在程序运行之前生成的。开发人员抉择 AOT 是因为它能够通过打消 JIT 实现的工作来显著缩短启动工夫。AOT 构建的应用程序实质上是特定于操作系统和体系结构的,这意味着须要额定的步骤能力使应用程序在多个环境中运行。例如,如果您想反对 Linux 和 Windows 以及 Arm64 和 x64,那么您须要构建四个变体(以反对所有组合)。AOT 代码也能够提供有价值的优化,但总体不如 JIT 多。
代码生成器优化之一是外在函数。硬件外在函数就是 .NET API 间接转换为 CPU 指令的例子。这已在整个 .NET 库中广泛用于 SIMD 指令。
互操作
.NET 被特意设计用于与本机库的低成本互操作。.NET 程序和库能够无缝调用低级操作系统 API 或利用 C/C++ 库的宏大生态系统。古代 .NET 运行时专一于提供低级互操作构建块,例如通过函数指针调用本机办法的能力,将托管办法公开为非托管回调或自定义接口转换。.NET 也在这个畛域一直倒退,在 .NET 7 中公布了源代码生成的解决方案,进一步缩小了开销并且便于应用 AOT。
上面的代码演示了 C# 函数指针的效率。
// Using a function pointer avoids a delegate allocation.
// Equivalent to `void (*fptr)(int) = &Callback;` in C
delegate* unmanaged<int, void> fptr = &Callback;
RegisterCallback(fptr);
[UnmanagedCallersOnly]
static void Callback(int a) => Console.WriteLine($"Callback: {a}");
[LibraryImport("...", EntryPoint = "RegisterCallback")]
static partial void RegisterCallback(delegate* unmanaged<int, void> fptr);
此示例应用 .NET 7 中引入的 LibraryImport 源代码生成器。它位于现有 DllImport 或 P/Invoke 性能之上。
独立包通过利用这些低级构建块(例如 ClangSharp、Xamarin.iOS 和 Xamarin.Mac、CsWinRT、CsWin32 和 DNNE)提供更高级别的特定于域的互操作解决方案。
这些新性能并不意味着内置运行时托管 / 非托管编组或 Windows COM 互操作等内置互操作解决方案没有用——咱们晓得它们有用,而且人们曾经开始依赖它们。那些之前内置到运行时中的性能将持续按原样提供反对,只是为了向后兼容,咱们没有进一步倒退它们的打算。所有将来的投资都将集中在互操作构建块以及它们反对的特定畛域和更高性能的解决方案上。
二进制散布
Microsoft 的 .NET 团队保护着多个二进制发行版,最近开始反对 Android、iOS 和 WebAssembly。该团队应用多种技术为这些环境中的每一个环境定制代码库。大多数平台是用 C# 编写的,这使得移植能够集中在绝对较小的组件集上。
社区保护着另一套发行版,次要集中于 Linux。例如,.NET 已蕴含在 Alpine Linux、Fedora、Red Hat Enterprise Linux 和 Ubuntu 中。
概括
咱们有几个版本进入古代 .NET 时代,最近公布了 .NET 7。咱们认为,如果咱们总结自 .NET Core 1.0 以来咱们始终在平台的最低级别构建的内容,将会很有用。咱们明确保留了原始 .NET 的精力,后果是一个新平台开拓了一条新路线,并为开发人员提供了新的和更多的价值。
让咱们用最开始的话题完结本篇文章。.NET 代表四个值:生产力、性能、安全性和可靠性。咱们深信,当不同的语言平台提供不同的办法时,开发人员会失去最好的服务。作为一个团队,咱们寻求为 .NET 开发人员提供高生产力,同时提供在性能、安全性和可靠性方面处于领先地位的平台。
这篇文章由 Jan Kotas、Rich Lander、Maoni Stephens 和 Stephen Toub 撰写,囊括了 .NET 团队共事的粗浅见解和审阅。