目录
在 Visual Studio 2022 中创建新的 .NET 10 控制台应用程序项目
微软已在今年年底(2025 年 11 月)发布 .NET 10。值得一提的是,这将是一个长期支持 (LTS) 版本。.NET 10 的新版现已发布。本文概述了 .NET 10 的新特性和增强功能。您可以从以下链接下载 .NET 10 预览版:https://dotnet.microsoft.com/en-us/download/dotnet/10.0。
下一篇:https://blog.youkuaiyun.com/hefeng_aspnet/article/details/156083627

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。
要使用本文中讨论的代码示例,必须安装以下软件:
- Visual Studio 2022 或 2026
- .NET 10.0
- ASP.NET 10.0 运行时
如果您的计算机上尚未安装 Visual Studio 2022,您可以从这里下载:https://visualstudio.microsoft.com/downloads/ 。
在本文中,在探索了新功能之后,您将实现一个利用 ASP.NET Core 10、Blazor 10、EF Core 10 和 C# 14 的简单应用程序。
.NET 10 中的新特性和增强功能
本节将探讨 .NET 10 中的新特性和增强功能。
.NET 运行时改进
.NET 10 增强了对优化 JIT 编译的支持,从而显著提升了性能和效率。例如,改进的方法内联和循环展开优化功能可以加快常用代码路径的执行速度,从而提高长时间运行应用程序的性能。
以下是 .NET 运行时 10 的主要性能增强:
- 数组接口方法的去虚拟化
- 内联后期去虚拟化方法
- 在栈中分配小尺寸数组
- 数组枚举的反抽象
.NET 10 库中的新特性和增强功能
微软发布了 .NET 10,其中包含大量新功能和更新的库,旨在提升速度、安全性和开发人员效率。此外,.NET 10 还引入了额外的 API,方便用户使用 span,从而帮助减少不必要的内存分配,如下面的代码片段所示:
public static class StringNormalizationExtensions
{
public static int GetNormalizedLength(this ReadOnlySpan<char> source,
NormalizationForm normalizationForm = NormalizationForm.FormC);
public static bool IsNormalized(this ReadOnlySpan<char> source,
NormalizationForm normalizationForm = NormalizationForm.FormC);
public static bool TryNormalize(this ReadOnlySpan<char> source,
Span<char> destination, out int charsWritten,
NormalizationForm normalizationForm = NormalizationForm.FormC);
}
.NET 10 库中包含大量新特性和增强功能,例如:
- ZipArchive性能改进
- JSON 序列化更新
CompareOptions.NumericOrdering通过枚举进行数值字符串比较- 改进
OrderedDictionary ValidationContext类中新增了 AOT 安全构造函数- 新的重载方法
TimeSpan.FromMilliseconds - 用于跨度的字符串规范化 API
在之前的 C# 编程语言版本中,所有 ZipArchiveEntry 实例都会先加载到内存中,然后再进行重写,这会增加资源占用并导致性能问题。在 .NET 10 中,ZipArchiveEntry 实例的资源使用得到了优化,现在只需将所需的条目加载到内存中,从而提升性能。以下代码片段展示了如何在 ZIP 压缩包中创建一个新条目,然后使用文件流实例向其中写入数据。
using (FileStream fileStream = new FileStream(@"D:\test.zip", FileMode.Open))
{
using (ZipArchive zipArchive = new ZipArchive(fileStream,
ZipArchiveMode.Update))
{
ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry("Sample.txt");
using (StreamWriter writer = new StreamWriter(zipArchiveEntry.Open()))
{
writer.WriteLine("This is a sample piece of text.");
}
}
}
为了处理该类型,ISOWeek 类中添加了以下新的重载方法DateOnly。
public static class ISOWeek
{
// Additional overloaded methods
public static int GetWeekOfYear(DateOnly date);
public static int GetYear(DateOnly date);
public static DateOnly ToDateOnly(int year, int week,
DayOfWeek dayOfWeek);
}
为了提高AOT安全性,该类中引入了一个新的构造函数ValidationContext。以下代码片段展示了该类中的更改:
public sealed class ValidationContext
{
public ValidationContext(object instance, string displayName,
IServiceProvider? serviceProvider = null,
IDictionary<object, object?>? items = null);
}
.NET 10 引入了更多重载方法,用于处理OrderedDictionary<TKey,TValue>,如下代码片段所示的情况:
public class OrderedDictionary<TKey, TValue>
{
//Additional overloads
public bool TryAdd(TKey key, TValue value, out int index);
public bool TryGetValue(TKey key, out TValue value, out int index);
}
数值字符串比较是 C# 中一项备受期待的功能。从 C# 14 开始,我们可以按数值而非字典顺序比较字符串。该CompareOptions枚举定义在 System.Globalization 命名空间中,如下面的代码片段所示:
using System;
using System.Globalization;
[Flags, Serializable]
[System.Runtime.InteropServices.ComVisible(true)]
public enum CompareOptions
{
None = 0x00000000,
IgnoreCase = 0x00000001,
IgnoreNonSpace = 0x00000002,
IgnoreSymbols = 0x00000004,
IgnoreKanaType = 0x00000008,
IgnoreWidth = 0x00000010,
OrdinalIgnoreCase = 0x10000000,
StringSort = 0x20000000,
Ordinal = 0x40000000
}
要使用枚举功能CompareOptions,您应该将 System.Globalization NuGet 包安装到您的项目中,并在程序中包含 System.Globalization 程序集。以下代码片段展示了如何实现这一点:
StringComparer numericStringComparer =
StringComparer.Create(CultureInfo.CurrentCulture,
CompareOptions.NumericOrdering);
foreach (string str in new[] { "05", "01", "03", "02", "04" }
.Order(numericStringComparer))
{
Console.WriteLine(str);
}
Console.ReadKey();
当执行前面的代码片段时,字符串数组中的数字将按升序显示,如图1所示。

