[翻譯]歡迎使用C#9.0

本文由公衆號[開發者精選資訊](微信號:yuantoutiao)翻譯首發,轉載請註明來源ios

C# 9.0 is taking shape, and I’d like to share our thinking on some of the major features we’re adding to this next version of the language.git

C#9.0初具規模,我想就咱們要添加到該語言下一版本中的一些主要功能分享咱們的想法。github

With every new version of C# we strive for greater clarity and simplicity in common coding scenarios, and C# 9.0 is no exception. One particular focus this time is supporting terse and immutable representation of data shapes.express

在C#的每一個新版本中,咱們都在通用編碼方案中力求更加清晰和簡單,C#9.0也不例外。此次的一個特別重點是支持數據類的簡潔和不變數據的表示形式。編程

Let’s dive in!c#

讓咱們開始吧!數組

Init-only properties

僅需初始化的屬性

Object initializers are pretty awesome. They give the client of a type a very flexible and readable format for creating an object, and they are especially great for nested object creation where a whole tree of objects is created in one go. Here’s a simple one:緩存

對象初始化器很是棒。它們爲類型實例提供了一種很是靈活且易於讀取的格式來建立對象,而且特別適合嵌套對象的建立,在該對象中一次性建立了整個對象樹。這是一個簡單的例子:微信

new Person { FirstName = "Scott", LastName = "Hunter" }

Object initializers also free the type author from writing a lot of construction boilerplate – all they have to do is write some properties!數據結構

對象初始值設定可使開發人員免於編寫大量樣板代碼–他們要寫一些屬性!

public class Person { public string FirstName { get; set; } public string LastName { get; set; } }

The one big limitation today is that the properties have to be mutable for object initializers to work: They function by first calling the object’s constructor (the default, parameterless one in this case) and then assigning to the property setters.

如今的一大侷限性在於,屬性必須是可變的,對象初始化程序才能起做用:它們經過首先調用對象的構造函數(在這種狀況下爲默認的,無參數的)來工做,而後賦值給屬性setter方法。

Init-only properties fix that! They introduce an init accessor that is a variant of the set accessor which can only be called during object initialization:

僅初始化屬性能夠解決該問題!它們引入了init訪問器,它是訪問器的變體,set只能在對象初始化期間調用它:

public class Person { public string FirstName { get; init; } public string LastName { get; init; } }

With this declaration, the client code above is still legal, but any subsequent assignment to the FirstName and LastName properties is an error.

使用此聲明,上面的客戶端代碼仍然合法,可是隨後對FirstNameLastName屬性的任何賦值都是錯誤的。

Init accessors and readonly fields

初始化訪問器和只讀字段

Because init accessors can only be called during initialization, they are allowed to mutate readonly fields of the enclosing class, just like you can in a constructor.

因爲init訪問器只能在初始化期間被調用,所以容許它們改變封閉類readonly的字段,就像在構造函數中同樣。

public class Person { private readonly string firstName; private readonly string lastName; public string FirstName { get => firstName; init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName))); } public string LastName { get => lastName; init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName))); } }

Records

記錄類

Init-only properties are great if you want to make individual properties immutable. If you want the whole object to be immutable and behave like a value, then you should consider declaring it as a record:

若是要使單個屬性不變,則僅初始化屬性很是有用。若是您但願整個對象是不可變的而且表現得像一個值,那麼您應該考慮將其聲明爲記錄

public data class Person { public string FirstName { get; init; } public string LastName { get; init; } }

The data keyword on the class declaration marks it as a record. This imbues it with several additional value-like behaviors, which we’ll dig into in the following. Generally speaking, records are meant to be seen more as 「values」 – data! – and less as objects. They aren’t meant to have mutable encapsulated state. Instead you represent change over time by creating new records representing the new state. They are defined not by their identity, but by their contents.

類聲明時使用data關鍵詞標記一個記錄類。這使它附加了其餘一些相似於值的行爲,咱們將在下面對此進行深刻研究。通常而言,記錄類應更多地視爲「值」 –數據!–而不是做爲對象。也就是說這個類成了不可改變的狀態。您能夠經過建立表示新狀態的新記錄來表示隨着時間的變化的記錄。它們不是由其身份定義的,而是由其內容定義的。

With-expressions

with 表達式

When working with immutable data, a common pattern is to create new values from existing ones to represent a new state. For instance, if our person were to change their last name we would represent it as a new object that’s a copy of the old one, except with a different last name. This technique is often referred to as non-destructive mutation. Instead of representing the person over time, the record represents the person’s state at a given time.

