用fullpage做的网站,建美食网站有哪些原因,html网站 下载,深入解析wordpress(原书第2版)假设要公开特殊化排序例程#xff0c;以就地对内存数据执行操作。可能要公开需要使用数组的方法#xff0c;并提供对相应 T[] 执行操作的实现。如果方法的调用方有数组#xff0c;且希望对整个数组进行排序#xff0c;这样做就非常合适。但如果调用方只想对部分数组进行排序… 假设要公开特殊化排序例程以就地对内存数据执行操作。可能要公开需要使用数组的方法并提供对相应 T[] 执行操作的实现。如果方法的调用方有数组且希望对整个数组进行排序这样做就非常合适。但如果调用方只想对部分数组进行排序该怎么办可能还要公开需要使用偏移和计数的重载。但如果要支持的内存数据不在数组中而是来自本机代码举个例子或位于堆栈上并且你只有指针和长度该怎么办如何才能让编写的排序方法对内存的任意区域执行操作同时还对完整数组或部分数组以及托管数组和非托管指针同样有效又例如假设要对 System.String 实现操作如使用特殊化分析方法。可能要公开需要使用字符串的方法并提供对字符串执行操作的实现。但如果要支持对部分字符串执行操作该怎么办虽然 String.Substring 可用于分离出仅感兴趣的部分但此操作的成本相对高昂涉及字符串分配和内存复制。正如数组示例中提到的可以使用偏移和计数。但如果调用方没有字符串而是有 char[]该怎么办或者如果调用方有 char*例如为了使用堆栈上某空间而使用 stackalloc 创建的或通过调用本机代码而生成的该怎么办如果才能让编写的分析方法不强制调用方执行任何分配或复制操作同时还对输入的类型字符串、char[] 和 char* 同样有效在这两个示例中都可以使用不安全代码和指针同时公开接受指针和长度的实现。不过这样一来就无法获取对 .NET 至关重要的安全保障并且会遇到对大多数 .NET 开发人员而言已成为过去的问题如缓冲区溢出和访问冲突。此外这还会引发其他性能损失如需要在操作期间固定托管对象让检索的指针一直有效。而且根据涉及的数据类型获取指针根本就不可行。此难题还是有解决方法的即使用 SpanT。什么是 SpanTSystem.SpanT 是在 .NET 中发挥关键作用的新值类型。使用它可以表示任意内存的相邻区域无论相应内存是与托管对象相关联还是通过互操作由本机代码提供亦或是位于堆栈上。除了具有上述用途外它仍能确保安全访问和高性能特性就像数组一样。例如可以通过数组创建 SpanTvar arr new byte[10];
Spanbyte bytes arr; // Implicit cast from T[] to SpanT随后可以轻松高效地创建 Span以利用 Span 的 Slice 方法重载仅表示/指向此数组的子集。随后可以为生成的 Span 编制索引以编写和读取原始数组中相关部分的数据Spanbyte slicedBytes bytes.Slice(start: 5, length: 2);
slicedBytes[0] 42;
slicedBytes[1] 43;
Assert.Equal(42, slicedBytes[0]);
Assert.Equal(43, slicedBytes[1]);
Assert.Equal(arr[5], slicedBytes[0]);
Assert.Equal(arr[6], slicedBytes[1]);
slicedBytes[2] 44; // Throws IndexOutOfRangeExceptionbytes[2] 45; // OKAssert.Equal(arr[2], bytes[2]);
Assert.Equal(45, arr[2]);正如之前提到的Span 不仅仅只能用于访问数组和分离出数组子集。还可用于引用堆栈上的数据。例如Spanbyte bytes stackalloc byte[2]; // Using C# 7.2 stackalloc support for spansbytes[0] 42;
bytes[1] 43;
Assert.Equal(42, bytes[0]);
Assert.Equal(43, bytes[1]);
bytes[2] 44; // throws IndexOutOfRangeException更为普遍的是Span 可用于引用任意指针和长度如通过本机堆分配的内存如下所示IntPtr ptr Marshal.AllocHGlobal(1);try{Spanbyte bytes;unsafe { bytes new Spanbyte((byte*)ptr, 1); }bytes[0] 42;Assert.Equal(42, bytes[0]);Assert.Equal(Marshal.ReadByte(ptr), bytes[0]);bytes[1] 43; // Throws IndexOutOfRangeException}finally { Marshal.FreeHGlobal(ptr); }SpanT 索引器利用 C# 7.0 中引入的 C# 语言功能即引用返回。索引器使用“引用 T”返回类型进行声明其中提供为数组编制索引的语义同时返回对实际存储位置的引用而不是相应位置上存在的副本public ref T this[int index] { get { ... } }通过示例可以最明显地体现这种引用返回类型索引器带来的影响如将它与不是引用返回类型的 ListT 索引器进行比较。例如struct MutableStruct { public int Value; }
...
SpanMutableStruct spanOfStructs new MutableStruct[1];
spanOfStructs[0].Value 42;
Assert.Equal(42, spanOfStructs[0].Value);var listOfStructs new ListMutableStruct { new MutableStruct() };
listOfStructs[0].Value 42; // Error CS1612: the return value is not a variableSpanT 的第二个变体为 System.ReadOnlySpanT可启用只读访问。此类型与 SpanT 基本类似不同之处在于前者的索引器利用新 C# 7.2 功能来返回“引用只读 T”而不是“引用 T”这样就可以处理 System.String 等不可变数据类型。使用 ReadOnlySpanT可以非常高效地分离字符串而无需执行分配或复制操作如下所示string str hello, world;string worldString str.Substring(startIndex: 7, length: 5); // Allocates ReadOnlySpanchar worldSpan str.AsReadOnlySpan().Slice(start: 7, length: 5); // No allocationAssert.Equal(w, worldSpan[0]);
worldSpan[0] a; // Error CS0200: indexer cannot be assigned toSpan 的优势还有许多远不止已提到的这些。例如Span 支持 reinterpret_cast 的理念即可以将 Spanbyte 强制转换为 Spanint其中Spanint 中的索引 0 映射到 Spanbyte 的前四个字节。这样一来如果读取字节缓冲区可以安全高效地将它传递到对分组字节视作整数执行操作的方法。如何实现 SpanT开发人员通常无需了解要使用的库是如何实现的。不过对于 SpanT对背后的运作机制详情至少有一个基本了解是值得的因为这些详情暗含有关性能和使用约束的相关信息。首先SpanT 是包含引用和长度的值类型定义大致如下public readonly ref struct SpanT
{private readonly ref T _pointer;private readonly int _length;...
}“引用 T”字段这一概念初看起来有些奇怪因为其实无法在 C# 或甚至 MSIL 中声明“引用 T”字段。不过SpanT 实际上旨在于运行时使用特殊内部类型可看作是内部实时 (JIT) 类型由 JIT 为其生成等效的“引用 T”字段。以可能更为熟悉的引用用法为例public static void AddOne(ref int value) value 1;
...var values new int[] { 42, 84, 126 };
AddOne(ref values[2]);
Assert.Equal(127, values[2]);此代码通过引用传递数组中的槽这样除优化外还可以在堆栈上生成引用 T。SpanT 中的引用 T 有异曲同工之妙直接封装在结构中。直接或间接包含此类引用的类型被称为类似引用的类型C# 7.2 编译器支持在签名中使用引用结构从而声明这种类似引用的类型。根据这一简要说明应明确两点SpanT 的定义方式可确保操作效率与数组一样高为 Span 编制索引无需通过计算来确定指针开头及其起始偏移因为“引用”字段本身已对两者进行了封装。相比之下ArraySegmentT 有单独的偏移字段这就增加了索引编制和数据传递操作的成本。鉴于类似引用的类型这一本质SpanT 因其“引用 T”字段而受到一些约束。第二点带来了一些有趣的后果即导致 .NET 包含第二组相关的类型由 MemoryT 主导。什么是 MemoryT为什么需要它SpanT 是类似引用的类型因为它包含“引用”字段而且“引用”字段不仅可以引用数组等对象的开头还可以引用它们的中间部分var arr new byte[100];
Spanbyte interiorRef1 arr.AsSpan().Slice(start: 20);
Spanbyte interiorRef2 new Spanbyte(arr, 20, arr.Length – 20);
Spanbyte interiorRef3 Spanbyte.DangerousCreate(arr, ref arr[20], arr.Length – 20);这些引用被称为“内部指针”。对于 .NET 运行时的垃圾回收器跟踪这些指针是一项成本相对高昂的操作。因此运行时将这些引用约束为仅存在于堆栈上因为它隐式规定了可以存在的内部指针数量下限。此外如前所述SpanT 大于计算机的字大小也就是说对 Span 执行的读取和写入操作不是原子操作。如果多个线程同时对 Span 在堆上的字段执行读取和写入操作存在“撕裂”风险。 假设现有一个已初始化的 Span其中包含有效引用和值为 50 的相应 _length。一个线程开始编写新 Span并且还编写新 _pointer 值。然后还未将相应的 _length 设置为 20另一个线程就开始读取 Span其中包含新 _pointer 和更长的旧 _length。这样一来SpanT 示例只能存在于堆栈上而不能存在于堆上。也就是说无法将 Span 装箱进而无法将 SpanT 与现有反射调用 API举个例子结合使用因为它们需要执行装箱。这意味着无法将 SpanT 字段封装在类中甚至也无法封装在不类似引用的结构中。也就是说如果 Span 可能会隐式成为类中的字段则无法使用它们。例如将它们捕获到 lambda 中或将它们捕获为异步方法或迭代器中的本地字段因为这些本地字段可能最终会成为编译器生成的状态机上的字段。 这还意味着无法将 SpanT 用作泛型参数因为类型参数实例可能最终会被装箱或以其他方式存储到堆上暂无“where T : ref struct”约束。对于许多方案尤其是对于受计算量限制和同步处理功能这些限制无关紧要。不过异步功能却是另一回事。无论是处理同步操作还是异步操作本文开头提到的大部分有关数组、数组切片和本机内存等问题仍存在。但如果 SpanT 无法存储到堆因而无法跨异步操作暂留那么还有什么解决方法答案就是 MemoryT。MemoryT looks very much like an ArraySegmentT:public readonly struct MemoryT
{private readonly object _object;private readonly int _index;private readonly int _length;...
}可以通过数组创建 MemoryT并进行切片。这与处理 Span 基本相同不同之处在于 MemoryT 是不类似引用的结构可以存在于堆上。然后若要执行同步处理可以从中获取 SpanT例如static async Taskint ChecksumReadAsync(Memorybyte buffer, Stream stream)
{int bytesRead await stream.ReadAsync(buffer);return Checksum(buffer.Span.Slice(0, bytesRead));// Or buffer.Slice(0, bytesRead).Span}static int Checksum(Spanbyte buffer) { ... }与 SpanT 和 ReadOnlySpanT 一样MemoryT 也有等效的只读类型即 ReadOnlyMemoryT。与预期一样它的 Span 属性返回 ReadOnlySpanT。请参阅图 1快速概览在这些类型之间进行转换的内置机制。图 1在 Span 相关类型之间进行非分配/非复制转换来自收件人机制ArraySegmentTMemoryT隐式强制转换、AsMemory 方法ArraySegmentTReadOnlyMemoryT隐式强制转换、AsReadOnlyMemory 方法ArraySegmentTReadOnlySpanT隐式强制转换、AsReadOnlySpan 方法ArraySegmentTSpanT隐式强制转换、AsSpan 方法ArraySegmentTT[]Array 属性MemoryTArraySegmentTTryGetArray 方法MemoryTReadOnlyMemoryT隐式强制转换、AsReadOnlyMemory 方法MemoryTSpanTSpan 属性ReadOnlyMemoryTArraySegmentTDangerousTryGetArray 方法ReadOnlyMemoryTReadOnlySpanTSpan 属性ReadOnlySpanTref readonly T索引器 get 取值函数、封送处理方法SpanTReadOnlySpanT隐式强制转换、AsReadOnlySpan 方法SpanTref T索引器 get 取值函数、封送处理方法字符串ReadOnlyMemorycharAsReadOnlyMemory 方法字符串ReadOnlySpanchar隐式强制转换、AsReadOnlySpan 方法T[]ArraySegmentT构造函数、隐式强制转换T[]MemoryT构造函数、隐式强制转换、AsMemory 方法T[]ReadOnlyMemoryT构造函数、隐式强制转换、AsReadOnlyMemory 方法T[]ReadOnlySpanT构造函数、隐式强制转换、AsReadOnlySpan 方法T[]SpanT构造函数、隐式强制转换、AsSpan 方法void*ReadOnlySpanT构造函数void*SpanT构造函数将会注意到MemoryT 的 _object 字段并未强类型化为 T[]而是存储为对象。这突出说明 MemoryT 可以包装数组以外的内容如 System.Buffers.OwnedMemoryT。OwnedMemoryT 是抽象类可用于包装需要密切管理其生存期的数据如从池中检索到的内存。此主题更为高级超出了本文的介绍范围但这就是 MemoryT 的用途所在例如用于将指针包装到本机内存。ReadOnlyMemorychar 也可以与字符串结合使用就像 ReadOnlySpanchar 一样。SpanT 和 MemoryT 如何与 .NET 库集成在上面的 MemoryT 代码片段中将会注意到传入 Memorybyte 的 Stream.ReadAsync 调用。但如今在 .NET 中Stream.ReadAsync 被定义为接受 byte[]。它的工作原理是什么为了支持 SpanT 及其成员即将向 .NET 添加数百个新成员和类型。其中大多是现有基于数组和基于字符串的方法的重载而另一些则是专注于特定处理方面的全新类型。例如除了包含需要使用字符串的现有重载外所有原始类型如 Int32现在都包含接受 ReadOnlySpanchar 的 Parse 重载。假设字符串包含两部分数字用逗号隔开如“123,456”且希望分析这部分数字。现在可以编写如下代码string input ...;int commaPos input.IndexOf(,);int first int.Parse(input.Substring(0, commaPos));int second int.Parse(input.Substring(commaPos 1));不过这会生成两个字符串分配。若要编写高性能代码两个字符串分配可能就太多了。此时可以改为编写如下代码string input ...;
ReadOnlySpanchar inputSpan input.AsReadOnlySpan();int commaPos input.IndexOf(,);int first int.Parse(inputSpan.Slice(0, commaPos));int second int.Parse(inputSpan.Slice(commaPos 1));通过使用基于 Span 的新 Parse 重载可以在这整个操作期间避免执行分配操作。类似分析和格式化方法可用于原始类型如 Int32其中包括 DateTime、TimeSpan 和 Guid 等核心类型甚至还包括 BigInteger 和 IPAddress 等更高级别类型。实际上已跨框架添加了许多这样的方法。从 System.Random 到 System.Text.StringBuilder再到 System.Net.Socket这些重载的添加有利于轻松高效地处理 {ReadOnly}SpanT 和 {ReadOnly}MemoryT。其中一些甚至带来了额外的好处。例如Stream 现包含以下方法public virtual ValueTaskint ReadAsync(Memorybyte destination,CancellationToken cancellationToken default) { ... }将会注意到不同于接受 byte[] 并返回 Taskint 的现有 ReadAsync 方法此重载不仅接受 Memorybyte而不是 byte[]还返回 ValueTaskint而不是 Taskint。在以下情况下ValueTaskT 是有助于避免执行分配操作的结构经常要求使用异步方法来同步返回内容以及不太可能为所有常见返回值缓存已完成任务。例如运行时可以为结果 true 和 false 缓存已完成的 Taskbool但无法为 Taskint 的所有可能结果值缓存四十亿任务对象。由于相当常见的是 Stream 实现的缓冲方式让 ReadAsync 调用同步完成因此这一新 ReadAsync 重载返回 ValueTaskint。也就是说同步完成的异步 Stream 读取操作可以完全避免执行分配操作。ValueTaskT 也用于其他新重载如 Socket.ReceiveAsync、Socket.SendAsync、WebSocket.ReceiveAsync 和 TextReader.ReadAsync 重载。此外在一些情况下SpanT 还支持向框架添加在过去引发内存安全问题的方法。假设要创建的字符串包含随机生成的值如某类 ID。现在可能会编写要求分配字符数组的代码如下所示int length ...;
Random rand ...;var chars new char[length];for (int i 0; i chars.Length; i)
{chars[i] (char)(rand.Next(0, 10) 0);
}string id new string(chars);可以改用堆栈分配甚至能够利用 Spanchar这样就无需使用不安全代码。此方法还利用接受 ReadOnlySpanchar 的新字符串构造函数如下所示int length ...;
Random rand ...;
Spanchar chars stackalloc char[length];for (int i 0; i chars.Length; i)
{chars[i] (char)(rand.Next(0, 10) 0);
}string id new string(chars);这样做更好因为避免了堆分配但仍不得不将堆栈上生成的数据复制到字符串中。同样只有在所需空间大小对于堆栈而言足够小时此方法才有效。如果长度较短如 32 个字节可以使用此方法但如果长度为数千字节很容易就会引发堆栈溢出问题。如果可以改为直接写入字符串的内存该怎么办SpanT 可以实现此目的。除了包含新构造函数以外字符串现在还包含 Create 方法public static string CreateTState(int length, TState state, SpanActionchar, TState action);
...public delegate void SpanActionT, in TArg(SpanT span, TArg arg);实现此方法是为了分配字符串并分发可写 Span执行写入操作后可以在构造字符串的同时填写字符串的内容。请注意在此示例中SpanT 的仅限堆栈这一本质非常有用因为可以保证在字符串的构造函数完成前 Span引用字符串的内部存储就不存在这样便无法在构造完成后使用 Span 改变字符串了int length ...;
Random rand ...;string id string.Create(length, rand, (Spanchar chars, Random r)
{for (int i 0; chars.Length; i){chars[i] (char)(r.Next(0, 10) 0);}
});现在不仅避免了分配操作还可以直接写入字符串在堆上的内存即也避免了复制操作且不受堆栈大小限制的约束。除了核心框架类型有新成员外我们还正在积极开发许多可与 Span 结合使用的新 .NET 类型从而在特定方案中实现高效处理。例如对于要编写高性能微服务和处理大量文本的网站的开发人员如果在使用 UTF-8 时无需编码和解码字符串则性能会大大提升。为此我们即将添加 System.Buffers.Text.Base64、System.Buffers.Text.Utf8Parser 和 System.Buffers.Text.Utf8Formatter 等新类型。这些类型对字节 Span 执行操作不仅避免了 Unicode 编码和解码还能够处理在各种网络堆栈的最低级别中常见的本机缓冲ReadOnlySpanbyte utf8Text ...;if (!Utf8Parser.TryParse(utf8Text, out Guid value,out int bytesConsumed, standardFormat P))throw new InvalidDataException();所有此类功能不仅仅只用于公共使用用途框架本身也可以利用这些基于 SpanT 和基于 MemoryT 的新方法来提升性能。跨 .NET Core 调用网站已切换为使用新的 ReadAsync 重载以避免不必要的分配操作。分析过去是通过分配子字符串完成现在可以避免执行分配操作。甚至 Rfc2898DeriveBytes 等间隙类型也实际运用了此功能利用 System.Security.Cryptography.HashAlgorithm 上基于 Spanbyte 的新 TryComputeHash 方法显著减少分配操作量每次算法迭代的字节数组可能迭代数千次和提升吞吐量。这并未止步于核心 .NET 库一级而是继续全面影响堆栈。ASP.NET Core 现在严重依赖 Span例如在 Span 基础之上编写 Kestrel 服务器的 HTTP 分析程序。Span 今后可能会通过较低级别 ASP.NET Core 中的公共 API 公开如在它的中间件管道中。.NET 运行时又如何呢.NET 运行时提供安全保障的方法之一是确保为数组编制的索引不超出数组的长度这种做法称为“边界检查”。例如以下面这个方法为例[MethodImpl(MethodImplOptions.NoInlining)]static int Return4th(int[] data) data[3];在我撰写本文使用的 x64 计算机上针对此方法生成的程序集如下所示sub rsp, 40cmp dword ptr [rcx8], 3jbe SHORT G_M22714_IG04mov eax, dword ptr [rcx28]add rsp, 40ret
G_M22714_IG04:call CORINFO_HELP_RNGCHKFAILint3cmp 指令将数据数组的长度与索引 3 进行比较。如果 3 超出范围异常抛出后续 jbe 指令会转到范围检查失败例程。虽然 JIT 需要生成代码以确保此类访问不会超出数组边界但这并不意味着每个数组访问都需要进行边界检查。以下面的 Sum 方法为例static int Sum(int[] data)
{int sum 0;for (int i 0; i data.Length; i) sum data[i];return sum;
}虽然 JIT 此时需要生成代码以确保对 data[i] 的访问不超出数组边界但因为 JIT 能够通过循环结构判断 i 一直在范围内循环从头到尾遍历每个元素所以 JIT 可以优化为不对数组进行边界检查。因此针对循环生成的程序集代码如下所示G_M33811_IG03:movsxd r9, edxadd eax, dword ptr [rcx4*r916]inc edxcmp r8d, edxjg SHORT G_M33811_IG03虽然 cmp 指令仍在循环中但只需将 i 值存储在 edx 寄存器中与数组长度存储在 r8d 寄存器中进行比较无需额外进行边界检查。运行时向 SpanSpanT 和 ReadOnlySpanT应用类似优化。将上面的示例与下面的代码进行比较唯一的变化是参数类型static int Sum(Spanint data)
{int sum 0;for (int i 0; i data.Length; i) sum data[i];return sum;
}针对此代码生成的程序集几乎完全相同G_M33812_IG03:movsxd r9, r8dadd ecx, dword ptr [rax4*r9]inc r8dcmp r8d, edxjl SHORT G_M33812_IG03程序集代码如此相似部分是因为不用进行边界检查。此外同样重要的是 JIT 将 Span 索引器识别为内部类型即 JIT 为索引器生成特殊代码而不是将它的实际 IL 代码转换为程序集。所有这些都是为了说明运行时可以为 Span 应用与数组相同的优化类型让 Span 成为高效的数据访问机制。如需了解更多详情请参阅 bit.ly/2zywvyI 上的博客文章。C# 语言和编译器又如何呢我已暗示添加到 C# 语言和编译器的功能有助于让 SpanT 成为 .NET 中的一流成员。C# 7.2 的多项功能都与 Span 相关实际上C# 7.2 编译器必须使用 SpanT。接下来将介绍三个此类功能。引用结构。如前所述SpanT 是类似引用的类型自版本 7.2 起在 C# 中公开为引用结构。通过将引用关键字置于结构前可以指示 C# 编译器将其他引用结构类型如 SpanT用作字段这样做还会注册要分配给类型的相关约束。例如若要为 SpanT 编写结构枚举器枚举器需要存储 SpanT因此它本身必须是引用结构如下所示public ref struct Enumerator
{private readonly Spanchar _span;private int _index;...
}Span 的 stackalloc 初始化。在旧版 C# 中只能将 stackalloc 的结果存储到指针本地变量中。自 C# 7.2 起现在可以在表达式中使用 stackalloc并能定目标到 Span而不使用不安全关键字。因为无需编写Spanbyte bytes;unsafe{byte* tmp stackalloc byte[length];bytes new Spanbyte(tmp, length);
}只需编写Spanbyte bytes stackalloc byte[length];如果需要一些空间来执行操作但又希望避免分配相对较小的堆内存此代码就非常有用。过去有以下两种选择编写两个完全不同的代码路径对基于堆栈的内存和基于堆的内存执行分配和操作。固定与托管分配相关联的内存再委托到实现实现也用于基于堆栈的内存并通过不安全代码中的指针控制进行编写。现在不使用代码复制即可完成相同的操作而且还可以使用安全代码和最简单的操作Spanbyte bytes length 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the SpanbyteSpan 使用验证。因为 Span 可以引用可能与给定堆栈帧相关联的数据所以传递 Span 可能存在危险此操作可能会引用不再有效的内存。例如假设方法尝试执行以下操作static Spanchar FormatGuid(Guid guid)
{Spanchar chars stackalloc char[100];bool formatted guid.TryFormat(chars, out int charsWritten, d);Debug.Assert(formatted);return chars.Slice(0, charsWritten); // Uh oh}此时空间从堆栈进行分配然后尝试返回对此空间的引用但在返回的同时此空间不再可用。幸运的是C# 编译器使用引用结构检测此类无效使用并会停止编译同时显示以下错误错误 CS8352:无法在此上下文中使用本地“字符”因为它可能会在声明范围外公开引用的变量接下来会怎样呢本文介绍的类型、方法、运行时优化和其他元素即将顺利添加到 .NET Core 2.1 中。之后我预计它们会全面影响 .NET Framework。核心类型如 SpanT和新类型如 Utf8Parser也即将顺利添加到与 .NET Standard 1.1 兼容的 System.Memory.dll 包中。这样一来相关功能将适用于现有 .NET Framework 和 .NET Core 版本尽管在内置于平台时没有实现一些优化。现在可以试用此包的预览版只需添加对 NuGet 上 System.Memory.dll 包的引用即可。当然请注意当前预览版与实际发布的稳定版之间可能会有重大变革。此类变革很大程度上源于像你这样的开发人员在试用功能集时提供的反馈。因此请试用预览版并关注 github.com/dotnet/coreclr 和 github.com/dotnet/corefx 存储库以掌握最新动态。此外有关文档还可以访问 aka.ms/ref72。总的来说此功能集能否取得成功依赖开发人员试用预览版、提供反馈以及利用这些类型生成自己的库所有这些都是为了能够在新式 .NET 程序中高效安全地访问内存。我们热切期待聆听大家的使用体验反馈最好能够与大家一起在 GitHub 上进一步改进 .NET。原文https://msdn.microsoft.com/zh-cn/magazine/mt814808.NET社区新闻深度好文欢迎访问公众号文章汇总 http://www.csharpkit.com