我们很高兴地宣布 C# 10 作为 .NET 6 和 Visual Studio 2022 的一部分已经发布了。在这篇文章中,我们将介绍 C# 10 的许多新功能,这些功能使您的代码更漂亮、更具表现力和更快 .

阅读 Visual Studio 2022 公告.NET 6 公告 以了解更多信息,包括如何安装。

全局和隐式usings

using 指令简化了您使用命名空间的方式。 C# 10 包括一个新的全局 using 指令和隐式 usings,以减少您需要在每个文件顶部指定的 usings 数量。

全局using指令

如果关键字 global 出现在 using 指令之前,则 using 适用于整个项目:

global using System;

您可以在全局 using 指令中使用 using 的任何功能。 例如,添加静态导入类型并使该类型的成员和嵌套类型在整个项目中可用。 如果您在 using 指令中使用别名,该别名也会影响您的整个项目:

global using static System.Console;
global using Env = System.Environment;

您可以将全局使用放在任何 .cs 文件中,包括 Program.cs 或专门命名的文件,如 globalusings.cs。 全局usings的范围是当前编译,一般对应当前项目。

有关详细信息,请参阅 全局 using 指令

隐式usings

隐式usings功能会自动为您正在构建的项目类型添加通用的全局using指令。 要启用隐式usings,请在 .csproj 文件中设置 ImplicitUsings 属性:

<PropertyGroup>
<!-- Other properties like OutputType and TargetFramework -->
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

在新的 .NET 6 模板中启用了隐式usings。 在此博客文章中阅读有关 .NET 6 模板更改的更多信息。

一些特定全局 using 指令集取决于您正在构建的应用程序的类型。 例如,控制台应用程序或类库的隐式usings不同于 ASP.NET 应用程序的隐式usings。

有关详细信息,请参阅此隐式usings文章。

Combining using功能

文件顶部的传统 using 指令、全局 using 指令和隐式 using 可以很好地协同工作。 隐式using允许您在项目文件中包含适合您正在构建的项目类型的 .NET 命名空间。 全局 using 指令允许您包含其他命名空间,以使它们在整个项目中可用。 代码文件顶部的 using 指令允许您包含项目中仅少数文件使用的命名空间。

无论它们是如何定义的,额外的 using 指令都会增加名称解析中出现歧义的可能性。 如果遇到这种情况,请考虑添加别名或减少要导入的命名空间的数量。 例如,您可以将全局 using 指令替换为文件子集顶部的显式 using 指令。

如果您需要删除通过隐式 usings 包含的命名空间,您可以在项目文件中指定它们:

<ItemGroup>
<Using Remove="System.Threading.Tasks" />
</ItemGroup>

您还可以添加命名空间,就像它们是全局 using 指令一样,您可以将 Using 项添加到项目文件中,例如:

<ItemGroup>
<Using Include="System.IO.Pipes" />
</ItemGroup>

文件范围的命名空间

许多文件包含单个命名空间的代码。 从 C# 10 开始,您可以将命名空间作为语句包含在内,后跟分号且不带花括号:

namespace MyCompany.MyNamespace;

class MyClass // Note: no indentation
{ ... }

他简化了代码并删除了嵌套级别。 只允许一个文件范围的命名空间声明,并且它必须在声明任何类型之前出现。

有关文件范围命名空间的更多信息,请参阅命名空间关键字文章。

对 lambda 表达式和方法组的改进

我们对 lambda 的语法 和类型进行了多项改进。 我们预计这些将广泛有用,并且驱动方案之一是使 ASP.NET Minimal API 更加简单。

lambda 的自然类型

Lambda 表达式现在有时具有“自然”类型。 这意味着编译器通常可以推断出 lambda 表达式的类型。

到目前为止,必须将 lambda 表达式转换为委托或表达式类型。 在大多数情况下,您会在 BCL 中使用重载的 Func<...> 或 Action<...> 委托类型之一:

Func<string, int> parse = (string s) => int.Parse(s);

但是,从 C# 10 开始,如果 lambda 没有这样的“目标类型”,我们将尝试为您计算一个:

var parse = (string s) => int.Parse(s);

您可以在您最喜欢的编辑器中将鼠标悬停在 var parse 上,然后查看类型仍然是 Func<string, int>。 一般来说,编译器将使用可用的 Func 或 Action 委托(如果存在合适的委托)。 否则,它将合成一个委托类型(例如,当您有 ref 参数或有大量参数时)。

并非所有 lambda 表达式都有自然类型——有些只是没有足够的类型信息。 例如,放弃参数类型将使编译器无法决定使用哪种委托类型:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

lambda 的自然类型意味着它们可以分配给较弱的类型,例如 object 或 Delegate:

object parse = (string s) => int.Parse(s);   // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>

当涉及到表达式树时,我们结合了“目标”和“自然”类型。 如果目标类型是 LambdaExpression 或非泛型 Expression(所有表达式树的基类型)并且 lambda 具有自然委托类型 D,我们将改为生成 Expression:

LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>

方法组的自然类型

方法组(即没有参数列表的方法名称)现在有时也具有自然类型。 您始终能够将方法组转换为兼容的委托类型:

Func<int> read = Console.Read;
Action<string> write = Console.Write;

现在,如果方法组只有一个重载,它将具有自然类型:

var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose

lambda 的返回类型

在前面的示例中,lambda 表达式的返回类型是显而易见的,并被推断出来的。 情况并非总是如此:

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type

在 C# 10 中,您可以在 lambda 表达式上指定显式返回类型,就像在方法或本地函数上一样。 返回类型在参数之前。 当你指定一个显式的返回类型时,参数必须用括号括起来,这样编译器或其他开发人员不会太混淆:

var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>

lambda 上的属性

从 C# 10 开始,您可以将属性放在 lambda 表达式上,就像对方法和本地函数一样。 当有属性时,lambda 的参数列表必须用括号括起来:

Func<string, int> parse = [Example(1)] (s) => int.Parse(s);
var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";

就像本地函数一样,如果属性在 AttributeTargets.Method 上有效,则可以将属性应用于 lambda。

Lambda 的调用方式与方法和本地函数不同,因此在调用 lambda 时属性没有任何影响。 但是,lambdas 上的属性对于代码分析仍然有用,并且可以通过反射发现它们。

structs的改进

C# 10 为structs引入了功能,可在structs(结构)和类之间提供更好的奇偶性。这些新功能包括无参数构造函数、字段初始值设定项、记录结构和 with 表达式。

无参数结构构造函数和字段初始值设定项

在 C# 10 之前,每个结构都有一个隐式的公共无参数构造函数,该构造函数将结构的字段设置为默认值。 在结构上创建无参数构造函数是错误的。

从 C# 10 开始,您可以包含自己的无参数结构构造函数。 如果您不提供,则将提供隐式无参数构造函数以将所有字段设置为默认值。 您在结构中创建的无参数构造函数必须是公共的并且不能是部分的:

public struct Address
{
public Address()
{
City = "<unknown>";
}
public string City { get; init; }
}

您可以如上所述在无参数构造函数中初始化字段,也可以通过字段或属性初始化程序初始化它们:

public struct Address
{
public string City { get; init; } = "<unknown>";
}

通过默认创建或作为数组分配的一部分创建的结构会忽略显式无参数构造函数,并始终将结构成员设置为其默认值。 有关结构中无参数构造函数的更多信息,请参阅结构类型。

Record structs

从 C# 10 开始,现在可以使用record struct 定义 record。 这些类似于 C# 9 中引入的record 类:

public record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}

您可以继续使用record定义记录类,也可以使用record类来清楚地说明。

结构已经具有值相等——当你比较它们时,它是按值。 记录结构添加 IEquatable 支持和 == 运算符。 记录结构提供 IEquatable 的自定义实现以避免反射的性能问题,并且它们包括记录功能,如 ToString() 覆盖。

记录结构可以是位置的,主构造函数隐式声明公共成员:

public record struct Person(string FirstName, string LastName);

