C# 9.0 中的新增功能What's new in C# 9.0

C # 9.0 將下列功能和增強功能新增至 c # 語言:C# 9.0 adds the following features and enhancements to the C# language:

  • 記錄Records
  • 僅供初始化的 SetterInit only setters
  • 最上層陳述式Top-level statements
  • 模式比對增強功能Pattern matching enhancements
  • 原生大小的整數Native sized integers
  • 函式指標Function pointers
  • 隱藏發出 localsinit 旗標Suppress emitting localsinit flag
  • 目標型別新運算式Target-typed new expressions
  • 靜態匿名函數static anonymous functions
  • 目標型別條件運算式Target-typed conditional expressions
  • Covariant 傳回類型Covariant return types
  • GetEnumerator迴圈的延伸模組支援 foreachExtension GetEnumerator support for foreach loops
  • Lambda 捨棄參數Lambda discard parameters
  • 區域函式上的屬性Attributes on local functions
  • 模組初始設定式Module initializers
  • 部分方法的新功能New features for partial methods

.Net 5 支援 c # 9.0。C# 9.0 is supported on .NET 5. 如需詳細資訊,請參閱 c # 語言版本控制For more information, see C# language versioning.

您可以從 .net 下載頁面下載最新的 .net SDK。You can download the latest .NET SDK from the .NET downloads page.

記錄類型Record types

C # 9.0 引進了 *記錄類型 _,這是一種參考型別,可提供合成方法來提供相等的值語義。C# 9.0 introduces *record types _, which are a reference type that provides synthesized methods to provide value semantics for equality. 依預設,記錄是不可變的。Records are immutable by default.

記錄類型可讓您輕鬆地在 .NET 中建立不可變的參考型別。Record types make it easy to create immutable reference types in .NET. 在過去,.NET 型別大多分類為參考型別 (包括類別和匿名型別) 和實值型別 (包括結構和元組) 。Historically, .NET types are largely classified as reference types (including classes and anonymous types) and value types (including structs and tuples). 雖然建議使用可變的實數值型別,但可變動的實值型別通常不會造成錯誤。While immutable value types are recommended, mutable value types don’t often introduce errors. 實值型別變數會保存值,以便在將實數值型別傳遞給方法時,對原始資料的複本進行變更。Value type variables hold the values so changes are made to a copy of the original data when value types are passed to methods.

不可變的參考型別也有許多優點。There are many advantages to immutable reference types as well. 這些優點在具有共用資料的並行程式中更加明顯。These advantages are more pronounced in concurrent programs with shared data. 可惜的是,c # 強制您撰寫相當多的額外程式碼來建立不可變的參考型別。Unfortunately, C# forced you to write quite a bit of extra code to create immutable reference types. 記錄提供使用值語義相等的不可變參考型別的類型宣告。Records provide a type declaration for an immutable reference type that uses value semantics for equality. 相等和雜湊碼的合成方法會將兩筆記錄視為相等,如果其屬性相等。The synthesized methods for equality and hash codes consider two records equal if their properties are all equal. 請考慮下列定義:Consider this definition:

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

    public Person(string first, string last) => (FirstName, LastName) = (first, last);
}

記錄定義 Person 會建立包含兩個 readonly 屬性的型別: FirstNameLastNameThe record definition creates a Person type that contains two readonly properties: FirstName and LastName. Person類型是參考型別。The Person type is a reference type. 如果您看過 IL,它就是一種類別。If you looked at the IL, it’s a class. 這是不可變的,因為在建立之後,就無法修改任何屬性。It’s immutable in that none of the properties can be modified once it's been created. 當您定義記錄類型時,編譯器會為您會合成數個其他方法:When you define a record type, the compiler synthesizes several other methods for you:

  • 以值為基礎之相等比較的方法Methods for value-based equality comparisons
  • 覆寫 GetHashCode()Override for GetHashCode()
  • 複製和複製成員Copy and Clone members
  • PrintMembersToString()PrintMembers and ToString()

記錄支援繼承。Records support inheritance. 您可以宣告衍生自的新記錄 Person ,如下所示:You can declare a new record derived from Person as follows:

public record Teacher : Person
{
    public string Subject { get; }