處理不可變數據時,一種常見的模式是從現有值建立新值以表示新狀態。例如,若是咱們的人要更改其姓氏,則將其表示爲一個新對象,該對象是舊對象的副本,但姓氏不一樣。這種技術一般被稱爲非破壞性突變記錄不表明一段時間內的人而是表明給定時間的人的狀態

To help with this style of programming, records allow for a new kind of expression; the with-expression:

爲了幫助這種編程風格,記錄容許一種新的表達方式 - with表達式:

var otherPerson = person with { LastName = "Hanselman" };

With-expressions use object initializer syntax to state what’s different in the new object from the old object. You can specify multiple properties.

With-expressions使用對象初始化器語法來聲明新對象與舊對象的不一樣之處。您能夠指定多個屬性。

A record implicitly defines a protected 「copy constructor」 – a constructor that takes an existing record object and copies it field by field to the new one:

一條記錄隱式定義了一個protected「複製構造函數」 –一種構造函數,它接受現有的記錄對象並將其逐字段複製到新的記錄對象中:

protected Person(Person original) { /* copy all the fields */ } // generated

The with expression causes the copy constructor to get called, and then applies the object initializer on top to change the properties accordingly.

with表達式使副本構造函數被調用,而後在頂部應用對象初始化程序以相應地更改屬性。

If you don’t like the default behavior of the generated copy constructor you can define your own instead, and that will be picked up by the with expression.

若是您不喜歡所生成的副本構造函數的默認行爲,則能夠定義本身的副本構造函數,並將其由with表達式提取

值類型的相等性判斷

All objects inherit a virtual Equals(object) method from the object class. This is used as the basis for the Object.Equals(object, object) static method when both parameters are non-null.

全部對象都object類繼承一個虛擬方法 Equals(object)。當兩個參數都不爲空時,它將用做靜態方法 objectObject.Equals(object, object)  的基礎相等性判斷。

Structs override this to have 「value-based equality」, comparing each field of the struct by calling Equals on them recursively. Records do the same.

結構體重載此方法以具備「基於值的相等性」,經過Equals遞歸調用結構來比較結構的每一個字段記錄和結構體同樣。

This means that in accordance with their 「value-ness」 two record objects can be equal to one another without being the same object. For instance if we modify the last name of the modified person back again:

這意味着,根據兩個記錄對象的值判斷相等性,而沒必要是同一對象。例如,若是咱們再次修改已修改人員的姓氏:

var originalPerson = otherPerson with { LastName = "Hunter" };

We would now have ReferenceEquals(person, originalPerson) = false (they aren’t the same object) but Equals(person, originalPerson) = true (they have the same value).

如今,咱們將有 ReferenceEquals(person, originalPerson)= false(它們不是同一對象),但Equals(person, originalPerson)= true(它們具備相同的值)。

If you don’t like the default field-by-field comparison behavior of the generated Equals override, you can write your own instead. You just need to be careful that you understand how value-based equality works in records, especially when inheritance is involved, which we’ll come back to below.

若是您不喜歡默認的逐域比較行爲,則能夠重寫Equals方法。您只須要注意瞭解基於值的相等性在記錄中的工做原理,尤爲是在涉及繼承時,咱們將再下面文章中回到這個問題上。

Along with the value-based Equals there’s also a value-based GetHashCode() override to go along with it.

除了基於值判斷的 Equals 方法外,還有一個 GetHashCode() 方法,能夠重寫。

Data members

數據成員

Records are overwhelmingly intended to be immutable, with init-only public properties that can be non-destructively modified through with-expressions. In order to optimize for that common case, records change the defaults of what a simple member declaration of the form string FirstName means. Instead of an implicitly private field, as in other class and struct declarations, in records this is taken to be shorthand for a public, init-only auto-property! Thus, the declaration:

記錄絕大多數都是不可變的,它們具備只能經過with表達式進行非破壞性修改的僅初始化的公共屬性爲了針對這種常見狀況進行優化,記錄更改了表單的簡單成員聲明的含義的默認值代替其餘類和結構聲明中的隱式私有字段,在記錄中將其視爲公共的,僅用於初始化的自動屬性的簡寫!所以,聲明:string FirstName

public data class Person { string FirstName; string LastName; }

Means exactly the same as the one we had before:

與以前使用以下代碼徹底相同:

public data class Person { public string FirstName { get; init; } public string LastName { get; init; } }

We think this makes for beautiful and clear record declarations. If you really want a private field, you can just add the private modifier explicitly:

咱們認爲這可使記錄聲明優美而清晰。若是您確實想要私有字段,則能夠private顯式添加修飾符:

private string firstName;

位置記錄

Sometimes it’s useful to have a more positional approach to a record, where its contents are given via constructor arguments, and can be extracted with positional deconstruction.

有時,對記錄採用具位置定位的方法頗有用,該記錄的內容是經過構造函數參數指定的,而且能夠經過位置解構來提取。

It’s perfectly possible to specify your own constructor and deconstructor in a record:

徹底有可能在記錄中指定您本身的構造函數和解構函數:

public data class Person { string FirstName; string LastName; public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName); public void Deconstruct(out string firstName, out string lastName) => (firstName, lastName) = (FirstName, LastName); }

But there’s a much shorter syntax for expressing exactly the same thing (modulo casing of parameter names):

可是,用於表達徹底相同的內容的語法要短得多(參數名稱的模數框):

public data class Person(string FirstName, string LastName);

This declares the public init-only auto-properties and the constructor and the deconstructor, so that you can write:

這聲明瞭僅用於初始化的公共自動屬性以及構造函數構造函數,以便您能夠編寫:

var person = new Person("Scott", "Hunter"); // positional construction var (f, l) = person; // positional deconstruction

If you don’t like the generated auto-property you can define your own property of the same name instead, and the generated constructor and deconstructor will just use that one.

若是您不喜歡生成的自動屬性,則能夠定義本身的同名屬性,使得生成的構造函數和反構造函數將僅使用該屬性。

Records and mutation

記錄和突變

The value-based semantics of a record don’t gel well with mutable state. Imagine putting a record object into a dictionary. Finding it again depends on Equals and (sometimes) GethashCode. But if the record changes its state, it will also change what it’s equal to! We might not be able to find it again! In a hash table implementation it might even corrupt the data structure, since placement is based on the hash code it has 「on arrival」!

記錄的基於值的語義與可變狀態不能很好地融合在一塊兒。想象一下將記錄對象放入字典中。再次找到它取決於Equals和(有時)GethashCode可是,若是記錄更改其狀態,則它也將更改其含義!咱們可能沒法再次找到它!在Hash表的實現中,它甚至可能破壞數據結構,由於放置是基於其「到達時」的Hash值!

There are probably some valid advanced uses of mutable state inside of records, notably for caching. But the manual work involved in overriding the default behaviors to ignore such state is likely to be considerable.

記錄內部可能存在對可變狀態的一些有效的高級用法,特別是用於緩存。可是,涉及覆蓋默認行爲以忽略這種狀態的手動工做可能很是耗時。

With-expressions and inheritance

with 表達式和繼承

Value-based equality and non-destructive mutation are notoriously challenging when combined with inheritance. Let’s add a derived record class Student to our running example:

與繼承相結合時,基於值的相等性和非破壞性突變是衆所周知的挑戰。Student咱們在正在運行的示例中添加一個派生記錄類

public data class Person { string FirstName; string LastName; } public data class Student : Person { int ID; }

And let’s start our with-expression example by actually creating a Student, but storing it in a Person variable:

讓咱們開始建立with表達式示例,方法是實際建立一個Student,但將其存儲在Person變量中:

Person person = new Student { FirstName = "Scott", LastName = "Hunter", ID = GetNewId() }; otherPerson = person with { LastName = "Hanselman" };

At the point of that with-expression on the last line the compiler has no idea that person actually contains a Student. Yet, the new person wouldn’t be a proper copy if it wasn’t actually a Student object, complete with the same ID as the first one copied over.

最後一行的with表達式位置,編譯器不知道person實際上包含一個Student。可是,若是新的 Person 類型實際上不是Student對象,那麼它就不是徹底的副本,而且與ID第一個被複制的對象徹底相同。(這句有點拗口?)

C# makes this work. Records have a hidden virtual method that is entrusted with 「cloning」 the whole object. Every derived record type overrides this method to call the copy constructor of that type, and the copy constructor of a derived record chains to the copy constructor of the base record. A with-expression simply calls the hidden 「clone」 method and applies the object initializer to the result.

C#作到了。記錄具備一個隱藏的虛擬方法,該方法委託「克隆」 整個對象。每一個派生記錄類型都將重寫此方法以調用該類型的副本構造函數,而派生記錄的副本構造函數將連接到基本記錄的副本構造函數。一個with-expression只是調用隱藏的「克隆」的方法和適用對象初始化的結果。