主构造函数的参数成为记录结构的公共自动实现属性。 与record类不同,隐式创建的属性是读/写的。 这使得将元组转换为命名类型变得更加容易。 将返回类型从 (string FirstName, string LastName) 之类的元组更改为 Person 的命名类型可以清理您的代码并保证成员名称一致。 声明位置记录结构很容易并保持可变语义。

如果您声明一个与主要构造函数参数同名的属性或字段,则不会合成任何自动属性并使用您的。

要创建不可变的记录结构,请将 readonly 添加到结构(就像您可以添加到任何结构一样)或将 readonly 应用于单个属性。 对象初始化器是可以设置只读属性的构造阶段的一部分。 这只是使用不可变记录结构的一种方法:

var person = new Person { FirstName = "Mads", LastName = "Torgersen"};

public readonly record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}

在本文中了解有关记录结构的更多信息。

Record类中 ToString() 上的密封修饰符

记录类也得到了改进。 从 C# 10 开始,ToString() 方法可以包含 seal 修饰符,这会阻止编译器为任何派生记录合成 ToString 实现。

在本文中的记录中了解有关 ToString() 的更多信息。

结构和匿名类型的表达式

C# 10 支持所有结构的 with 表达式,包括记录结构,以及匿名类型:

var person2 = person with { LastName = "Kristensen" };

这将返回一个具有新值的新实例。 您可以更新任意数量的值。 您未设置的值将保留与初始实例相同的值。

在本文中了解有关 with 的更多信息

内插字符串改进

当我们在 C# 中添加内插字符串时,我们总觉得在性能和表现力方面,使用该语法可以做更多事情。

内插字符串处理程序

今天,编译器将内插字符串转换为对 string.Format 的调用。 这会导致很多分配——参数的装箱、参数数组的分配,当然还有结果字符串本身。 此外,它在实际插值的含义上没有任何回旋余地。

在 C# 10 中,我们添加了一个库模式,允许 API “接管”对内插字符串参数表达式的处理。 例如,考虑 StringBuilder.Append:

var sb = new StringBuilder();
sb.Append($"Hello {args[0]}, how are you?");

到目前为止,这将使用新分配和计算的字符串调用 Append(string? value) 重载,将其附加到 StringBuilder 的一个块中。 但是,Append 现在有一个新的重载 Append(ref StringBuilder.AppendInterpolatedStringHandler handler),当使用内插字符串作为参数时,它优先于字符串重载。

通常,当您看到SomethingInterpolatedStringHandler 形式的参数类型时,API 作者在幕后做了一些工作,以更恰当地处理插值字符串以满足其目的。 在我们的 Append 示例中,字符串“Hello”、args[0] 和“,how are you?” 将单独附加到 StringBuilder 中,这样效率更高且结果相同。

有时您只想在特定条件下完成构建字符串的工作。 一个例子是 Debug.Assert:

Debug.Assert(condition, $"{SomethingExpensiveHappensHere()}");

在大多数情况下,条件为真,第二个参数未使用。 但是,每次调用都会计算所有参数,从而不必要地减慢执行速度。 Debug.Assert 现在有一个带有自定义插值字符串构建器的重载,它确保第二个参数甚至不被评估,除非条件为假。

最后,这是一个在给定调用中实际更改字符串插值行为的示例: String.Create() 允许您指定 IFormatProvider 用于格式化插值字符串参数本身的洞中的表达式:

String.Create(CultureInfo.InvariantCulture, $"The result is {result}");

您可以在本文和有关创建自定义处理程序的本教程中了解有关内插字符串处理程序的更多信息。

常量内插字符串

如果内插字符串的所有洞都是常量字符串,那么生成的字符串现在也是常量。 这使您可以在更多地方使用字符串插值语法,例如属性:

[Obsolete($"Call {nameof(Discard)} instead")]

请注意,必须用常量字符串填充洞。 其他类型,如数字或日期值,不能使用,因为它们对文化敏感,并且不能在编译时计算。

其他改进

C# 10 对整个语言进行了许多较小的改进。 其中一些只是使 C# 以您期望的方式工作。