    public Teacher(string first, string last, string sub)
        : base(first, last) => Subject = sub;
}

您也可以密封記錄以防止進一步的衍生:You can also seal records to prevent further derivation:

public sealed record Student : Person
{
    public int Level { get; }

    public Student(string first, string last, int level) : base(first, last) => Level = level;
}

編譯器會會合成上述方法的不同版本。The compiler synthesizes different versions of the methods above. 如果記錄類型是密封的,且直接基類為 object,則方法簽章相依于。The method signatures depend on if the record type is sealed and if the direct base class is object. 記錄應該具有下列功能:Records should have the following capabilities:

  • 相等是以值為基礎,並且包含類型相符的檢查。Equality is value-based, and includes a check that the types match. 例如, Student Person 即使兩筆記錄共用相同的名稱,a 也不能相等。For example, a Student can't be equal to a Person, even if the two records share the same name.
  • 記錄會為您產生一致的字串表示。Records have a consistent string representation generated for you.
  • 記錄支援複製結構。Records support copy construction. 正確的複製結構必須包含繼承階層,以及開發人員新增的屬性。Correct copy construction must include inheritance hierarchies, and properties added by developers.
  • 您可以修改記錄以進行複製。Records can be copied with modification. 這些複製和修改作業支援非破壞性的變化。These copy and modify operations supports non-destructive mutation.

除了熟悉的多載 Equalsoperator == 和以外 operator != ,編譯器也會會合成新的 EqualityContract 屬性。In addition to the familiar Equals overloads, operator ==, and operator !=, the compiler synthesizes a new EqualityContract property. 屬性 Type 會傳回符合記錄類型的物件。The property returns a Type object that matches the type of the record. 如果基底類型為 object ,則屬性為 virtualIf the base type is object, the property is virtual. 如果基底類型是另一種記錄類型,則此屬性為 overrideIf the base type is another record type, the property is an override. 如果記錄類型為 sealed ,則屬性為 sealedIf the record type is sealed, the property is sealed. 合成會 GetHashCode 使用 GetHashCode 基底類型中宣告的所有屬性和欄位,以及記錄類型。The synthesized GetHashCode uses the GetHashCode from all properties and fields declared in the base type and the record type. 這些合成方法會在整個繼承階層架構中強制執行以值為基礎的相等。These synthesized methods enforce value-based equality throughout an inheritance hierarchy. 這表示 Student 永遠不會將永遠視為 Person 相同名稱的。That means a Student will never be considered equal to a Person with the same name. 這兩筆記錄的類型必須相符,以及在記錄類型中共用的所有屬性都相等。The types of the two records must match as well as all properties shared among the record types being equal.

記錄也有合成的函式,以及用來建立複本的「複製」方法。Records also have a synthesized constructor and a "clone" method for creating copies. 合成的函式具有記錄類型的單一參數。The synthesized constructor has a single parameter of the record type. 它會針對記錄的所有屬性產生具有相同值的新記錄。It produces a new record with the same values for all properties of the record. 如果記錄是密封的,則這個函式是私用的,否則會受到保護。This constructor is private if the record is sealed, otherwise it's protected. 合成的 "clone" 方法支援記錄階層的複製結構。The synthesized "clone" method supports copy construction for record hierarchies. 「Clone」一詞是以引號括住,因為實際名稱是編譯器產生的。The term "clone" is in quotes because the actual name is compiler generated. 您無法 Clone 在記錄類型中建立名為的方法。You can't create a method named Clone in a record type. 合成的 "clone" 方法會傳回使用虛擬分派複製的記錄類型。The synthesized "clone" method returns the type of record being copied using virtual dispatch. 編譯器會根據上的存取修飾詞,為 "clone" 方法新增不同的修飾詞 recordThe compiler adds different modifiers for the "clone" method depending on the access modifiers on the record:

  • 如果記錄類型為,則「 abstract 複製」方法也是 abstractIf the record type is abstract, the "clone" method is also abstract. 如果基底類型不 object 是,則方法也是 overrideIf the base type isn't object, the method is also override.
  • 針對不是 abstract 基底類型為下列情況的記錄類型 objectFor record types that aren't abstract when the base type is object:
    • 如果記錄是,則不會 sealed 將其他修飾詞新增至「複製」方法 (這表示不會 virtual) 。If the record is sealed, no additional modifiers are added to the "clone" method (meaning it is not virtual).
    • 如果記錄不是 sealed ,則「複製」方法是 virtualIf the record isn't sealed, the "clone" method is virtual.
  • 針對 abstract 不是基底類型不是的記錄類型 objectFor record types that aren't abstract when the base type is not object:
    • 如果記錄為,則「 sealed 複製」方法也是 sealedIf the record is sealed, the "clone" method is also sealed.
    • 如果記錄不是 sealed ,則「複製」方法是 overrideIf the record isn't sealed, the "clone" method is override.

