bit1618c 功能简介

With the upcoming release of .NET 5 — the release which unifies the .NET runtimes — Microsoft recently announced the features that will be included in C# 9. With the final Preview version of C# 9 being announced today, it’s not far off.

随着即将发布的统一.NET运行时的.NET 5版本, Microsoft最近宣布了C#9中将包含的功能。今天宣布了C#9的最终预览版,相距不远。

In this article, we’ll take a look at the new features in C# 9, and how they can be used to improve your code. We’ll focus on the major features first, and discuss some of the smaller companion features towards the end. If you want to dive deeper, take a look at the Language Feature Status document in the Roslyn repository!

在本文中,我们将介绍C#9中的新功能以及如何使用它们来改进您的代码。 我们将首先关注主要功能,并在最后讨论一些较小的辅助功能。 如果您想更深入,请查看Roslyn存储库中的“ 语言功能状态”文档!

1.记录 (1. Records)

Records bridge the gap that currently exists between class and struct types. Classes are more efficient to pass around, but their equality is determined by their underlying reference, rather than the values of its members. Structs get value semantics when determining equality, but have to be copied when passed around.

记录弥合了classstruct类型之间当前存在的鸿沟。 类的传递效率更高,但它们的相等性由其基础引用而不是其成员的值决定。 结构在确定相等性时会获得值语义,但在传递时必须复制。

Because in most applications objects tend to get passed around, this effectively means that you more often than not end up implementing a class and overriding the default equality behaviour, even though value semantics would be desirable.

因为在大多数应用程序中对象往往会被传递,所以这实际上意味着您常常会最终实现一个class并覆盖默认的相等行为,即使需要值语义。

Records alleviate this discomfort by having value semantics, while still being passed by reference.

记录通过具有值语义来减轻这种不适,同时仍通过引用传递

Classes that behave as a record are denoted by the record keyword in the type definition:

充当记录的类由类型定义中的record关键字表示:

public record Order
{public int Id { get; init set; }public string Status { get; init set; }public bool IsPaid { get; init set; }
}

One of the key differences between regular classes and records is that records are intended to be immutable; their state never changes. Therefore, each records represents the state of an object at a specific point in time.

常规类和记录之间的主要区别之一是记录旨在保持不变。 他们的状态永远不会改变。 因此,每个记录代表对象在特定时间点的状态

Whenever the object’s state changes, we create a copy of the record, updating the members that have changed, rather than changing the original record directly. We’ll explore how records are updated in the next section.

每当对象状态更改时,我们都会创建记录的副本,更新已更改的成员,而不是直接更改原始记录。 在下一节中,我们将探讨如何更新记录。

更新记录 (Updating Records)

Records are intended to be immutable. Instead of mutating their data, you create a new instance, but with different values. This is where the with expression comes in.

记录旨在保持不变。 您无需更改其数据,而是创建一个具有不同值的新实例。 这是with表达式出现的地方。

For example, consider we have an Order record whose status changes to “Delivered”, instead of updating the Status property of our order object, we’d create a new instance of the Order type, copying the values from our original order, but updating the status:

例如,假设我们有一个Order记录,其状态更改为“已交付”,我们将创建Order类型的新实例,而不是更新订单对象的Status属性,从原始订单中复制值,然后更新状态:

var updatedOrder = order with { Status = "Delivered" };

Unlike with regular reference types, the status of the order object isn’t changed, only that of the newly created updatedOrder — any code operating on the order object will still observe its previous value.

与常规引用类型不同, order对象的状态不会更改,只有新创建的updatedOrder的状态会更改-在order对象上运行的任何代码仍将观察其先前的值。

That way, each record represents an object’s state at a specific point in time. Whenever a value changes, a new record is created. This immutability allows methods that operate on a record to have the guarantee that the data it operates on will never change mid-execution.

这样,每条记录都代表对象在特定时间点的状态。 每当值更改时,都会创建一个新记录。 这种不变性使对记录进行操作的方法可以保证对其进行操作的数据永远不会在执行过程中更改。

平等 (Equality)

By default, regular classes are considered equal when they share the same underlying reference. This means that, even if you create two instances of the same class, holding the exact same values, they will not be considered equal.

默认情况下,当常规类共享相同的基础reference时,它们被视为相等。 这意味着,即使您创建两个完全相同的类的实例,并且它们拥有完全相同的值,也不会将它们视为相等。

This behaviour changes when you declare your class as a record. Rather than comparing the object’s reference, records are compared by value. This means that two different objects holding the same values will be considered equal, or as Mads Torgersen puts it:

当您将类声明为记录时,此行为将更改。 记录不是通过比较对象的引用而是通过值进行比较。 这意味着具有相同值的两个不同对象将被视为相等,或者如Mads Torgersen所说:

[Records] are defined not by their identity, but by their contents.

[记录]的定义不是根据其标识,而是根据其内容。

为什么我们需要记录? (Why do we need records?)

There are a number of reasons immutable types in the form of records are incredibly useful. I could probably write an entire article about why immutable objects are great for overall software design, but for this article, we’ll only take a look at the type of classes that benefit the most from being a record.

有很多原因导致记录形式的不可变类型非常有用。 我可能会写一整篇文章,说明为什么不可变对象对于整体软件设计非常有用,但是对于本文,我们只会看一下从记录中受益最大的类的类型。

数据传输和事件 (Data Transfer and Events)

Whether we’re interacting with an external service by sending or receiving data, or simply transferring data between layers within our own application, Data Transfer Objects are a perfect example of where records shine.

无论我们是通过发送或接收数据与外部服务进行交互,还是在我们自己的应用程序中的各个层之间简单地传输数据,数据传输对象都是记录闪耀的绝佳示例。

The very purpose of a data transfer object is to carry data from one part of the code to another, which makes them inherently immutable. Let’s assume we receive an event from an external system notifying us that an order has been paid for. We’re not going to make any changes to that event — if we did, it would no longer accurately represent the event that occurred.

数据传输对象的真正目的是将数据从代码的一部分传送到另一部分,这使它们本质上是不变的。 假设我们从外部系统收到一个事件,通知我们订单已经付款。 我们不会对该事件进行任何更改-如果这样做,它将不再准确地表示发生的事件。

Instead, we could declare the event as a record and pass it to our handlers. Each of our handlers subsequently have the guarantee that the event is represented exactly as it occurred, guaranteeing there are no side effects.

相反,我们可以将事件声明为record ,并将其传递给我们的处理程序。 随后,我们的每个处理程序都保证该事件的表示形式与发生的事件完全相同,从而确保没有副作用。

2.仅初始化属性 (2. Init-only Properties)

We’ve previously explored records, which represent immutable data that is copied, rather than updated over time. Having built-in immutable data types is a great addition to the language, but how do we instantiate these objects? Will we have to pass all of its data as constructor arguments? Not quite. That’s where init-only properties come in.

我们之前曾浏览过记录,这些记录代表了不变的数据,这些数据是被复制而不是随时间更新的。 具有内置的不可变数据类型是该语言的重要补充,但是如何实例化这些对象? 我们是否必须将其所有数据作为构造函数参数传递? 不完全的。 那就是init-only属性的来源。

As the name implies, init-only properties can only be set when the object is initialized. Their syntax is succinct; init is essentially added as a modifier to property setters:

顾名思义,仅初始化属性只能在初始化对象时设置。 它们的语法简洁。 init本质上是作为属性设置器的修饰符添加的:

public record Order
{public int Id { get; init set; }public string Status { get; init set; }public bool IsPaid { get; init set; }
}

For the sharp-eyed among us, this is the exact same type I used to exemplify records in the previous section.

对于我们当中敏锐的眼睛,这与我在上一节中示例记录的类型完全相同。

A property whose setter is marked as init can only be assigned when the object is instantiated:

仅当实例化对象时,才能分配其设置方法标记为init的属性:

var order = new Order
{Id = 100,Status = "Created",IsPaid = false
};

Any attempt to subsequently change the value of one of these properties, e.g. order.Status = "Shipped" will result in a compiler error.

随后尝试更改这些属性之一的值的任何尝试,例如order.Status = "Shipped"都会导致编译器错误。

我们为什么需要这些? (Why do we need these?)

As I alluded to in the previous section, the instantiation of immutable objects can not currently be expressed in C# in a tersely manner. Previously, object initialiser syntax (allowing you to set properties when instantiating an object, e.g. new Order { <property assignments> }; ) required the property’s setters to be accessible to the caller, either by declaring them as public or internal .

正如我在上一节中提到的那样,不可变对象的实例当前无法以简洁的方式在C#中表示。 以前,对象初始化程序语法(允许您在实例化对象时设置属性,例如new Order { <property assignments> }; )要求调用者可以访问属性的设置器,方法是将它们声明为publicinternal

Naturally, when we create immutable types and want to shield our class against external modification, we don’t want to leave our setters exposed.

自然,当我们创建不可变类型并希望保护我们的类免受外部修改时,我们不想让设置者暴露。

Declaring immutable types has traditionally been somewhat of a hassle. Init-only properties alleviate some of this pain by allowing you to instantiate immutable objects more expressively, while at the same time not over-exposing your object’s members.

声明不可变类型在传统上有点麻烦。 仅初始化属性通过允许您更富有表现力地实例化不可变对象,同时又不过度暴露对象的成员,减轻了某些痛苦。