在解构中混合声明和变量

在 C# 10 之前,解构要求所有变量都是新的,或者所有变量都必须事先声明。 在 C# 10 中,您可以混合:

int x2;
int y2;
(x2, y2) = (0, 1); // Works in C# 9
(var x, var y) = (0, 1); // Works in C# 9
(x2, var y3) = (0, 1); // Works in C# 10 onwards

在有关解构的文章中了解更多信息。

改进的明确分配

如果您使用尚未明确分配的值,C# 会产生错误。 C# 10 可以更好地理解您的代码并且产生更少的虚假错误。 这些相同的改进还意味着您将看到更少的针对空引用的虚假错误和警告。

在 C# 10 中的新增功能文章中了解有关 C# 确定赋值的更多信息。

扩展的属性模式

C# 10 添加了扩展属性模式,以便更轻松地访问模式中的嵌套属性值。 例如,如果我们在上面的 Person 记录中添加一个地址,我们可以通过以下两种方式进行模式匹配:

object obj = new Person
{
FirstName = "Kathleen",
LastName = "Dollard",
Address = new Address { City = "Seattle" }
}; if (obj is Person { Address: { City: "Seattle" } })
Console.WriteLine("Seattle"); if (obj is Person { Address.City: "Seattle" }) // Extended property pattern
Console.WriteLine("Seattle");

扩展属性模式简化了代码并使其更易于阅读,尤其是在匹配多个属性时。

模式匹配文章中了解有关扩展属性模式的更多信息。

调用者表达式属性

CallerArgumentExpressionAttribute 提供有关方法调用上下文的信息。 与其他 CompilerServices 属性一样,此属性应用于可选参数。 在这种情况下,一个字符串:

void CheckExpression(bool condition,
[CallerArgumentExpression("condition")] string? message = null )
{
Console.WriteLine($"Condition: {message}");
}

传递给 CallerArgumentExpression 的参数名称是不同参数的名称。 作为参数传递给该参数的表达式将包含在字符串中。 例如,

var a = 6;
var b = true;
CheckExpression(true);
CheckExpression(b);
CheckExpression(a > 5); // Output:
// Condition: true
// Condition: b
// Condition: a > 5

ArgumentNullException.ThrowIfNull() 是如何使用此属性的一个很好的示例。 它通过默认提供的值来避免必须传入参数名称:

void MyMethod(object value)
{
ArgumentNullException.ThrowIfNull(value);
}

了解有关 CallerArgumentExpressionAttribute 的更多信息

结束

安装 .NET 6 或 Visual Studio 2022,享受 C# 10,并告诉我们您的想法!