图 1:以升序显示字符串数组中的数字
在 .NET 10 中,该TimeSpan.FromMilliseconds方法新增了一个重载版本,接受单个参数,如下面的代码片段所示:
public readonly struct TimeSpan
{
public static TimeSpan FromMilliseconds(long milliseconds,
long microseconds);
public static TimeSpan FromMilliseconds(long milliseconds);
}
前面的代码片段中,结构体FromMilliseconds中的方法有两个重载版本TimeSpan。现在,第一个重载方法的第二个参数不再是可选的。而新增的重载版本FromMilliseconds则接受一个在之前的 C# 编程语言版本中不支持的参数。
小型固定大小数组的栈分配
由于 .NET 10 支持将较小的不可变数组分配到栈上,因此可以显著降低垃圾回收的开销,从而消除堆内存分配的需求,进而改善数组内存分配的开销。此外,.NET 10 通过将小数组存储在栈上,降低了垃圾回收 (GC) 的开销,并提高了整体系统效率。
支持高级矢量扩展
由于支持 AVX 10.2 内部函数,.NET 10 能够实现高性能 CPU 优化。虽然默认情况下此功能处于禁用状态,但一旦兼容的处理器上市,它将显著提升数值计算、人工智能和图形计算的性能。这些改进将极大地惠及从事科学计算、图像处理和机器学习应用开发的开发者。
垃圾回收(GC)增强
虽然托管对象是在托管堆中创建并在垃圾回收周期中从托管堆中移除的,但您需要编写代码来显式删除非托管对象。垃圾回收是 CLR 采用的一种策略,用于清除已成为“垃圾”的托管对象所占用的内存。.NET Framework 和 .NET Core 都将垃圾回收作为一项内置功能,用于清理托管对象占用的内存。
.NET 10 增强了对内存管理的支持,从而降低了实际应用中的延迟。后台垃圾回收器 (GC) 经过优化,通过内存压缩增强功能消除了内存碎片,提高了应用程序的整体性能。
增强型代码布局
在 .NET 10 中,JIT 编译器采用了一种新的方式来将方法代码组织成基本块,从而优化运行时性能。在早期版本的 .NET Framework 和 .NET Core 中,JIT 使用反向后序遍历 (RPO) 对程序的流程图进行初始布局。虽然这种方法在一定程度上提高了性能,但也存在一些缺点。
需要注意的是,代码块重排序是基于非对称旅行商问题的简化模型实现的。借助这些技术,.NET 10 优化了热路径密度,同时减少了分支距离,从而提高了整体性能。