Paweł Czerwiński on 帕维尔Czerwiński上UnsplashUnsplash

3.改进的模式匹配 (3. Improved Pattern Matching)

Ever since C# 7, Microsoft has been gradually improving the pattern matching support in C#. Since the data our applications process are more and more defined by their shape (i.e. their data), rather than arbitrary implementation details such as their CLR type, the increased ability to declare logical patterns to determine behaviour is a more than welcome addition to the language.

从C#7开始,Microsoft一直在逐步改善C#中的模式匹配支持。 由于我们的应用程序处理的数据越来越多地由它们的形状 (即它们的数据)定义,而不是由诸如CLR类型之类的任意实现细节定义,因此增加了声明逻辑模式以确定行为的能力,这是对该语言的一种令人欢迎的添加。

In the previous version of C#, a new switch syntax was introduced which allowed us to perform pattern matching on a type’s properties:

在C#的先前版本中,引入了新的switch语法,该语法使我们能够对类型的属性执行模式匹配:

static string Display(object o) => o switch
{Point { X: 0, Y: 0 }         => "origin",Point { X: var x, Y: var y } => $"({x}, {y})",_                            => "unknown"
};

Let’s say we want to use pattern matching to determine the ticket price for an individual visiting the zoo. Depending on their age, they either pay full price, or get a discount. In C# 8, you could express this as:

假设我们要使用模式匹配来确定参观动物园的个人的门票价格。 根据他们的年龄,他们要么全额付款,要么获得折扣。 在C#8中,您可以这样表示:

static int CalculateTicketPrice(Person person)
{return person switch{var p when p.Age <= 12 => 5,var p when p.Age > 12 && p.Age <= 60 => 15,var p when p.Age > 60 => 10};
}

Note, there is quite a bit of noise in our code, even though our switch only really touches the p.Age property of our class.

请注意,即使我们的开关仅真正触及类的p.Age属性,我们的代码中还是有很多杂音。

With C# 9, switch expressions can now also be relational. This means that they can operate on relational operators (such as < > >= etc), which means we can express our switch arms in relation to the p.Age

使用C#9,开关表达式现在也可以是关系式的。 这意味着它们可以对关系运算符进行操作(例如< > >=等),这意味着我们可以表达与p.Age相关的切换臂。

static int CalculateTicketPrice(Person person)
{return person when person.Age switch{<= 12 => 5,> 12 and <= 60 => 15,> 60 => 10};
}

Note the above snippet also includes the and logical operator. In addition to using relational operators, you can form switch arms using logical expressions such as and , not and or — this allows you to succinctly declare intervals.