你所不知道的 C# 10新特性的更多相关文章

  1. 你所不知道的 CSS 阴影技巧与细节

    关于 CSS 阴影,之前已经有写过一篇,box-shadow 与 filter:drop-shadow 详解及奇技淫巧,介绍了一些关于 box-shadow 的用法. 最近一个新的项目,CSS-Ins ...

  2. 你所不知道的 CSS 阴影技巧与细节 滚动视差?CSS 不在话下 神奇的选择器 :focus-within 当角色转换为面试官之后 NPOI 教程 - 3.2 打印相关设置 前端XSS相关整理 委托入门案例

    你所不知道的 CSS 阴影技巧与细节   关于 CSS 阴影,之前已经有写过一篇,box-shadow 与 filter:drop-shadow 详解及奇技淫巧,介绍了一些关于 box-shadow  ...

  3. 你所不知道的库存超限做法 服务器一般达到多少qps比较好[转] JAVA格物致知基础篇:你所不知道的返回码 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading) EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public? 藏在正则表达式里的陷阱 两道面试题,带你解析Java类加载机制

    你所不知道的库存超限做法 在互联网企业中,限购的做法,多种多样,有的别出心裁,有的因循守旧,但是种种做法皆想达到的目的,无外乎几种,商品卖的完,系统抗的住,库存不超限.虽然短短数语,却有着说不完,道不 ...

  4. 你所不知道的setTimeout

    JavaScript提供定时执行代码的功能,叫做定时器(timer),主要由setTimeout()和setInterval()这两个函数来完成.它们向任务队列添加定时任务.初始接触它的人都觉得好简单 ...

  5. 你所不知道的五件事情--java.util.concurrent(第二部分)

    这是Ted Neward在IBM developerWorks中5 things系列文章中的一篇,仍然讲述了关于Java并发集合API的一些应用窍门,值得大家学习.(2010.06.17最后更新) 摘 ...

  6. 你所不知道的C++

    C++与C的不同 C++从诞生之初就号称和C是兼容的,正是这种兼容,使C++得以迅猛发展,然而也正是这种兼容,让C++背上了沉重的历史包袱.且不论其利弊,让我们来看看C++在兼容C的那部分中,与C语言 ...

  7. 你所不知道的 CSS 滤镜技巧与细节

    承接上一篇你所不知道的 CSS 动画技巧与细节,本文主要介绍 CSS 滤镜的不常用用法,希望能给读者带来一些干货! OK,下面直接进入正文.本文所描述的滤镜,指的是 CSS3 出来后的滤镜,不是 IE ...

  8. 关于setTimeout()你所不知道的地方,详解setTimeout()

    关于setTimeout()你所不知道的地方,详解setTimeout() 前言:看了这篇文章,1.注意setTimeout引用的是全部变量还是局部变量了,当直接调用外部函数方法时,实际上函数内部的变 ...

  9. 你所不知道的html5与html中的那些事(五)——web图像

    文章简介:       现在的页面,一般都离不开图像,而怎么做才能让我们的页面中的图像加载的又快又好呢?在优化页面速度的时候还有什么事是你所不知道的呢?     下面看看今天我为大家带来了哪些关于we ...

随机推荐

  1. Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  2. Android官方文档翻译 五 1.3Building a Simple User Interface

    Building a Simple User Interface 创建一个简单的用户界面 This lesson teaches you to 这节课将教给你: Create a Linear Lay ...

  3. JVM完整详解:内存分配+运行原理+回收算法+GC参数等

    不管是BAT面试,还是工作实践中的JVM调优以及参数设置,或者内存溢出检测等,都需要涉及到Java虚拟机的内存模型.内存分配,以及回收算法机制等,这些都是必考.必会技能. JVM内存模型 JVM内存模 ...

  4. 【刷题-LeetCode】148 Sort List

    Sort List Sort a linked list in O(n log n) time using constant space complexity. Example 1: Input: 4 ...

  5. 【采坑小计】prometheus的remote write协议遇到的问题

    没有读懂源码以前,无脑试错总是效率很低的! 1.thanos receiver报store locally for endpoint : conflict 接口返回的日志: store locally ...

  6. gin中绑定uri

    package main import ( "github.com/gin-gonic/gin" "net/http" ) type Person struct ...

  7. 回顾 Flutter 2021 重要时刻,奉上虎年红包封面喜迎新年!

    2021 年,Flutter 正式进入 2.x 系列的正式版发布,年初的 Flutter 2 的发布 打开了一个新的"格局",为 Flutter 的加入了第五大特色--「可移植性」 ...

  8. Java 中对象锁和类锁的区别? 关键字 Synchronized的用法?

    一  对象锁和类锁的关系 /* * 对象锁和[类锁] 全局锁的关系? 对象锁是用于对象实例方法,或者一个对象实例上的 this 类锁是用于类的静态方法或者一个类的class对象上的. Ag.class ...

  9. jsp 中 include指令 用法, <%@ include file="..."%> 和 <jsp:include page="..." flush="true" />的区别?

    原文链接https://blog.csdn.net/u012187452/article/details/51779052 1. 什么是jsp 文件? 个人理解.  jsp 是一个容器,可以将我们编写 ...

  10. CSS 3D的魅力

    用户1093975发表于Web项目聚集地订阅 151 在这篇文章中: 前言: demo1 demo2 结语: 本文介绍了CSS来实现3D效果,并且有详细代码和解释.建议大家只字不差的阅读.本文的作者是 ...