所有這些規則的結果都是一致地跨任何記錄類型階層來實行相等的結果。The result of all these rules is the equality is implemented consistently across any hierarchy of record types. 如果兩筆記錄的屬性相等且其類型相同,就會彼此相等,如下列範例所示:Two records are equal to each other if their properties are equal and their types are the same, as shown in the following example:

var person = new Person("Bill", "Wagner");
var student = new Student("Bill", "Wagner", 11);

Console.WriteLine(student == person); // false

編譯器會會合成兩種支援列印輸出的方法:覆 ToString() 寫、和 PrintMembersThe compiler synthesizes two methods that support printed output: a ToString() override, and PrintMembers. PrintMembers會接受 System.Text.StringBuilder 做為其引數。The PrintMembers takes a System.Text.StringBuilder as its argument. 它會針對記錄類型中的所有屬性,附加以逗號分隔的屬性名稱和值清單。It appends a comma-separated list of property names and values for all properties in the record type. PrintMembers 針對衍生自其他記錄的任何記錄,呼叫基底實作為。PrintMembers calls the base implementation for any records derived from other records. ToString() 寫會傳回所產生的字串 PrintMembers ,並以和括住 { }The ToString() override returns the string produced by PrintMembers, surrounded by { and }. 例如,的方法會傳回, ToString() Student string 如下列程式碼所示:For example, the ToString() method for Student returns a string like the following code:

"Student { LastName = Wagner, FirstName = Bill, Level = 11 }"

到目前為止所顯示的範例會使用傳統語法來宣告屬性。The examples shown so far use traditional syntax to declare properties. 有更精確的形式稱為「 *位置記錄*」。There's a more concise form called positional records. 以下是稍早定義為位置記錄的三種記錄類型:Here are the three record types defined earlier as positional records:

public record Person(string FirstName, string LastName);

public record Teacher(string FirstName, string LastName,
    string Subject)
    : Person(FirstName, LastName);

public sealed record Student(string FirstName,
    string LastName, int Level)
    : Person(FirstName, LastName);

這些宣告會建立與舊版 (的相同功能,但有幾個額外的功能) 在下一節中討論。These declarations create the same functionality as the earlier version (with a couple extra features covered in the following section). 這些宣告的結尾都是分號而不是方括弧,因為這些記錄不會加入額外的方法。These declarations end with a semicolon instead of brackets because these records don't add additional methods. 您可以新增內文,也可以包含任何其他方法:You can add a body, and include any additional methods as well:

public record Pet(string Name)
{
    public void ShredTheFurniture() =>
        Console.WriteLine("Shredding furniture");
}

public record Dog(string Name) : Pet(Name)
{
    public void WagTail() =>
        Console.WriteLine("It's tail wagging time");

    public override string ToString()
    {
        StringBuilder s = new();
        base.PrintMembers(s);
        return $"{s.ToString()} is a dog";
    }
}

編譯器會產生 Deconstruct 位置記錄的方法。The compiler produces a Deconstruct method for positional records. Deconstruct方法的參數與記錄類型中所有公用屬性的名稱相符。The Deconstruct method has parameters that match the names of all public properties in the record type. Deconstruct方法可以用來將記錄解構到其元件屬性中:The Deconstruct method can be used to deconstruct the record into its component properties:

var person = new Person("Bill", "Wagner");

var (first, last) = person;
Console.WriteLine(first);
Console.WriteLine(last);

最後,記錄支援 with 運算式Finally, records support with expressions. * with Expression_ _ 會指示編譯器建立記錄的複本,但 _with 已修改的指定屬性:A *with expression_ _ instructs the compiler to create a copy of a record, but _with specified properties modified:

Person brother = person with { FirstName = "Paul" };

上一行會建立新的 Person 記錄,其中 LastName 屬性是的複本 person ,而 FirstName"Paul"The previous line creates a new Person record where the LastName property is a copy of person, and the FirstName is "Paul". 您可以在運算式中設定任意數目的屬性 withYou can set any number of properties in a with expression. 您也可以使用 with 運算式來建立確切的複本。You can also use with expressions to create an exact copy. 您可以針對要修改的屬性指定空的集合:You specify the empty set for the properties to modify:

Person clone = person with { };

任何合成成員(「複製」方法除外)都可以由您撰寫。Any of the synthesized members except the "clone" method may be written by you. 如果記錄類型的方法符合任何合成方法的簽章,則編譯器不會合成該方法。If a record type has a method that matches the signature of any synthesized method, the compiler doesn't synthesize that method. 先前的 Dog 記錄範例包含手動編碼的 ToString() 方法做為範例。The earlier Dog record example contains a hand coded ToString() method as an example.

深入瞭解這項 記錄探索 教學課程中的記錄類型。Learn more about record types in this exploration of records tutorial.

僅供初始化的 SetterInit only setters

*Init only setter _ 提供一致的語法來初始化物件的成員。*Init only setters _ provide consistent syntax to initialize members of an object. 屬性初始化運算式可讓它清楚哪個值會設定哪個屬性。Property initializers make it clear which value is setting which property. 缺點是這些屬性必須是可設定的。The downside is that those properties must be settable. 從 c # 9.0 開始,您可以建立存取子, init 而不是 set 屬性和索引子的存取子。Starting with C# 9.0, you can create init accessors instead of set accessors for properties and indexers. 呼叫端可以使用屬性初始化運算式語法來設定建立運算式中的這些值,但在結構完成之後,這些屬性是唯讀的。Callers can use property initializer syntax to set these values in creation expressions, but those properties are readonly once construction has completed. 僅限 Init 的 setter 提供視窗來變更狀態。Init only setters provide a window to change state. 當建築階段結束時,該視窗就會關閉。That window closes when the construction phase ends. 在所有初始化後(包括屬性初始化運算式和 with-expression),都能有效地結束結構階段。The construction phase effectively ends after all initialization, including property initializers and with-expressions have completed.

您可以 init 只在您撰寫的任何類型中宣告 setter。You can declare init only setters in any type you write. 例如,下列結構會定義氣象觀察結構:For example, the following struct defines a weather observation structure:

public struct WeatherObservation
{
    public DateTime RecordedAt { get; init; }
    public decimal TemperatureInCelsius { get; init; }
    public decimal PressureInMillibars { get; init; }

    public override string ToString() =>
        $"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " +
        $"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure";
}

呼叫端可以使用屬性初始化運算式語法來設定值,同時仍然保留永久性:Callers can use property initializer syntax to set the values, while still preserving the immutability:

var now = new WeatherObservation 
{ 
    RecordedAt = DateTime.Now, 
    TemperatureInCelsius = 20, 
    PressureInMillibars = 998.0m 
};

但是,在初始化之後變更觀察是一項錯誤,方法是在初始化之外指派給僅限初始化的屬性:But, changing an observation after initialization is an error by assigning to an init-only property outside of initialization:

// Error! CS8852.
now.TemperatureInCelsius = 18;

僅初始化 setter 有助於從衍生類別設定基類屬性。Init only setters can be useful to set base class properties from derived classes. 它們也可以透過基類中的協助程式來設定衍生屬性。They can also set derived properties through helpers in a base class. 位置記錄會使用僅初始化 setter 來宣告屬性。Positional records declare properties using init only setters. 這些 setter 會在 with 運算式中使用。Those setters are used in with-expressions. 您可以針對任何或定義,宣告 init only 的 setter class structYou can declare init only setters for any class or struct you define.

最上層陳述式Top-level statements

*最上層的語句* 會從許多應用程式中移除不必要的繁瑣。Top-level statements remove unnecessary ceremony from many applications. 請考慮標準 "Hello World!"Consider the canonical "Hello World!" 程式:program:

using System;

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

只有一行程式碼會執行任何作業。There’s only one line of code that does anything. 使用最上層的語句,您可以使用 using 語句和執行工作的單行來取代所有重複使用的語句:With top-level statements, you can replace all that boilerplate with the using statement and the single line that does the work:

using System;

Console.WriteLine("Hello World!");

如果您需要單行程式,您可以移除指示詞, using 並使用完整類型名稱:If you wanted a one-line program, you could remove the using directive and use the fully qualified type name:

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

您的應用程式中只有一個檔案可以使用最上層的語句。Only one file in your application may use top-level statements. 如果編譯器在多個原始程式檔中尋找最上層的語句,則會產生錯誤。If the compiler finds top-level statements in multiple source files, it’s an error. 如果您將最上層語句與宣告的程式進入點方法(通常是方法)結合,也會發生錯誤 MainIt’s also an error if you combine top-level statements with a declared program entry point method, typically a Main method. 您可以認為一個檔案包含的語句通常會在 Main 類別的方法中 ProgramIn a sense, you can think that one file contains the statements that would normally be in the Main method of a Program class.

這項功能最常見的用途之一就是建立教學教材。One of the most common uses for this feature is creating teaching materials. 初學者 c # 開發人員可以撰寫標準 "Hello World!"Beginner C# developers can write the canonical “Hello World!” 在一或兩行程式碼中。in one or two lines of code. 不需要額外的額外儀式。None of the extra ceremony is needed. 不過,經驗豐富的開發人員也會發現這項功能有許多用途。However, seasoned developers will find many uses for this feature as well. 最上層的語句可啟用類似腳本的實驗體驗,類似于 Jupyter 筆記本所提供的功能。Top-level statements enable a script-like experience for experimentation similar to what Jupyter notebooks provide. 最上層的語句非常適合小型主控台程式和公用程式。Top-level statements are great for small console programs and utilities. Azure Functions 是最適合最上層語句的使用案例。Azure Functions are an ideal use case for top-level statements.

最重要的是,最重要的語句不會限制應用程式的範圍或複雜度。Most importantly, top-level statements don't limit your application’s scope or complexity. 這些語句可以存取或使用任何 .NET 類別。Those statements can access or use any .NET class. 它們也不會限制您使用命令列引數或傳回值。They also don’t limit your use of command-line arguments or return values. 最上層語句可以存取名為 args 的字串陣列。Top-level statements can access an array of strings named args. 如果最上層的語句傳回整數值,該值就會變成合成方法的整數傳回碼 MainIf the top-level statements return an integer value, that value becomes the integer return code from a synthesized Main method. 最上層語句可能包含非同步運算式。The top-level statements may contain async expressions. 在此情況下,合成的進入點會傳回 TaskTask<int>In that case, the synthesized entry point returns a Task, or Task<int>.

模式比對增強功能Pattern matching enhancements

C # 9 包含新的模式比對改良功能:C# 9 includes new pattern matching improvements:

  • *類型模式* 符合變數是類型Type patterns match a variable is a type
  • *括弧模式* 會強制執行或強調模式組合的優先順序Parenthesized patterns enforce or emphasize the precedence of pattern combinations
  • *組成 and 模式* 需要這兩個模式相符Conjunctive and patterns require both patterns to match
  • Disjunctive or 模式 需要有兩種模式相符Disjunctive or patterns require either pattern to match
  • *否定 not 模式* 要求模式不相符Negated not patterns require that a pattern doesn’t match
  • *關聯式模式* 要求輸入小於、大於、小於或等於或大於或等於指定的常數。Relational patterns require the input be less than, greater than, less than or equal, or greater than or equal to a given constant.

這些模式會擴充模式的語法。These patterns enrich the syntax for patterns. 請考慮下列範例:Consider these examples:

public static bool IsLetter(this char c) =>
    c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

或者,使用選擇性的括弧讓它清楚的 and 優先順序高於 orAlternatively, with optional parentheses to make it clear that and has higher precedence than or:

public static bool IsLetterOrSeparator(this char c) =>
    c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

其中一個最常見的用法是 null 檢查的新語法:One of the most common uses is a new syntax for a null check:

if (e is not null)
{
    // ...
}

這些模式中的任何一種都可以在允許模式的任何內容中使用: is 模式運算式、 switch 運算式、嵌套模式,以及 switch 語句 case 標籤的模式。Any of these patterns can be used in any context where patterns are allowed: is pattern expressions, switch expressions, nested patterns, and the pattern of a switch statement’s case label.

效能和互通性Performance and interop

有三項新功能可改善需要高效能的原生 interop 和低層級程式庫的支援:原生大小的整數、函式指標,以及省略 localsinit 旗標。Three new features improve support for native interop and low-level libraries that require high performance: native sized integers, function pointers, and omitting the localsinit flag.

原生大小的整數 nintnuint 是整數類型。Native sized integers, nint and nuint, are integer types. 它們是以基礎類型和來 System.IntPtr 表示 System.UIntPtrThey're expressed by the underlying types System.IntPtr and System.UIntPtr. 編譯器會以原生 int 的形式呈現這些類型的其他轉換和作業。The compiler surfaces additional conversions and operations for these types as native ints. 原生大小的整數會定義或的屬性 MaxValue MinValueNative sized integers define properties for MaxValue or MinValue. 這些值無法表示為編譯時間常數,因為它們依存于目的電腦上的整數原生大小。These values can't be expressed as compile time constants because they depend on the native size of an integer on the target machine. 這些值在執行時間是唯讀的。Those values are readonly at runtime. 您可以 nint 在 [.] 範圍中使用的常數值 int.MinValueYou can use constant values for nint in the range [int.MinValue .. int.MaxValue].int.MaxValue]. 您可以 nuint 在 [.] 範圍中使用的常數值 uint.MinValueYou can use constant values for nuint in the range [uint.MinValue .. uint.MaxValue].uint.MaxValue]. 編譯器會使用和類型來執行所有一元和二元運算子的常數折迭 System.Int32 System.UInt32The compiler performs constant folding for all unary and binary operators using the System.Int32 and System.UInt32 types. 如果結果不符合32位,則作業會在執行時間執行,且不會被視為常數。If the result doesn't fit in 32 bits, the operation is executed at runtime and isn't considered a constant. 原生大小的整數可提高使用整數數學的案例中的效能,而且需要盡可能最快的效能。Native sized integers can increase performance in scenarios where integer math is used extensively and needs to have the fastest performance possible.

函式指標提供簡單的語法來存取 IL 操作碼 ldftncalliFunction pointers provide an easy syntax to access the IL opcodes ldftn and calli. 您可以使用新的語法來宣告函式指標 delegate_You can declare function pointers using new delegate_ syntax. delegate*類型是指標類型。A delegate* type is a pointer type. delegate* calli 相對於在方法上使用的委派,叫用型別會使用 callvirt Invoke()Invoking the delegate* type uses calli, in contrast to a delegate that uses callvirt on the Invoke() method. 在語法上,叫用相同。Syntactically, the invocations are identical. 函數指標調用會使用 managed 呼叫慣例。Function pointer invocation uses the managed calling convention. 您可以在 unmanaged 語法之後加入關鍵字, delegate* 以宣告您想要 unmanaged 呼叫慣例。You add the unmanaged keyword after the delegate* syntax to declare that you want the unmanaged calling convention. 您可以使用宣告上的屬性來指定其他呼叫慣例 delegate*Other calling conventions can be specified using attributes on the delegate* declaration.

最後,您可以加入, System.Runtime.CompilerServices.SkipLocalsInitAttribute 以指示編譯器不要發出 localsinit 旗標。Finally, you can add the System.Runtime.CompilerServices.SkipLocalsInitAttribute to instruct the compiler not to emit the localsinit flag. 此旗標會指示 CLR 將所有區域變數初始化為零。This flag instructs the CLR to zero-initialize all local variables. localsinit自1.0 起,旗標已是 c # 的預設行為。The localsinit flag has been the default behavior for C# since 1.0. 不過,在某些情況下,額外的零初始化可能會有顯著的效能影響。However, the extra zero-initialization may have measurable performance impact in some scenarios. 尤其是在使用時 stackallocIn particular, when you use stackalloc. 在這些情況下,您可以新增 SkipLocalsInitAttributeIn those cases, you can add the SkipLocalsInitAttribute. 您可以將它加入至單一方法或屬性,或加入至 classstructinterface 或甚至是模組。You may add it to a single method or property, or to a class, struct, interface, or even a module. 這個屬性不會影響 abstract 方法,它會影響針對實作為產生的程式碼。This attribute doesn't affect abstract methods; it affects the code generated for the implementation.