请注意,以上代码段还包含and逻辑运算符。 除了使用关系运算符外,您还可以使用逻辑表达式(例如andnotor来形成开关臂,这使您可以简洁地声明间隔。

In addition, you’re now also able to use pattern matching in if statements, to determine if an object satisfies a specific “shape”. For example, let’s say we have a list of Vector3 objects, and we want to print all vectors whose X coordinate equals 1. Using pattern matching, this could be expressed as:

此外,您现在还可以在if语句中使用模式匹配,以确定对象是否满足特定的“形状”。 例如,假设我们有一个Vector3对象的列表,我们想打印X坐标等于1的所有矢量。使用模式匹配,可以表示为:

foreach (var v in vectors){if (v is { X: 1 })Console.WriteLine(v);}

Note that this also implicitly performs a null check through the is keyword. Not particularly useful when dealing with value types such as Vector3 , but you get the point.

请注意,这还会通过is关键字隐式执行null检查。 在处理诸如Vector3类的值类型时,它并不是特别有用,但是您明白了。

我们为什么需要这个? (Why do we need this?)

In modern day software engineering, you rarely ever get to work on your own walled garden anymore. Chances are, your system interoperates with external systems, who may be following different rules. This means that the ability to process data based on its shape, rather than an arbitrary implementation detail like its CLR type, becomes increasingly important.

在现代软件工程中,您几乎再也无法在自己的围墙花园中工作了。 您的系统很可能与可能遵循不同规则的外部系统进行互操作。 这意味着根据数据的形状而不是像CLR类型的任意实现细节处理数据的能力变得越来越重要。

All of this is purely syntactic sugar; the above switch expression gets compiled into a bunch of if statements:

所有这些纯粹是语法糖; 上面的switch表达式被编译为一堆if语句:

private static int CalculateTicketPrice(Person person)
{int result;if (person.Age > 12){if (person.Age <= 12 || person.Age > 60){if (person.Age <= 60){throw new SwitchExpressionException(person);}result = 10;}else{result = 15;}}else{result = 5;}return result;
}

But in this case, the sugar definitely makes our code sweeter. If you had to choose, would you rather write a four lines of code switch expression, or the intricate if else construct above?

但是在这种情况下,糖肯定会使我们的代码更甜美。 如果必须选择,是要编写四行代码switch表达式,还是上面复杂的if else语句?

We can expect more and more patterns be supported in future versions of C#. You can find more details and examples in the Pattern Matching section of the official C# documentation. It pays to get acquainted with them, as they’ll allow you to write your logic trees far more succinctly.

我们可以预期,将来的C#版本将支持越来越多的模式。 您可以在官方C#文档的模式匹配部分中找到更多详细信息和示例。 熟悉它们是值得的,因为它们使您可以更简洁地编写逻辑树。

Photo by Bruno Ramos Lara on Unsplash
照片由Bruno Ramos Lara在Unsplash上拍摄

4.目标打字的改进 (4. Target Typing Improvements)

Target typing is the concept that allows the compiler to infer the type, for instance, when assigning null :

目标类型化是一种概念,它使编译器可以推断类型,例如,在分配null

string s = null;

Strictly speaking, this is a (string) null assignment, but because the left hand side of the assignment already declares the variable’s type as a string , you don’t need to manually cast it.

严格来说,这是一个(string) null赋值,但是由于赋值的左侧已经将变量的类型声明为string ,因此您无需手动对其进行强制转换。

There are a handful of these cases currently in C# where you can elide the type declaration: null assignments, lambda expressions, and to a lesser extent, array initializers.

当前在C#中有几种情况可以忽略类型声明: null赋值,lambda表达式,以及较小范围内的数组初始值设定项。

Target typing is being expanded on in C# 9, which should make your life as a programmer ever so slightly better:

目标类型正在C#9中扩展,这将使您作为程序员的生活变得稍微好一点:

目标键入的new表达式 (Target Typed new expressions)

Barring implicit array initializers, the syntax for creating a new object always involved new T() where T is the type you’re initializing.

除隐式数组初始化程序外,用于创建新对象的语法始终涉及new T() ,其中T是您要初始化的类型。

With C# 9, you can omit the type from the new expression as long as the left hand side of the assignment dictates the type. Therefore, the following assignment will be legal:

使用C#9,只要赋值的左侧指示类型,就可以new表达式中省略类型。 因此,以下分配将是合法的:

Vector3 vec = (1, 2, 3);

目标类型和共享类型 (Target Typing and Shared Types)

Currently, if the return values of a method don’t match, the compiler issues an error. Let’s say we have the following method:

当前,如果方法的返回值不匹配,则编译器将发出错误。 假设我们有以下方法:

public IEnumerable<int> GetNumbers(bool even)
{return even ? new List<int> {2, 4, 6} : new []{1, 3, 5};
}

Even though both List<int> and int[] are IEnumerable<int> , the compiler still issues a warning. You can get around this by casting either the array or the List<int> to IEnumerable<int> :

即使List<int>int[]都是IEnumerable<int> ,编译器仍然会发出警告。 您可以通过将数组或List<int>强制转换为IEnumerable<int>

public IEnumerable<int> GetNumbers(bool even)
{return even ? (IEnumerable<int>) new List<int> {2, 4, 6} : new []{1, 3, 5};
}

But in C# 9, you will no longer have to. As long as the two operands have a shared type (in this case, IEnumerable<int> ), your code will compile just fine.

但是在C#9中,您将不再需要。 只要两个操作数具有共享类型(在本例中为IEnumerable<int> ),您的代码就可以正常编译。

我们为什么需要这个? (Why do we need this?)

These language features are more of a quality of life improvement. Being able to forego the type when creating a new object is neat, but it’s not drastically going to change things.

这些语言功能可以提高生活质量。 能够在创建新对象时放弃类型是一件很整洁的事情,但是并不能彻底改变它。

I personally think it’s great that the C# language team is putting in the time and effort to push these smaller quality of life changes, too. C# has gradually become a more enjoyable language to program in, and these smaller features are like the icing on the cake.

我个人认为C#语言团队投入大量的时间和精力来推动这些生活质量的改善也很棒。 C#已逐渐成为一种更易于编程的语言,这些较小的功能就像锦上添花。

5.顶级程序 (5. Top-Level Programs)

Though not necessarily a major feature for established codebases, top-level programs allow the programmer to omit the “ceremony” associated with even the simplest C# programs.

尽管不一定是已建立代码库的主要功能,但顶层程序仍允许程序员省略与最简单的C#程序相关的“仪式”。

Before, when creating a new Console Application to simply write “Hello World!” to the Console, you’d be greeted with a bunch of code:

以前,在创建新的控制台应用程序时只需编写“ Hello World!”即可。 到控制台,您会收到一堆代码:

using System;namespace ConsoleApp
{class Program{static void Main(string[] args){Console.WriteLine("Hello World!");}}
}

And this example is already trimmed down to remove the unnecessary using directives you got for free. Experienced programmers know all these constructs: using directives, namespace declarations, and ultimately the class and method that contain the code that prints “Hello World!”.

并且已经精简了此示例,以删除您免费获得的不必要的using指令。 经验丰富的程序员了解所有这些构造: using指令, namespace声明,最终使用包含打印“ Hello World!”的代码的class和方法。

For new programmers however, that’s a lot to grasp. They may be wondering “Why do I need all this code to write a simple Hello World program?”. And that’s a good question, because really, you don’t need all this code just to write Hello World. It’s part of the ceremony that comes with C# and its nominal type system.

但是,对于新程序员来说,要掌握很多东西。 他们可能想知道“为什么我需要所有这些代码才能编写一个简单的Hello World程序?”。 这是一个很好的问题,因为实际上,您不需要所有这些代码即可编写Hello World。 这是C#及其标称类型系统附带的仪式的一部分。

The ceremony is incredibly useful when designing large applications and systems, but when writing something trivial like a Hello World, it’s just unnecessary bloat.

当设计大型应用程序和系统时,该仪式非常有用,但是当编写诸如Hello World之类的琐碎东西时,这仅仅是不必要的膨胀。

Starting with C# 9, you can simply skip the ceremony, and your Hello World can be expressed as:

从C#9开始,您只需跳过仪式,您的Hello World可以表示为:

using System;Console.WriteLine("Hello World!");

So does that mean namespaces and classes are no longer required? Well, not exactly. Even though you don’t have to define a namespace and a class, the compiler still generates them for you. Essentially, top level programs are “just” syntactic sugar.

那么这是否意味着不再需要名称空间和类? 好吧,不完全是。 即使您不必定义名称空间和类,编译器仍会为您生成它们。 本质上,顶级程序是“仅”语法糖。

If you look at the decompiled source code for the above snippet, you’ll see that the compiler takes care of all the ceremony for you:

如果查看以上代码段的反编译源代码,您会看到编译器为您完成了所有的工作:

using System;
using System.Runtime.CompilerServices;// Token: 0x02000002 RID: 2
[CompilerGenerated]
internal static class <Program>$
{// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250private static void <Main>$(string[] args){Console.WriteLine("Hello World!!");}
}

Worth bearing in mind is, that even though it’s not explicit, your code still runs in a static context, by virtue of the static void Main() method.

值得记住的是,即使它不是显式的,您的代码仍然可以通过static void Main()方法在静态上下文中运行。

我们为什么需要这个? (Why do we need this?)

This is a relatively minor feature that makes C# more accessible to novice programmers, in theory. Visual Studio still generates the whole thing, including namespaces and a class definition by default, but you could theoretically write a couple of lines of C# along with the appropriate using directives, and run it.

从理论上讲,这是一个相对较小的功能,使C#更易于新手程序员使用。 Visual Studio仍然会生成整个内容,包括默认情况下的名称空间和类定义,但是理论上您可以编写几行C#以及相应的using指令,然后运行它。

My suspicion is that this is indirectly aimed at projects such as Try .NET. For those cases where you simply want to run a snippet of C#, having top level programs becomes extremely useful.

我怀疑这是间接针对诸如Try .NET的项目的。 对于只想运行C#代码段的情况,拥有顶级程序将变得非常有用。

Why? Well, the simple answer is because using directives cannot be contained in method bodies (they become using statements instead). This poses a problem for running code “dynamically”, because the using directives have to be provided by user-code, but must be included at the top of the file, before the class declaration begins. And as we all know, without proper using directives, the code simply won’t compile.

为什么? 好吧,简单的答案是因为using 指令不能包含在方法主体中(它们变成了using语句)。 这给“动态”运行代码带来了问题,因为using指令必须由用户代码提供,但必须在class声明开始之前包含在文件的顶部。 众所周知,如果没有正确using指令,代码将无法编译。

For scenarios where running arbitrary snippets of C# is desirable, having top level programs is very useful. Simply take the user’s code, along with all their usings, and have the compiler deal with placing them in their proper places.

对于需要运行任意C#代码片段的场景,拥有顶级程序非常有用。 只需将用户的代码及其所有用法一起使用,并让编译器处理将它们放置在适当的位置。

荣誉奖 (Honorable Mentions)

As C# was starting to take its final shape, there are features that didn’t make the cut for this specific release. One of them were simplified argument null checks, which was omitted from the C# 9 at a fairly late stage. I think it’s fairly likely this feature will end up making it into the next version of C#, considering it was originally accepted for C# 9. Therefore, it gets an honorary mention.

随着C#逐渐成形,某些功能并未在该特定版本中发挥作用。 其中之一是简化的参数null检查,这在相当晚的阶段从C#9中省略了。 考虑到它最初是C#9所接受的,我认为该功能很有可能最终使其成为C#的下一版本。因此,它得到了荣誉奖。

简化的空参数检查 (Simplified Null Argument Checks)

The relevance of this feature wholly depends on your code base, and whether or not you extensively implement precondition checks across your code.

此功能的相关性完全取决于您的代码库,以及是否在代码中广泛实施前提条件检查。

When defining a method that accepts a reference type as one of its parameters, you can very often deduce that when a null reference is passed, the function will fail. If you elide checking for null before the method goes about its business, chances are you’ll be met with arguably the most obnoxious exception to deal with: the NullReferenceException

当定义一个接受引用类型作为其参数之一的方法时,通常可以推断出,当传递null引用时,该函数将失败。 如果在该方法开始其业务之前就忽略了null ,则很有可能会遇到最令人讨厌的异常处理: NullReferenceException

To avoid those exceptions, you’ll want to make sure a null reference doesn’t actually make it into the method’s body. A common way to do that is by adding validating the arguments passed to a method, or constructor:

为了避免这些异常,您将要确保null引用实际上并没有进入方法的主体。 一种常见的方法是添加验证传递给方法或构造函数的参数:

public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor, ILoggerFactory loggerFactory)
{if (optionsAccessor == null){throw new ArgumentNullException(nameof(optionsAccessor));}if (loggerFactory == null){throw new ArgumentNullException(nameof(loggerFactory));}// Actual constructor code goes here.
}