Value-based equality and inheritance

值類型的相等性與繼承

Similarly to the with-expression support, value-based equality also has to be 「virtual」, in the sense that Students need to compare all the Student fields, even if the statically known type at the point of comparison is a base type like Person. That is easily achieved by overriding the already virtual Equals method.

with表達式支持的方式相似,值類型的相等性也必須是「虛擬的」,即Student須要比較全部Student字段,即便在比較時靜態已知的類型是基本類型,例如Person經過覆蓋已經存在的虛擬Equals方法很容易實現

However, there is an additional challenge with equality: What if you compare two different kinds of Person? We can’t really just let one of them decide which equality to apply: Equality is supposed to be symmetric, so the result should be the same regardless of which of the two objects come first. In other words, they have to agree on the equality being applied!

可是,平等還有另一個挑戰:若是比較兩種不一樣的類型Person怎麼辦?咱們不能真正讓他們中的一個決定要應用哪一個相等相等應該是對稱的,所以不管兩個對象中的哪一個首先出現,結果都應該相同。換句話說,他們必須就適用的相等達成一致

An example to illustrate the problem:

一個例子來講明這個問題:

Person person1 = new Person { FirstName = "Scott", LastName = "Hunter" }; Person person2 = new Student { FirstName = "Scott", LastName = "Hunter", ID = GetNewId() };

Are the two objects equal to one another? person1 might think so, since person2 has all the Person things right, but person2 would beg to differ! We need to make sure that they both agree that they are different objects.

這兩個對象彼此相等嗎?person1也許會這樣想,由於person2全部的Person事情都對,person2希望不同凡響!咱們須要確保它們都贊成它們是不一樣的對象。

Once again, C# takes care of this for you automatically. The way it’s done is that records have a virtual protected property called EqualityContract. Every derived record overrides it, and in order to compare equal, the two objects musts have the same EqualityContract.

C#再一次自動爲您解決此問題。完成的方式是記錄具備稱爲的虛擬受保護屬性EqualityContract每一個派生的記錄都會覆蓋它,而且爲了比較相等,兩個對象必須具備相同的EqualityContract

Top-level programs

頂層程序

Writing a simple program in C# requires a remarkable amount of boilerplate code:

用C#編寫一個簡單的程序須要大量的樣板代碼,例如:

using System; class Program { static void Main() { Console.WriteLine("Hello World!"); } }

This is not only overwhelming for language beginners, but clutters up the code and adds levels of indentation.

這不只使語言初學者不知所措,並且使代碼混亂並增長了縮進級別。

In C# 9.0 you can just choose to write your main program at the top level instead:

在C#9.0中,您能夠選擇在最頂層編寫主程序:

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

Any statement is allowed. The program has to occur after the usings and before any type or namespace declarations in the file, and you can only do this in one file, just as you can have only one Main method today.

容許任何語句。該程序必須在usings以後而且在文件中任何類型或名稱空間聲明以前發生,而且您只能在一個文件中執行此操做,就像Main今天只有一種方法同樣。

If you want to return a status code you can do that. If you want to await things you can do that. And if you want to access command line arguments, args is available as a 「magic」 parameter.

若是要返回狀態代碼,能夠執行此操做。若是您想要await作某事,您能夠這樣作。並且,若是要訪問命令行參數,args則能夠做爲「魔術」參數使用。

Local functions are a form of statement and are also allowed in the top level program. It is an error to call them from anywhere outside of the top level statement section.

局部函數是語句的一種形式,而且在頂層程序中也容許使用。從頂級語句部分以外的任何地方調用它們是錯誤的。

Improved pattern matching

改進的模式匹配

Several new kinds of patterns have been added in C# 9.0. Let’s look at them in the context of this code snippet from the pattern matching tutorial:

C#9.0中添加了幾種新的模式。讓咱們在模式匹配教程的如下代碼片斷的上下文中查看它們

public static decimal CalculateToll(object vehicle) => vehicle switch { ... DeliveryTruck t when t.GrossWeightClass > 5000 => 10.00m + 5.00m, DeliveryTruck t when t.GrossWeightClass < 3000 => 10.00m - 2.00m, DeliveryTruck _ => 10.00m, _ => throw new ArgumentException("Not a known vehicle type", nameof(vehicle)) };

Simple type patterns

簡單類型模式

Currently, a type pattern needs to declare an identifier when the type matches – even if that identifier is a discard _, as in DeliveryTruck _ above. But now you can just write the type:

當前,類型模式須要在類型匹配時聲明一個標識符,即便該標識符是一個廢棄_如上所述)。可是如今您能夠這樣編寫類型:_

DeliveryTruck => 10.00m,

Relational patterns

關係匹配模式

C# 9.0 introduces patterns corresponding to the relational operators <<= and so on. So you can now write the DeliveryTruck part of the above pattern as a nested switch expression:

C#9.0引入了與關係運算符相對應的模式<<=依此類推。所以,您如今能夠DeliveryTruck將上述模式一部分編寫爲嵌套的switch表達式:

DeliveryTruck t when t.GrossWeightClass switch { > 5000 => 10.00m + 5.00m, < 3000 => 10.00m - 2.00m, _ => 10.00m, },

Here > 5000 and < 3000 are relational patterns.

這裏是關係模式是大於5000小於3000

Logical patterns

邏輯匹配模式

Finally you can combine patterns with logical operators andor and not, spelled out as words to avoid confusion with the operators used in expressions. For instance, the cases of the nested switch above could be put into ascending order like this:

最後,您能夠用邏輯運算符相結合的模式andor而且not,闡述了做爲的話,以免在表達式中使用操做者的困惑。例如,上面的嵌套開關的狀況能夠按以下升序排列:

DeliveryTruck t when t.GrossWeightClass switch { < 3000 => 10.00m - 2.00m, >= 3000 and <= 5000 => 10.00m, > 5000 => 10.00m + 5.00m, },

The middle case there uses and to combine two relational patterns and form a pattern representing an interval.

中間狀況and用來組合兩個關係模式並造成表示間隔的模式。

A common use of the not pattern will be applying it to the null constant pattern, as in not null. For instance we can split the handling of unknown cases depending on whether they are null:

模式的常見用法是not將其應用於null恆定模式,如代碼中所示例如,咱們能夠根據未知案例是否爲空來拆分處理方式:not null

not null => throw new ArgumentException($"Not a known vehicle type: {vehicle}", nameof(vehicle)), null => throw new ArgumentNullException(nameof(vehicle))

Also not is going to be convenient in if-conditions containing is-expressions where, instead of unwieldy double parentheses:

not在包含is表達式的if條件中,代替笨重的雙括號,之前這樣寫

if (!(e is Customer)) { ... }

You can just say

如今能夠這麼寫

if (e is not Customer) { ... }

Improved target typing

改進了目標類型(建立方式)

「Target typing」 is a term we use for when an expression gets its type from the context of where it’s being used. For instance null and lambda expressions are always target typed.

「目標類型」是當表達式從其使用位置的上下文中獲取其類型時使用的術語。例如null,lambda表達式始終是目標類型。

In C# 9.0 some expressions that weren’t previously target typed become able to be guided by their context.

在C#9.0中,某些之前不是目標類型的表達式能夠經過其上下文進行引導。

Target-typed new expressions

目標類型的new表達式

new expressions in C# have always required a type to be specified (except for implicitly typed array expressions). Now you can leave out the type if there’s a clear type that the expressions is being assigned to.

C#中的new表達式建立對象始終要求指定類型(隱式類型的數組表達式除外)。如今,若是有一個明確的類型要分配給表達式,則能夠省去該類型。

Point p = new (3, 5);

Target typed ?? and ?:

目標類型???:

Sometimes conditional ?? and ?: expressions don’t have an obvious shared type between the branches. Such cases fail today, but C# 9.0 will allow them if there’s a target type that both branches convert to:

有時條件???:表達式在分支之間沒有明顯的共享類型。這種狀況如今不容許,可是若是兩個分支都轉換爲如下目標類型,則C#9.0將容許它們:

Person person = student ?? customer; // Shared base type int? result = b ? 0 : null; // nullable value type

Covariant returns

返回值協變

It’s sometimes useful to express that a method override in a derived class has a more specific return type than the declaration in the base type. C# 9.0 allows that:

表達派生類中的方法重寫比基類型中的聲明更具體的返回類型有時是有用的。C#9.0容許:

abstract class Animal { public abstract Food GetFood(); ... } class Tiger : Animal { public override Meat GetFood() => ...; }

And much more…

還有更多…

The best place to check out the full set of upcoming features for C# 9.0 and follow their completion is the Language Feature Status on the Roslyn (C#/VB Compiler) GitHub repo.

在Roslyn(C#/ VB編譯器)GitHub存儲庫上,查看C#9.0即將推出的所有功能並完成這些功能的最佳場所

 

原文:https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/

 

Records and mutation

相關文章
相關標籤/搜索