在某些情況下,這些功能可以改善效能。These features can improve performance in some scenarios. 在採用之前和之後,請務必謹慎使用它們。They should be used only after careful benchmarking both before and after adoption. 牽涉到原生大小整數的程式碼必須在具有不同整數大小的多個目標平臺上進行測試。Code involving native sized integers must be tested on multiple target platforms with different integer sizes. 其他功能則需要 unsafe 程式碼。The other features require unsafe code.

符合和完成功能Fit and finish features

許多其他功能可協助您更有效率地撰寫程式碼。Many of the other features help you write code more efficiently. 在 c # 9.0 中,您可以在已建立物件的型別已知時,省略 new 運算式中的型別。In C# 9.0, you can omit the type in a new expression when the created object's type is already known. 最常見的用法是在欄位宣告中:The most common use is in field declarations:

private List<WeatherObservation> _observations = new();

new當您需要建立新的物件以傳遞至方法的引數時,也可以使用目標型別。Target-typed new can also be used when you need to create a new object to pass as an argument to a method. 請考慮 ForecastFor() 具有下列簽章的方法:Consider a ForecastFor() method with the following signature:

public WeatherForecast ForecastFor(DateTime forecastDate, WeatherForecastOptions options)

您可以呼叫它,如下所示:You could call it as follows:

var forecast = station.ForecastFor(DateTime.Now.AddDays(2), new());

這項功能的另一個不錯用途是將它與 init only 屬性結合,以初始化新的物件:Another nice use for this feature is to combine it with init only properties to initialize a new object:

WeatherStation station = new() { Location = "Seattle, WA" };

您可以使用語句傳回預設的函式所建立的實例 return new();You can return an instance created by the default constructor using a return new(); statement.

類似的功能可改善 條件運算式的目標型別解析。A similar feature improves the target type resolution of conditional expressions. 進行這項變更時,這兩個運算式不需要從一個運算式隱含轉換成另一個運算式,但兩者都可能會隱含地轉換成目標型別。With this change, the two expressions need not have an implicit conversion from one to the other, but may both have implicit conversions to a target type. 您可能不會注意到這種變更。You likely won’t notice this change. 您將會注意到,某些條件運算式先前需要轉換或根本無法編譯。What you will notice is that some conditional expressions that previously required casts or wouldn’t compile now just work.

從 c # 9.0 開始,您可以將 static 修飾詞加入至 lambda 運算式匿名方法Starting in C# 9.0, you can add the static modifier to lambda expressions or anonymous methods. 靜態 lambda 運算式類似于 static 區域函數:靜態 lambda 或匿名方法無法捕捉區域變數或實例狀態。Static lambda expressions are analogous to the static local functions: a static lambda or anonymous method can't capture local variables or instance state. static修飾詞可避免意外地捕捉其他變數。The static modifier prevents accidentally capturing other variables.

協變數傳回型別提供覆 方法傳回類型的彈性。Covariant return types provide flexibility for the return types of override methods. 覆寫方法可以傳回衍生自覆寫基底方法之傳回型別的型別。An override method can return a type derived from the return type of the overridden base method. 這對於記錄和支援虛擬複製品或 factory 方法的其他類型而言很有用。This can be useful for records and for other types that support virtual clone or factory methods.

此外, foreach 迴圈也會辨識並使用擴充方法 GetEnumerator ,以其他方式滿足 foreach 模式。In addition, the foreach loop will recognize and use an extension method GetEnumerator that otherwise satisfies the foreach pattern. 這項變更表示 foreach 與其他以模式為基礎的結構(例如非同步模式和以模式為基礎的解構)一致。This change means foreach is consistent with other pattern-based constructions such as the async pattern, and pattern-based deconstruction. 在實務上,這項變更表示您可以將 foreach 支援新增至任何類型。In practice, this change means you can add foreach support to any type. 列舉物件在設計方面有意義時,您應該限制其使用方式。You should limit its use to when enumerating an object makes sense in your design.