The standard .NET libraries are laden with these checks, and chances are, your code is as well.

标准的.NET库中充满了这些检查,并且您的代码也很有可能。

While necessary, these checks make the method’s prologue bloated, as well as slightly monotonous. Fortunately, in C# 9, language support will be introduced to ensure references passed are non-null, using the ! annotation on required parameters:

必要时,这些检查会使方法的序言膨胀,并且略显单调。 幸运的是,在C#9中,将引入语言支持,以确保使用!传递的引用为非null ! 必需参数的注释:

public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor!, ILoggerFactory loggerFactory!)
{// Actual constructor code goes here, having the guarantee that neither optionsAccessor nor loggerFactory are null._options = optionsAccessor.Value;_logger = loggerFactory.CreateLogger<MemoryCache>();
}

The constructor (or any other method) can continue its execution, knowing that the arguments passed are non-null, and can therefore forego the entire null-checking ceremony.

构造函数(或任何其他方法)可以继续执行,知道传递的参数为非null,因此可以放弃整个null检查过程。

静态与运行时检查 (Static vs runtime checking)

In the previous version of C#, opt-in support for non-nullable reference types was introduced. When enabled, the compiler would issue a warning every time null was either passed or assigned to a reference type that wasn’t explicitly marked as nullable.

在C#的早期版本中,引入了对非空引用类型的选择支持 。 启用后,每次传递null或将其分配给未明确标记为可为空的引用类型时,编译器都会发出警告。

