C# 10.0 中的新增功能
语言进化
新功能
文件范围命名空间声明
全局 using 指令
常量插值字符串
扩展属性模式
Lambda 改进
调用者参数表达式
记录结构
增量生成器
改进的明确分配分析
结论
参考
C# 大约在 20 年前首次发布,最近发布的速度加快了,过去 4 年里每年都会发布新版本。
C# 10.0 很可能会在 11 月 9 日举行的.NET Conf 2021上与 .NET 6.0 一同发布(大会将于 11 月 11 日结束)。因此,我想总结一下 C# 10.0 中引入的一些新功能。不过首先,先来回顾一下历史……
语言进化
最初,C# 每隔几年发布一次。每个版本通常都包含一个标志性特性,需要仔细思考如何使用它。例如,C# 2.0 引入了泛型,C# 3.0 引入了 LINQ,C# 5.0 引入了用于异步编程的 async/await。
随着最新版本每年发布,他们更加注重通过较小的更新来简化代码。然而,大型新功能现在可能会在几个版本中逐步演进。例如,模式匹配现在持续演进,跨越了 4 个版本。记录功能是这种演进方法的另一个例子。
C# 语言设计也在 2013 年左右从封闭、秘密的开发模式转变为开放的开发模式。现在,您甚至可以为GitHub 上托管的 C# 语言设计论坛做出贡献,帮助该语言进一步发展。设计会议和路线图都会公开发布在网站上,以便进行公开讨论和反馈。
新功能
我选择不描述 C# 10.0 的所有提议功能,因为这篇文章会很长(它已经很长了!)。如果您对完整的提议列表感兴趣,请参阅C# 语言版本历史记录。
-
文件范围命名空间声明省去了命名空间声明带来的浪费的缩进和括号,从而为所有 C# 文件删除了一级缩进和括号。
-
全局使用指令允许您集中定义使用指令,这样就不需要在每个文件中定义它们。
-
常量插值字符串允许在可能的情况下将插值字符串声明为常量。
-
扩展属性模式简化了模式匹配语句中嵌套属性的使用。
-
Lambda 改进包括在 lambda 上定义属性的能力、定义 lambda 时使用 var 的能力以及指定 lambda 返回类型的能力。
-
调用者参数表达式添加了一个新属性来帮助在编译时识别表达式信息。
-
记录结构演变了 C# 9.0 记录类引用类型,以添加新的记录结构值类型来有效地操作小记录。
-
增量生成器将源代码生成器提升到一个新的水平,不仅可以生成源代码,还可以提高生成性能。
-
改进了明确赋值分析,修复了一些编译器抱怨变量尚未初始化的模糊情况。
文件范围命名空间声明
而不是...
namespace WhatsNew10
{
public class Foo
{
}
}
...您可以通过使用以下内容声明命名空间来省去顶级括号...
namespace WhatsNew10;
public class Foo
{
}
全局 using 指令
using 指令从指定的命名空间导入所有类型。
在任何代码文件中的指令中添加关键字“global”将导入这些命名空间类型并使这些类型可用于项目中的所有代码。
global using System;
编译器还会根据 .NET 6.0 项目 SDK 类型创建隐式全局 using 指令。以下列出了两种主要项目类型的隐式 using 指令……
项目 SDK 类型 | 隐式全局使用 |
---|---|
微软.NET SDK | 系统 |
系统 | |
系统输入输出 | |
系统.Net.Linq | |
系统.Net.Http | |
系统线程 | |
系统线程任务 | |
Microsoft.NET.Sdk.Web | 系统 |
System.Net.Http.Json | |
Microsoft.AspNetCore.Builder | |
Microsoft.AspNetCore.Hosting | |
Microsoft.AspNetCore.Http | |
Microsoft.AspNetCore.路由 | |
Microsoft.Extensions.配置 | |
Microsoft.Extensions.DependencyInjection | |
Microsoft.Extensions.Hosting | |
Microsoft.Extensions.Logging |
因此,与 C# 9.0顶级语句功能一起,Program.cs 的完整代码文件可以如下所示...
Console.WriteLine("Hello, World!");
常量插值字符串
现在允许对插值字符串使用以下常量关键字,而以前会产生编译错误......
const string s1 = $"Hello world";
const string s3 = $"{s1} C#10";
扩展属性模式
考虑以下情况...
var aa = new A { Property1 = "foo" };
var bb = new B { Property2 = aa };
public class A
{
public string Property1 { get; set; }
}
public class B
{
public A Property2 { get; set; }
}
要在 B 的实例上使用模式匹配,您之前必须......
bool isFoo = bb switch
{
{ Property2 : { Property1: "foo"} } => true,
_ => false
};
...但是现在您可以通过以下方式更简单地完成此操作...
bool now = bb switch
{
{ Property2.Property1 : "foo" } => true,
_ => false
};
Lambda 改进
自动推断 lambda 的类型是 C# 10.0 中引入的一个很好的简化功能。
Func<string, bool> isFooInC9 = ss => ss == "foo"; // C# 9.0
var isFooIn10 = (string ss) => ss == "foo"; // C# 10.0
Lambdas 也可以在其上定义属性,因此......
[HttpGet("/")] Foo GetFoo() => new(Id: 0, Name: "Name");
app.MapAction((Func<Foo>)GetFoo);
...你现在可以写...
app.MapAction([HttpGet("/")] () => new Foo(Id: 0, Name: "Name"));
现在还可以明确指定 lambda 的返回类型...
var foo = () => 1; // foo is type Func<Int32>
var bar = () => (long) 1; // bar is type Func<Int64>
还有一些其他改进,包括直接 lambda 调用和增强的过载解析,可以在lambda 改进提案中进行审查。
调用者参数表达式
现在您可以通过以下方式获取字符串形式的表达式...
// Writes "(1 == 2 ? true : false) is False" to the console.
Print((1 == 2 ? true : false));
static void Print(
bool result,
[CallerArgumentExpression("result")] string expr = default
)
{
Console.WriteLine($"({expr}) is {result}");
}
记录结构
C# 9.0 中引入的记录类非常有用,因为它可以更轻松地创建线程安全的不可变对象(通过使用 init 关键字而不是 set 关键字)。记录类的另一个好处是,编译器会创建一些类似值的内置方法,例如:
- 相等运算符,
- 基于属性值的数字哈希码(因此您可以使用基于哈希的集合中的记录进行快速查找),以及
- 将记录属性的值转换为字符串的方法。
记录结构体也提供编译器生成的内置记录函数(不同于普通结构体,后者仅生成 getter 和 setter)。然而,记录类是引用类型,因此该类型的实例会在堆中分配。另一方面,记录结构体是值类型,因此会在栈中分配。这意味着记录结构体可以被高效地操作。
所以现在我们可以用一行代码创建一个不可变的记录结构......
public readonly record struct Foo(string Fiz, string Faz);
...并使用它...
var foo1 = new Foo { Fiz = "Fizzy", Faz = "Fazzy" };
var foo2 = new Foo { Fiz = "Fizzy", Faz = "Fazzy" };
var foo3 = foo1 with { Faz = "Zazzy" };
Console.WriteLine($"{foo1}"); // Foo { Fiz = Fizzy, Faz = Fazzy }
Console.WriteLine($"{foo2}"); // Foo { Fiz = Fizzy, Faz = Fazzy }
Console.WriteLine($"{foo3}"); // Foo { Fiz = Fizzy, Faz = Zazzy }
Console.WriteLine($"{foo1.GetHashCode()}"); // 129686175
Console.WriteLine($"{foo2.GetHashCode()}"); // 129686175
Console.WriteLine($"{foo3.GetHashCode()}"); // 784730341
Console.WriteLine($"{(foo1 == foo2)}"); // True
Console.WriteLine($"{(foo1 == foo3)}"); // False
增量生成器
.NET 5 中引入了源生成器,它允许……
C# 开发人员可以检查用户代码并生成可添加到编译中的新 C# 源文件。这通过一种我们称之为“源生成器”的新组件来实现。(C# 源生成器简介)
增量生成器是一项新功能,允许生成源代码、工件和嵌入式文件。可以实现使用缓存数据执行进一步转换的管道,从而提高性能。
我承认我并不完全了解如何使用此功能,我期待 .NET Conf 提供一些简单的解释和示例。
改进的明确分配分析
明确赋值分析是编译器进行的静态分析,以确保变量在使用前已赋值。C 和 C++ 没有此功能,但 Java 有。
之前有一些有趣的情况会报错,但现在在 C# 10.0 中已经解决了。这些情况比较晦涩,但如果你真的感兴趣,可以深入研究一下。
结论
因此,C# 10.0 提出了一些不错的简化方案。他们正在根据开发者的反馈,持续改进模式匹配和记录等功能,我认为这是件好事。C# 甚至可能再存活 20 年!