接下來,您可以使用捨棄作為 lambda 運算式的參數。Next, you can use discards as parameters to lambda expressions. 這種便利性可讓您避免將引數命名,而編譯器可能會避免使用它。This convenience enables you to avoid naming the argument, and the compiler may avoid using it. 您可以使用 _ 做為任何引數。You use the _ for any argument. 如需詳細資訊,請參閱lambda運算式一文的lambda 運算式章節的輸入參數For more information, see the Input parameters of a lambda expression section of the Lambda expressions article.

最後,您現在可以將屬性套用至 區域函數Finally, you can now apply attributes to local functions. 例如,您可以將 可為 null 的屬性批註 套用至區域函數。For example, you can apply nullable attribute annotations to local functions.

程式碼產生器的支援Support for code generators

有兩個最終功能支援 c # 程式碼產生器。Two final features support C# code generators. C # 程式碼產生器是您可以撰寫的元件,類似于 roslyn 分析器或程式碼修正。C# code generators are a component you can write that is similar to a roslyn analyzer or code fix. 差別在於程式碼產生器會分析程式碼,並在編譯過程中撰寫新的原始程式碼檔。The difference is that code generators analyze code and write new source code files as part of the compilation process. 一般的程式碼產生器會搜尋程式碼中的屬性或其他慣例。A typical code generator searches code for attributes or other conventions.

程式碼產生器會使用 Roslyn 分析 Api 來讀取屬性或其他程式碼元素。A code generator reads attributes or other code elements using the Roslyn analysis APIs. 在該資訊中,它會將新的程式碼加入至編譯中。From that information, it adds new code to the compilation. 來源產生器只能新增程式碼;不允許它們修改編譯中的任何現有程式碼。Source generators can only add code; they aren't allowed to modify any existing code in the compilation.

針對程式碼產生器新增的兩項功能是 部分方法語法 _ 和 _模組初始化運算式*_ 的擴充。The two features added for code generators are extensions to partial method syntax _, and _module initializers*_. 首先是部分方法的變更。First, the changes to partial methods. 在 c # 9.0 之前,部分方法是 private 但無法指定存取修飾詞、傳回 void ,而且不能有 out 參數。Before C# 9.0, partial methods are private but can't specify an access modifier, have a void return, and can't have out parameters. 這些限制表示如果未提供任何方法執行,則編譯器會移除對部分方法的所有呼叫。These restrictions meant that if no method implementation is provided, the compiler removes all calls to the partial method. C # 9.0 會移除這些限制,但需要部分方法宣告才能執行。C# 9.0 removes these restrictions, but requires that partial method declarations have an implementation. 程式碼產生器可以提供該執行。Code generators can provide that implementation. 為了避免引進重大變更,編譯器會考慮任何部分方法,而不使用存取修飾詞來遵循舊規則。To avoid introducing a breaking change, the compiler considers any partial method without an access modifier to follow the old rules. 如果部分方法包含 private 存取修飾詞,則新的規則會管理該部分方法。If the partial method includes the private access modifier, the new rules govern that partial method.

程式碼產生器的第二項新功能是 _ 模組初始化運算式 *。The second new feature for code generators is _*module initializers**. 模組初始化運算式是已 ModuleInitializerAttribute 附加屬性的方法。Module initializers are methods that have the ModuleInitializerAttribute attribute attached to them. 在整個模組中的任何其他欄位存取或方法調用之前,執行時間會呼叫這些方法。These methods will be called by the runtime before any other field access or method invocation within the entire module. 模組初始化運算式方法:A module initializer method:

  • 必須是靜態Must be static
  • 必須是無參數Must be parameterless
  • 必須傳回 voidMust return void
  • 不得為泛型方法Must not be a generic method
  • 不得包含在泛型類別中Must not be contained in a generic class
  • 必須可從包含的模組存取Must be accessible from the containing module

最後一個專案符號點實際上表示方法及其包含的類別必須為內部或公用。That last bullet point effectively means the method and its containing class must be internal or public. 方法不能是區域函數。The method can't be a local function.