Of course, sometimes null was to be expected — e.g. whenever FirstOrDefault doesn’t find a value matching its predicate. You could get around the warning by expressing the return type as, for example a string! . This is called the null-forgiving operator.

当然,有时会预期为null -例如,只要FirstOrDefault未找到与其谓词匹配的值。 您可以通过将返回类型表示为例如string!来避开警告string! 。 这被称为null宽容运算符

While the syntax of the null-forgiving operator and the new style null check are very similar, there are some major differences. First off, the ! for the null check is declared on the parameter, rather than its type.

尽管可以使用null省略运算符的语法和新样式的null检查非常相似,但还是有一些主要区别。 首先, ! 空检查是在参数而不是其类型上声明的。

This is because these null checks do not affect the type system at all. Whenever a nullable context is set, the compiler statically checks at compile time whether any of the code paths may logically lead to a null value being passed, and subsequently issues a warning if that’s the case.

这是因为这些空检查根本不影响类型系统。 每当设置了可为空的上下文时,编译器都会在编译时静态检查是否有任何代码路径在逻辑上可能导致传递了null值,并且在这种情况下随后发出警告。

These new checks are run-time only. They are directly equivalent to:

这些新检查仅在运行时。 它们直接等效于:

if (o == null)     throw new ArgumentNullException(nameof(o));

我们为什么需要这些? (Why do we need these?)

Null references are notorious for being difficult to debug, so the fewer chances of one occurring, the better. While the non-nullable reference types introduced in the previous version of C# provide a very idyllic solution to the null problem, it is not always a practical one.