C# 14 的新特性
C# 14 引入了大量新特性和增强功能,旨在提高开发人员的效率和代码质量。在本节中,我将探讨 C# 14 中的一些关键特性和增强功能,例如:
- 使用 Visual Studio 中的预览功能
- 关键词
Field - 增强对使用 lambda 表达式的支持
- 增强的部分功能
- 简单 lambda 参数的修饰符
- 启用内联以进行去虚拟化
- 数组接口方法的去虚拟化
- 在栈中分配值类型数组
- 支持空条件赋值
- 支持隐式跨度转换
- 支持对引用类型的小数组进行栈分配
在 Visual Studio 2022 中创建新的 .NET 10 控制台应用程序项目
让我们创建一个控制台应用程序项目,以便使用本文后续章节中提供的 C# 14 代码示例。您可以通过多种方式在 Visual Studio 2022 中创建项目。启动 Visual Studio 2022 后,您会看到“开始”窗口。您可以选择“不编写代码继续”来启动 Visual Studio 2022 IDE 的主界面。
要在 Visual Studio 2022 中创建新的控制台应用程序项目:
- 启动 Visual Studio 2022 IDE。
- 在“创建新项目”窗口中,选择“控制台应用程序”,然后单击“下一步”继续。
- 在“配置新项目”窗口中,指定项目名称和创建路径。
- 如果您希望解决方案文件和项目创建在同一目录中,可以选择选中“将解决方案和项目放在同一目录中”复选框。单击“下一步”继续。
- 在“附加信息”屏幕中,将目标框架指定为 .NET 10.0,如图 2所示。
- 点击“创建”完成该过程。

