选择 .NET 的 n 个理由
自从我们启动快速发展的 .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 团队同事的深刻见解和审阅。
相关文章:
选择 .NET 的 n 个理由
自从我们启动快速发展的 .NET 开源和跨平台项目以来,.NET 发生了很大变化。我们重新思考并完善了该平台,添加了专为性能和安全性而设计的新低级功能,以及以生产力为中心的高级功能。Span<T>、硬件内在函数和可为空的引用类型都是示例。…...

spark第三章:工程化代码
系列文章目录 spark第一章:环境安装 spark第二章:sparkcore实例 spark第三章:工程化代码 文章目录系列文章目录前言一、三层架构二、拆分WordCount1.三层拆分2.代码抽取总结前言 我们上一次博客,完成了一些案例的练习࿰…...
Vue实战【封装一个简单的列表组件,实现增删改查】
文章目录🌟前言🌟table组件封装🌟父组件(展示表格的页面)🌟控制台查看父子组件通信是否成功🌟Vue2父子组件传递参数🌟写在最后🌟JSON包里写函数,关注博主不迷…...

微前端(无界)
前言:微前端已经是一个非常成熟的领域了,但开发者不管采用哪个现有方案,在适配成本、样式隔离、运行性能、页面白屏、子应用通信、子应用保活、多应用激活、vite 框架支持、应用共享等用户核心诉求都或存在问题,或无法提供支持。本…...
强烈推荐:0基础入门网安必备《网络安全知识图谱》
蚁景网安学院一直专注于网安实战技能培养,提供全方位的网安安全学习解决方案。我们集聚专业网安技术大佬资源,倾力打造了这本更全面更系统的“网络安全知识图谱”,让大家在网络安全学习路上不迷茫。 在这份网安技能地图册里,我们对…...

网络技术与应用概论(上)——“计算机网络”
各位CSDN的uu们你们好呀,今天,小雅兰的内容依旧是计算机网络的一些知识点噢,下面,让我们进入计算机网络的世界吧 网络内涵 网络特征 网络定义 互联网发展过程 从ARPA网络到Internet 从低速互联网到高速互联网 从数据结构到统一网…...

JAVASE/封装、继承、多态
博客制作不易,欢迎各位点赞👍收藏⭐关注前言在学习面向对象编程语言时,封装、继承、多态则是我们必须学习和使用的三大特征。本文通过举例,说明了该三大特征的基本权限特点。一、访问限定符范围private默认权限protectedpublic同一…...

SpringBoot ElasticSearch 【SpringBoot系列16】
SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。 程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发 elasticsearch是一款非常强大的开源搜索引擎&a…...
Virtual box磁盘大小调整操作
Virtual box磁盘大小调整操作环境说明思路操作1、挂载要压缩的硬盘到 ~/data2、填充 0 文件3、删除 全是0空文件4、虚拟机关机5、在windows环境下用VBoxManage.exe 进行压缩硬盘加大环境说明 主机 windows 虚拟机 ubuntu 分配了 80G 的硬盘,现在已经占用 80 G 了。…...

MySQL注入秘籍【上篇】
MySQL注入秘籍【上篇】1.数据库敏感信息常用语句2.联合(UNION)查询注入3.报错注入原理常见报错注入函数1.数据库敏感信息常用语句 获取数据库版本信息 select version(); select innodb_version;获取当前用户 select user();获取当前数据库 select database();数…...

简单三步解决动态规划难题,记好这三步,动态规划就不难
目录一、简单的一维DP剑指 Offer 10- I. 斐波那契数列1、三板斧解决问题2、优雅的解决问题剑指 Offer 63 股票的最大利润1、三板斧解决问题2、优雅的解决问题二、进阶的二维DP剑指offer47 礼物的最大价值1、三板斧解决问题2、优雅的解决问题编辑距离1、三板斧解决问题2、优雅的…...

算法进阶指南打卡
文章目录 基本算法 位运算递推与递归前缀和与差分二分排序倍增贪心总结与练习基本数据结构 栈队列链表与邻接表Hash字符串Tire二叉堆总结与练习搜索 树与图的遍历深度优先搜索剪枝迭代加深广度优先搜索广度变形A*IDA*总结与练习数学知识 质数约数同余矩阵乘法高斯消元与线性空…...

Chapter6.2:其他根轨迹及综合实例分析
该系列博客主要讲述Matlab软件在自动控制方面的应用,如无自动控制理论基础,请先学习自动控制系列博文,该系列博客不再详细讲解自动控制理论知识。 自动控制理论基础相关链接:https://blog.csdn.net/qq_39032096/category_10287468…...

3. 无重复字符的最长子串——滑动窗口
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 示例 2: 输入: s "bbbbb" 输出: 1 解释: 因为无…...

ChatGPT研究分享:机器第一次开始理解人类世界
0、为什么会对ChatGPT感兴趣一开始,我对ChatGPT是没什么关注的,无非就是有更大的数据集,完成了更大规模的计算,所以能够回答更多的问题。但后来了解到几个案例,开始觉得这个事情并不简单。我先分别列举出来,…...

可换皮肤的Qt登录界面
⭐️我叫忆_恒心,一名喜欢书写博客的在读研究生👨🎓。 如果觉得本文能帮到您,麻烦点个赞👍呗! 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧,喜欢的小伙伴给个三连支持一下呗。👍⭐️❤️ 可换皮肤的Qt登录界面 QSS的学习笔记 快…...

Spring的常见问题汇总
一、bean实例化1、构造方法底层是无参构造方法来new的对象。2、静态工厂实例化Bean实质上就是:创建一个静态工厂类,然后调用静态工厂类的静态方法,来创建对象。3、实例工厂与FactoryBean实质上就是:创建一个工厂类,工厂…...

yolov8训练筷子点数数据集
序言 yolov8发布这么久了,一直没有机会尝试一下,今天用之前自己制作的筷子点数数据集进行训练,并且记录一下使用过程以及一些常见的操作方式,供以后翻阅。 一、环境准备 yolov8的训练相对于之前的yolov5简单了很多,…...

使用 Python 从点云生成 3D 网格
从点云生成 3D 网格的最快方法 已经用 Python 编写了几个实现来从点云中获取网格。它们中的大多数的问题在于它们意味着设置许多难以调整的参数,尤其是在不是 3D 数据处理专家的情况下。在这个简短的指南中,我想展示从点云生成网格的最快和最简单的过程。…...
vue使用split()将字符串分割数组join()将数组转字符串reverse()将数组反转
1.split() 将字符串切割成数组 const str Hello Vue2 Vue3 console.log(str.split()) console.log(str.split()) console.log(str.split( )) console.log(str.split( , 2)) console.log(str.split( , 6))输出如下 1.split()不传参数默认整个字符串作为数组的一个元素…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...