空引用因难以调试而臭名昭著,因此发生一次的机会越少越好。 虽然在C#的先前版本中引入的不可为空的引用类型为null问题提供了非常理想的解决方案,但它并不总是实用的。

While it may be convenient to enable non-nullable reference types on new projects, it’s far more difficult to retrofit existing projects with the feature. Even more so if your software is directly exposed to external components — e.g. library code.

虽然在新项目中启用非空引用类型可能很方便,但是用该功能改造现有项目要困难得多。 如果您的软件直接暴露于外部组件(例如,库代码),则更是如此。

Therefore, null checking remains a repetitive, but necessary chore. If you take a look at a random selection of code files in the .NET core libraries, you’ll find that many of the public methods exposed contain very similar prologues: null checks. This feature simply lets us write all of those checks with a single character. What’s not to like?

因此, null检查仍然是重复的,但却是必需的琐事。 如果查看.NET核心库中随机选择的代码文件,您会发现public许多public方法都包含非常相似的序言: null检查。 此功能仅使我们可以用单个字符写所有这些支票。 不喜欢什么

结论 (Conclusion)

This is the largest feature set added to C# in years. With the ability to work with data based on its shape, rather than an arbitrary implementation detail like the data’s CLR type, Microsoft solidifies C# as a modern-day, multi-purpose language.

这是多年来添加到C#中的最大功能集。 由于能够根据数据的形状而不是像数据的CLR类型这样的任意实现细节来处理数据,Microsoft将C#巩固为一种现代的多用途语言。

Where TypeScript offers programmers a very versatile and highly valid option when a less-strict typing system is desirable, C# is the king of the hill when it comes to rigidity and reliability. Features such as record types and init-only properties only solidify this position further.

当需要使用不太严格的键入系统时,TypeScript为程序员提供了一个非常通用且高度有效的选择,而在刚性和可靠性方面,C#是最重要的选择。 记录类型和仅初始化属性等功能只会进一步巩固这一地位。

If you have been thinking about giving C# a try, now is the time!

如果您一直在考虑尝试C#,那么现在是时候了!

C#语言存储库 (The C# Language Repository)

If you find the evolution of C# over time as interesting as I do, you might want to check out the CSharpLang repository over on GitHub. In this repository, the C# team and the community discuss proposals for future language features, and you can get a glimpse on what’s coming next.

如果您发现C#随着时间的推移像我一样有趣,那么您可能想在GitHub上查看CSharpLang存储库 。 在此存储库中,C#团队和社区讨论了有关未来语言功能的建议,您可以大致了解下一步的发展。

For example, here’s a list of the proposals of features described in this article:

例如,这是本文描述的功能建议的列表:

  1. Record types

    记录类型

  2. Init-only property setters

    仅初始化属性设置器

  3. Pattern Matching

    模式匹配

  4. Target Typing

    目标打字

  5. Top-Level Programs / Top-Level Statements

    顶级程序/顶级语句

翻译自: https://medium.com/swlh/an-introduction-to-the-new-features-in-c-9-305dc8fb74d2

bit1618c 功能简介


http://www.taodudu.cc/news/show-2813251.html

相关文章:

  • 那个时候我不小心建立了一个间谍应用
  • 人格测试_数据人格
  • 怎么保证自动化脚本没有问题_质量保证将使设计变得自动化
  • 9型人格人口占比数据_数据人格
  • NUIST第十一届程序设计竞赛
  • [usaco6.1.1Postal Vans]
  • 南京信息工程大学第二届程序设计大赛团队赛:L-三元对
  • ACM里的期望和概率问题 从入门到精(入)通(土)
  • ACM里的生成函数初探
  • Java对象数组的初始化
  • JAVA----动态初始化数组的null判断
  • java 数组声明并初始化_Java数组的声明与初始化
  • JAVA一维数组如何初始化
  • java数组初始化的方式,java中初始化数组的方式有几种
  • java数组初始化 new_java 数组初始化
  • 浅谈Java设计之——Java初始化数组(List/Map)时为何要空数组而不是null
  • Java数组初始化长度
  • 初始化一个java空数组_Java 数组的两种初始化方式
  • java数组初始化的方式_java数组初始化方式
  • java数组 初始化_用Java初始化数组
  • java数组不初始化长度_数组之初始化 Java
  • Java中关于数组的初始化方式
  • java数组初始化赋值_Java数组的三种初始化方式
  • java数组初始化为 1_Java Class 1.1数组初始化
  • java定义数组变量初始化为0_java中怎么数组初始化?
  • java类型的数组初始化_java数组初始化详解
  • java 如何初始化数组_java中初始化数组的三种方式分别是什么
  • Java动态初始化数组,元素默认值规则
  • java 初始化数组_java如何实现数组初始化
  • java 初始化数组_Java中数组的初始化