图 2:在“附加信息”屏幕中配置您的应用程序
本文后续章节将用到此应用程序。
在 Visual Studio 中使用功能
要在 Visual Studio 中使用功能,请编辑项目文件并添加元素LangVersion,如下面的代码片段所示:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
如果需要配置多个 C# 项目以使用预览功能,请创建一个Directory.Build.props文件并将以下代码添加到该文件中:
<Project>
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>
请勿Directory.Build.Props在文件夹的子目录中使用该文件,因为该文件夹的子目录包含用 C# 和 VB 编写的项目,因为它们的版本不同。
字段关键词
在 C# 14 之前,您需要声明一个支持字段并实现 Get 和 Set 访问器来检查字符串类型的属性是否设置为 null,如下面的代码片段所示:
private string _address;
public string Address
{
get => _address;
set => _address = value ?? throw new ArgumentNullException(nameof(value));
}
C# 14 引入了Field`this` 关键字,使代码更加简洁、易读且易于维护。您可以在类的顶部声明这些字段,并在类的构造函数中初始化它们。以下代码片段展示了如何在 C# 14 中修改前面的代码,以确保属性Address永远不会被赋值null,从而使源代码更加简洁清晰。
public string Address
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
此外,C# 14 允许为与字段支持的属性相关的 Get 和 Set 访问器声明主体。请注意,如果类型中已包含名为 field 的符号,请使用 `@field`@field或 `@ this.fieldget` 来将其与关键字区分开来,避免歧义。
增强对 Lambda 表达式的支持
ref在 C# 14 中,您可以在 lambda 表达式参数上使用`<a>`、 `<b>` scoped、in`<c>` out、`<d>` 或 `<c>`等参数修饰符,ref readonly而无需显式指定参数类型。让我们通过一个代码示例来理解这一点。请看以下代码片段:
TryParse<int> parse1 = (text, out result) =>
{
Int32.TryParse(text, out result);
};
该语句在 C# 14 中完全有效。您不再需要显式指定参数类型。
部分事件和部分构造函数
`partial` 关键字在 C# 编程语言中已经使用了很长时间。到目前为止,您已经见过部分方法、部分属性和部分索引器。在 C# 14 中,您可以将 `partial` 关键字用于事件和构造函数,也就是说,现在您可以拥有部分事件和部分构造函数。C# 现在为处理类的部分成员提供了更强大的支持。
现在,您可以在构造函数或事件的签名中包含 `partial` 关键字,从而实现声明代码和实现代码的隔离。对于部分构造函数和事件,您只需要一个定义和一个实现。您只能在部分构造函数的实现中使用this()`or`初始化器。base()
如果构造函数需要调用同一类或基类中的另一个构造函数(使用 `__init__`或 `__init__` this())base(),则初始化器只能在分部构造函数的实现声明中提及。以下代码片段展示了如何在分部类中声明分部方法,并在另一个同名分部类中编写该方法的实现代码。
public partial class MyExamplePartialClass
{
public MyExamplePartialClass()
{
MyExamplePartialMethod();
}
partial void MyExamplePartialMethod();
}
public partial class MyExamplePartialClass
{
partial void MyExamplePartialMethod()
{
//Write your code here to implement
//the body of the method
}
}
在前面的代码片段中,该MyExamplePartialClass类型可以位于同一个文件中,也可以位于不同的文件中。以下代码片段演示了如何在 C# 14 中使用部分构造函数。
public partial class Author
{
public partial Author(Guid id); //This represents
//declaring the partial constructor, i.e.,
//i.e., it contains only its signature
}
public partial class Author
{
public partial Author(Guid id)
{
//This shows a partial constructor, i.e.,
//it contains the actual body
}
}
前面的代码片段Author展示了一个名为 `<partial class>` 的部分类,其中声明了一个部分构造函数。请注意,我声明了部分构造函数的签名,而没有包含任何方法体。该部分构造Author函数在另一个同名的部分类中实现,也就是说,这两个部分类的名称相同。
以下是 C# 14 中使用部分构造函数的关键规则概览:
- 分部构造函数只能有一个定义声明和一个实现声明。
- 分部构造函数只能在分部类中使用。
- 部分构造函数不能是抽象的,也就是说,它不能带有 abstract 修饰符。
在 C# 中,部分事件表示事件声明,其中事件定义与其实现是分离的。当 C# 类的定义被拆分,使得事件处理逻辑与类定义分离时,可以使用部分事件。这样,您可以定义事件签名,并让源代码生成器生成实现。这有助于分离应用程序的关注点,同时还能编写更简洁、更易于维护的代码。
与部分构造函数类似,部分事件只能声明两次——一次是定义(不包含任何主体),另一次是实现。在部分事件的实现声明中,必须同时提供 Add 和 Remove 访问器。与部分构造函数一样,部分事件只能用于部分类,即在类声明中使用 `partial` 关键字标记的类。不能将部分事件标记为抽象类,也不能在接口中使用 `partial` 关键字来实现事件。
以下是 C# 14 中使用部分事件的关键规则概览:
- 对于部分事件,可以有一个定义声明和一个实现声明。
- 部分事件的实现声明必须包含一个或多个访问器。
- 部分事件只能在部分类中使用,而不能在抽象类或接口的实现中使用。
清单 1展示了如何在 C# 14 中实现部分事件。
public partial class PartialEventDeclarationExample
{
public partial event EventHandler DataReceived;
}
public partial class PartialEventImplementationExample
{
private EventHandler _dataReceivedExampleHandler;
public partial event EventHandler DataReceivedEventExample
{
add
{
Console.WriteLine("A new subscriber has been added to
DataReceivedEventExample");
_dataReceivedExampleHandler += value;
}
remove
{
Console.WriteLine("An existing subscriber has been
removed from DataReceivedEventExample");
_dataReceivedExampleHandler -= value;
}
}
protected void OnDataReceived(EventArgs e)
{
_dataReceivedExampleHandler?.Invoke(this, e);
}
}
提高 Lambda 表达式的可读性
在 C# 14 中,您可以向 lambda 表达式添加参数修饰符,例如ref` ref readonly<a>`、scoped`<b>`、`<c> in` 和 `<d>`,而out无需显式声明类型,从而提高了 lambda 表达式的可读性。例如,在 C# 13 中,您需要为这些修饰符指定显式类型,如下面的代码片段所示:
TryParse<int> parse = (string text, out int result) =>
{
return int.TryParse(text, out result);
};
由于 .NET 10 提供了类型推断支持,因此可以这样编写相同的代码:
delegate bool TryParse<T>
(
string text, out T result
);
TryParse<int> parse = (text, out result) => int.TryParse(text, out result);
启用内联以进行去虚拟化
C# 中的虚方法用于实现运行时多态。尽管它们提供了极大的灵活性,但也存在一些性能方面的不足。C# 编程语言底层使用虚方法表(也称为 vtable)来确定运行时要调用的具体方法。需要注意的是,vtable 是类特定的,也就是说,每个包含至少一个虚方法的类都有一个对应的 vtable。.NET Core 10 中的即时 (JIT) 编译器能够内联那些符合反虚拟化条件的方法。
数组接口方法的去虚拟化
在 .NET 10 中,JIT 编译器支持基于内联观察结果的去虚拟化。它现在会修改包含内联返回值的临时变量的类型。.NET 10 运行时增强了对提升性能和减少源代码抽象开销的支持。这是通过启用 JIT 编译器对数组接口方法进行去虚拟化来实现的。在 .NET 10 之前,JIT 能够通过移除方法中的边界检查来优化 .NET 9 中的循环,例如:
static int AddIntegers(int[] integers)
{
int sum = 0;
for (int i = 0; i < integers.Length; i++)
{
sum += integers[i];
}
return sum;
}
也就是说,如果你要迭代一个 foreach 循环IEnumerable<int>,就会引入虚函数调用,从而阻止你使用内联或消除代码中的边界检查来优化源代码。
static int AddIntegers(int[] integers)
{
int sum = 0;
IEnumerable<int> temp = integers;
foreach (var n in temp)
{
sum += n;
}
return sum;
}
在栈中分配值类型数组
在栈内存中分配和释放对象可以减少垃圾回收器的开销,因为垃圾回收器需要跟踪的对象更少。一旦对象被分配到栈内存中,即时编译器 (JIT) 就会将其替换为其标量值。在 .NET 10 中,JIT 可以在栈内存中分配小型、固定大小的值类型数组。
在 .NET 9 中,您可以将对象分配到栈上,从而减少垃圾回收的开销并提高性能。在 .NET 10 中,这一特性得到了进一步增强,允许分配小型、固定大小的值类型数组,这些数组在栈上没有任何垃圾回收引用。
请看以下代码片段:
static int AddIntegers()
{
int[] integers = { 1, 2, 3 };
int sum = 0;
for (int i = 0; i < integers.Length; i++)
{
sum += integers[i];
}
return sum;
}
在前面的代码片段中,由于命名整数数组的大小是固定的,因此 JIT 将其分配在栈上而不是堆上,从而提高了性能。
空条件赋值
null在 C# 14 中,将变量赋值给属性时无需再检查其值。现在,您可以null在赋值语句的左侧使用条件成员访问运算符,如代码片段所示:
employee?.DepartmentId = GetDepartmentId(employeeId);
在 C# 14 之前,您需要编写以下代码:
if (employee is not null)
{
employee.DepartmentId = GetDepartmentId(employeeId);
}
此功能可减少代码中冗长的空值检查,从而使代码更高效、更简洁、更不易出错。让我们用另一个代码示例来说明这一点。请看以下代码片段,它演示了一个名为 `class` 的简单 C# 类。MyExampleClass:
public class MyExampleClass
{
public string MyExampleProperty
{
get;
set;
}
}
以下名为 `<class_name>` 的静态类MyExampleFactory包含一个静态方法,该方法返回一个实例。MyExampleClass:
public static class MyExampleFactory
{
public static MyExampleClass MyExampleMethod()
{
return new MyExampleClass();
}
}
通常情况下,您可以使用以下代码片段来实例化MyExampleClass类并为其属性赋值:
MyExampleClass?
myExampleObject = MyExampleFactory.MyExampleMethod();
if (myExampleObject is not null)
{
myExampleObject.MyExampleProperty = "Hello World!";
}
使用 C# 14,前面的代码可以简化为以下形式:
MyExampleClass?
myExampleObject = MyExampleFactory.MyExampleMethod();
myExampleObject?.MyExampleProperty = "Hello World!";
执行前面的代码时,运行时会检查myExampleObject实例是否为空。如果实例不为空,则执行赋值操作,即将字符串“Hello World!”赋值给其名为 `<string>` 的字符串属性MyExampleProperty。如果实例为空,则忽略赋值语句。该类MyExampleFactory是一个工厂类,即遵循工厂设计模式并返回 `<string>` 类型实例的类MyExampleClass。
隐式跨度转换
Span<T>`System`、 `System` Memory<T>、` ReadOnlySpanSystem` 和ReadOnlyMemory`System` 是在 System 命名空间中定义的类型,并在较新版本的 C# 编程语言中添加。它们表示连续的内存块,并提供了一种类型安全的方式来访问任意内存的连续区域。
System.Span<T>在 C# 14 中,微软为 C# 语言本身引入了对`T` 和 `C`类型的一流支持System.ReadOnlySpan<T>。因此,您现在可以利用这些类型的隐式转换,从而提高代码的可读性和灵活性。这些增强功能提升了性能、安全性和互操作性,并提供了一种在 C# 中更自然的方式来编写跨度(span)代码。
System.Span<T>这两种System.ReadOnlySpan<T>类型都能通过减少内存分配来显著提升应用程序的性能,从而减轻垃圾回收器的压力。Span<T>它们定义在 System 命名空间中,如下面的代码片段所示:
public readonly ref struct Span<T>
{
internal readonly ByReference<T> _pointer;
private readonly int _length;
// Other members
}
ReadOnlySpan<T>定义在 System 命名空间中,如下所示:
public readonly ref struct ReadOnlySpan<T>
{
internal readonly ref T _reference;
private readonly int _length;
//Other members
}
现在可以将以下类型转换为Span<T>:
- 数组
- 指针
- 整数指针
- stackalloc
此外,您还可以将以下类型转换为ReadOnlySpan<T>:
- 数组
- 指针
- 整数指针
- stackalloc
- 细绳
以下代码片段展示了如何Span<T>在 C# 14 中将数组转换为 A:
string[] words =
{
"This", "is", "a", "sample", "text."
};
ReadOnlySpan<string> readOnlySpan = words;
引用类型小数组的栈分配
在 .NET 10 中,微软改进了栈内存中对象的分配机制。在 .NET 10 之前,无论数组的生命周期如何,引用类型的数组始终分配在堆内存中。而 .NET 10 允许 JIT 在数组的生命周期尚未超过其创建上下文时,将其分配到栈内存中。
请看以下代码片段:
static void DisplayText()
{
string[] array = { "A", "B", "C", "D", "E" };
foreach (var str in array)
{
Console.WriteLine(str);
}
}
在前面的代码片段中,名为“array”的字符串数组将在栈内存中分配。
要使用 C# 14,您的计算机上需要安装 .NET 10 SDK 和 Visual Studio 2022(版本 17.14 或更高版本)。此外,您还需要在 Visual Studio 的<LangVersion>14</LangVersion>配置.csproj文件中启用 C# 14 功能。

下一篇:https://blog.youkuaiyun.com/hefeng_aspnet/article/details/156083627
如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。
1063

被折叠的 条评论
为什么被折叠?