bit1618c 功能简介_c 9中的新功能简介相关推荐

  1. spring 5.x(1)-----Spring Framework 5.x中的新功能

    Spring Framework 5.x中有什么新功能 5.1版中的新功能 一般核心修订 基础设施: 在类路径和模块路径上对JDK 11的无警告支持. 支持Graal原生图像约束(反射,参数名称). ...

  2. drx功能开启后_简单实用!小米手机中这些新功能真香

    小米手机作为国产机热销品牌之一,它除了有好看的外观,还有很多隐藏的实用功能,今天小编就来和大家分享5个小米手机里你不知道的功能. Al电话助理 看到陌生号码时,很多人第一反应就是挂掉,不想接听,但又担 ...

  3. SQLSERVER2014中的新功能

    SQLSERVER2014中的新功能 转载自:http://blog.csdn.net/maco_wang/article/details/22701087 博客人物:maco_wang SQLSER ...

  4. java中怎样定义实数_Java Math 类中的新功能,第 1 部分: 实数

    在这篇由两部分组成的文章中,Elliotte Rusty Harold 与您一起探讨经典 java.lang.Math 类中的"新"功能.第 1 部分主要讨论比较单调的数学函数.第 ...

  5. Windows Server 2012 DHCP 服务器中的新功能:故障转移和策略

    Windows Server 2012 DHCP 服务器中的新功能如下: DHCP 故障转移:此功能提供让两个 DHCP 服务器服务于 同一子网或作用域的 IP 地址和选项配置的能力,前提是 DHCP ...

  6. Java平台,标准版Oracle JDK 9中的新功能

    Java平台,标准版 Oracle JDK 9中的新增功能 版本9 E77563-05 2017年9月 JDK 9中的新功能概述 Java Platform,Standard Edition 9是一个 ...

  7. Java 11中的新功能和API详解系列1

    Java 11中的新功能和API详解系列1 2018.9.27 版权声明:本文为博主chszs的原创文章,未经博主允许不得转载. JDK 11在语言语法方面有一个小改动,增加了相当数量的新API,以及 ...

  8. PhotoZoom Classic 7中的新功能

    众所周知PhotoZoom Classic是家庭使用理想的放大图像软件.目前很多用户还在使用PhotoZoom Classic 6,对于PhotoZoom Classic 7还是有点陌生.其实在6代衍 ...

  9. python keyshot_带你快速了解KeyShot 6中的新功能

    原标题:带你快速了解KeyShot 6中的新功能 经常有朋友问起好用吗?KeyShot在版本完善的同时增加了强大的新功能,加快了渲染速度和创建高质量的视觉效果,体验一种完整工作流与更快的照明功能和扩展 ...

最新文章

  1. 8)排序①排序算法之交换排序[1]冒泡排序法
  2. OSNIT信息收集分析框架OSRFramework
  3. Spring的事务管理1
  4. 什么是 MIME Type
  5. Python之模块与包(下)
  6. HDFS的读/写流程
  7. 大厂HR年底绷不住了:怎么招程序员这么难,尤其搞这项技术的!!
  8. net 自定义表单的设计
  9. 用html5开发本地桌面应用,十个使用HTML5开发的精彩应用
  10. 火狐浏览器不能看网页视频了的解决方法
  11. 猫耳FM导出音频转换为音频格式(m4a/mp3)
  12. C 二维数组存入学生成绩 ,并求平均分,对平均分降序排序
  13. 74LVC245AD技术资料
  14. oeasy教您玩转python - 012 - # 刷新时间
  15. java右移和无符号右移区别_Java 无符号右移与右移运算符的使用介绍
  16. vmware 上网问题解决
  17. 我所认识的BIMRevit
  18. 获取根节点的两种方式
  19. 异常检测:综述(基本都是无监督算法)【时间序列算法:AR/MA/ARMA】【传统机器学习算法:孤独森林、One Class SVM】【深度学习算法:AutoEncoder、LSTM、DeepLog】
  20. Android常用设计模式

热门文章

  1. Linux基础之vim
  2. Karl Guttag:AR眼镜应根据用途来设计,VST并未解决技术难题
  3. 案例分享 | CEVA 使用 TensorFlow Lite 在边缘设备部署语音识别引擎及前端
  4. Spring Security CSRF防御源码分析
  5. 今日头条怎么做发布,如何用今日头条号进行宣传推广?
  6. 求一元二次方的根(虚根求法)
  7. Composite Coloring(思维 数论(筛素数 分解质因数))
  8. java爬取新浪微博带有“展开全文”的完整微博文本
  9. linux mysql stop 报错_mysql linux上安装使用
  10. 触发器(Trigger)