C# 二十年語法變遷之 C# 7參考

語言: CN / TW / HK

C# 二十年語法變遷之 C# 7參考

https://benbowen.blog/post/two_decades_of_csharp_iii/

自從 C# 於 2000 年推出以來,該語言的規模已經大大增加,我不確定任何人是否有可能在任何時候都對每一種語言特性都有深入的瞭解。因此,我想寫一系列快速參考文章,總結自 C# 2.0 以來所有主要的新語言特性。我不會詳細介紹它們中的任何一個,但我希望這個系列可以作為我自己(希望你也是!)的參考,我可以不時回過頭來記住我使用的工具工具箱裡有。:)

開始之前的一個小提示:我將跳過一些更基本的東西(例如 C# 2.0 引入了泛型,但它們的使用範圍如此廣泛,以至於它們不值得包括在內);而且我還可以將一些功能“粘合”在一起,以使其更簡潔。本系列並不打算成為該語言的權威或歷史記錄。相反,它更像是可能派上用場的重要語言功能的“備忘單”。您可能會發現瀏覽左側的目錄以搜尋您不認識或需要快速提醒的任何功能很有用。

C# 7.0

元組

元組是包含一組兩個或多個相關物件的結構。它們對於處理或返回通常不會一起關聯到類或結構中的多個相關值很有用。

static void Test() {

// This line creates a 3-tuple (a tuple with 3 items) named 'userDetails'.

// The tuple's three properties are an int named Age, a string named Name, and a float named TestScore

var userDetails = (Age: 30, Name: "Ben", TestScore: 50f);



// ... Later on we can use the tuple like any old struct/class:

var age = userDetails.Age;

var name = userDetails.Name;

var testScore = userDetails.TestScore;

}

• “元組宣告” 如果我們想將元組傳遞給另一個函式或將其儲存在容器中怎麼辦?我們可以使用類似的語法宣告一個元組型別:

static readonly List<(int Age, string Name, float TestScore)> _userList;



static void AddUserToList((int Age, string Name, float TestScore) user) {

_userList.Add(user);

}



// ...



static void Test() {

AddUserToList((30, "Ben", 50f));

}

• “元組型別宣告” 元組的一個很好的用途是更適合替代方法上的“輸出”引數:

static (int Min, int Max) GetLimits(IReadOnlyCollection<int> values) {

return (values.Min(), values.Max());

}

• “元組返回型別”

static void Test() {

var values = new[] { 1, 2, 3, 4, 5, 6, 7 };



// Using the GetLimits() function from previous example

var (min, max) = GetLimits(values);



Console.WriteLine($"Min value is {min}");

Console.WriteLine($"Min value is {max}");

}



// Alternative syntax for pre-existing variables:



static int _min;

static int _max;



static void Test() {

var values = new[] { 1, 2, 3, 4, 5, 6, 7 };



(_min, _max) = GetLimits(values); // No 'var' because we're not declaring new variables.



Console.WriteLine($"Min value is {_min}");

Console.WriteLine($"Min value is {_max}");

}

• “元組解構”

但是請注意,元組不應該被過度使用。當你真正需要的是一個適當的類或結構時,到處使用元組是很危險的。將相關資料一起封裝成“真實”型別!例如,實際上,這些示例中的欄位應封裝為User物件。我建議不要在公共 API 中使用元組(我只在方法/類的實現和私有幫助函式中使用它們)。

可以使用或不使用程式設計師定義的屬性名稱來宣告元組(從 C# 7.1 開始):

static void Test() {

var age = 30;

var name = "Ben";

var testScore = 50f;



var implicitlyNamedTuple = (age, name, testScore);

var explicitlyNamedTuple = (Age: age, Name: name, TestScore: testScore);



//var userAge = implicitlyNamedTuple.age; // Looks ugly! 'age' property is lower-case

var userAge = explicitlyNamedTuple.Age; // Much better :)

}

• “元組屬性命名”

請注意,當自動建立名稱時,編譯器只需複製傳入的引數、欄位、本地或屬性的名稱來建立元組;因此我們的implicitlyNamedTuple的屬性是小寫的。正是出於這個原因,我總是喜歡使用顯式命名(因為當然,C# 中的駝峰命名法對於公共成員來說是非常規的)。

任何元組的基礎型別是ValueTuple<T1, T2, ..., Tn>,其中型別引數的數量等於元組中的專案數。注意不要使用Tuple<>代替ValueTuple<>;這是一種較舊的、大部分已棄用的型別,效率較低,與ValueTuple<>相比幾乎沒有任何優勢。

元組成員的命名實際上是一個編譯器技巧。ValueTuple<>中屬性的“真實”名稱始終是Item1、Item2、Item3等。在宣告元組型別時,編譯器會新增 一個專門的屬性 [1] 在幕後讓它工作,這也是智慧感知的。

儘管元組是值型別,但有趣的是它們是可變的。這有一些效能影響,以及使用可變值型別時通常的“陷阱”。

自定義解構

您可以通過宣告公共Deconstruct()方法 使任何型別像元組一樣可解構。該方法必須返回 void,並且所有引數都必須是引數- 這些將成為將被分配的解構變數。這是一個例子:

class User {

public string Name { get; set; }

public int Age { get; set; }



public void Deconstruct(out string name, out int age) {

name = Name;

age = Age;

}

}



// ...



static void Test() {

var user = new User { Name = "Ben", Age = 30 };



var (name, age) = user;

}

• “使用者類解構器”

簡單模式匹配

這是一系列旨在使編寫某些程式更容易的各種新功能。

is 表示式

對於檢查物件是否是給定型別的例項並同時為所述物件建立該型別的本地別名最有用:

var user = GetUser();



if (user is Manager manager) {

Console.WriteLine($"User is a manager and has {manager.Reports.Count} direct reports!");

}

• “型別模式的“是”表示式”

Null 值永遠不會匹配is 表示式,因此is 表示式可用於過濾掉 null 值:

var userList = GetUserList();



if (userList.FirstOrDefault() is User user) {

Console.WriteLine($"User database contains at least one user!");

}

• “用於空檢查的“是”表示式” 除了型別模式,還支援常量模式。這些都可以在 switch 表示式中使用。當與允許過濾匹配的when 表示式結合使用時,常量模式最有用。

當匹配多個 switch case 時,只輸入遇到的第一個匹配 case(你甚至不能用goto case故意在 case 之間跳轉)。一個例外是預設情況,它總是最後評估。

var user = GetUser();



// Type pattern matching

switch (user) {

case null:

Console.WriteLine("No user found.");

break;

case Manager m when m.Department == Department.Engineering:

Console.WriteLine($"User is a manager in the engineering department and has {m.Reports.Count} direct reports.");

break;

case Manager m when m.Department == Department.Marketing:

Console.WriteLine($"User is a manager in the marketing department and manages {m.CustomerAccounts.Count} customer accounts.");

break;

case Trainee t when t.AgeInYears >= 18:

Console.WriteLine($"User is a trainee and has completed {t.Courses.Count(c => c.IsCompleted)} of their training courses.");

break;

case Trainee t: // This case will only be entered if the one above was not

Console.WriteLine($"User is a junior trainee.");

break;

default:

Console.WriteLine($"User is just a user.");

break;

}

• “使用 When 表示式切換模式匹配” 與is expression類似,null 使用者無法匹配首先檢查其型別的任何大小寫。即使我們將user宣告為型別為Manager的區域性變數,如果GetUser()返回null值,則永遠不會輸入 case case Manager m:(即使我們刪除了case null:)。

區域性函式

此功能允許在函式內宣告函式。這些內部(即本地)函式只能在外部函式的範圍內訪問。

static void PrintUserReport(List<User> users) {

string CreateBioString(User u) {

var bioStart = $"{u.Name}: {u.AgeInYears} years old, {(DateTime.Now - u.JoinDate).TotalYears:N1} years at company";

if (u.AgeInYears <= 18) return bioStart;

else return $"{bioStart}; marital status: {u.MaritalStatus}";

}



foreach (var user in users) {

Console.WriteLine(CreateBioString(user));

}

}



// ... On User.cs ...



bool DueForPayRaise {

get {

bool IsEligible() {

return AgeInYears >= 18 && (DateTime.Now - u.JoinDate).TotalYears >= 1d;

}



return IsEligible() && (DateTime.Now - u.LastPayRaise).TotalYears >= 1d;

}

}

• “本地函式”

內聯“Out”變數宣告

這個簡單的特性允許在使用 out 變數時更加簡潔:

// BEFORE

static void Test() {

int parseResult;

if (Int32.TryParse(someString, out parseResult)) {

// ... Use parseResult here

}

}





// AFTER

static void Test() {

if (Int32.TryParse(someString, out var parseResult)) {

// ... Use parseResult here

}

}

• “內聯輸出變數”

丟擲表示式

這個非常方便的功能也有助於簡潔。它允許您在通常期望值的地方丟擲異常。例子:

User _currentUser;



public User CurrentUser {

get {

return _currentUser;

}

set {

_currentUser = value ?? throw new ArgumentNullException(nameof(value));

}

}

• “Throw Expressions Example A”

public MaritalStatus MaritalStatus {

get {

return _currentUser.AgeInYears >= 18

? _currentUser.MaritalStatus

: throw new InvalidOperationException($"Can not disclose marital status of non-adult.");

}

}

• “Throw Expressions Example B”

參考本地和返回

這個與效能相關的特性允許使用、儲存和返回對變數/資料位置的引用。

從早期的 C# 開始,引用引數允許我們將變數的引用傳遞給方法。現在我們還可以返回對屬性、欄位或其他堆分配變數(例如陣列值)的引用:

int _viewMatricesStartIndex;

int _projectionMatricesStartIndex;

int _worldMatricesStartIndex;

Matrix4x4[] _matrices;



public ref Matrix4x4 GetMatrix(MatrixType type, int offset) {

switch (type) {

case MatrixType.ViewMatrix:

return ref _matrices[_viewMatricesStartIndex + offset];

case MatrixType.ProjectionMatrix:

return ref _matrices[_projectionMatricesStartIndex + offset];

case MatrixType.WorldMatrix:

return ref _matrices[_worldMatricesStartIndex + offset];

default:

throw new ArgumentOutOfRangeException(nameof(type));

}

}

• “Ref Returns” 此方法返回對 _matrices 陣列中 Matrix4x4 的引用,而 不是 [2] 其value 的副本。對於複製大型值型別例項將不可避免的情況,這可以帶來效能優勢。 在方法中使用返回的引用需要宣告一個ref local:

static void Test() {

// Sets _matrices[_viewMatricesStartIndex + 3] to Matrix4x4.Identity

ref var viewMatrix = ref GetMatrix(MatrixType.ViewMatrix, 3);

viewMatrix = Matrix4x4.Identity;



// We can dereference the reference and copy its value to a local here by using the standard local variable declaration syntax

var projMatrix = GetMatrix(MatrixType.ProjectionMatrix, 2);

projMatrix.M11 = 3f; // Changes only the local 'projMatrix', does not affect anything in _matrices

}

• “Ref Locals” 這兩種單獨的語法允許“選擇加入”在按引用返回的方法上使用 ref locals;當我們不想要或不需要它時,一直忽略它。

我們還可以通過直接從 ref-returning 方法返回的引用來設定值:

static void Test() {

// Sets _matrices[_viewMatricesStartIndex + 3] to Matrix4x4.Identity

GetMatrix(MatrixType.ViewMatrix, 3) = Matrix4x4.Identity;

}

• “通過返回的引用設定值”

棄元

此功能允許宣告您忽略所需引數的意圖。使用下劃線 ( _ ) 表示您不想使用 out 引數、表示式的結果或 lambda 引數:

static void Test() {

// Just want to test if this is a valid value; we don't need the parsed value

if (!Int32.TryParse(_userInput, out _)) {

// ..

}



// Don't want the result of this method, just need to invoke it

_ = _someInterface.SomeMethod();



// Don't want to use these parameters in a lambda (C# 9+ only)

_someInterface.DoThing((_, _, param3, _) => param3 == "hello");

}

數字分隔符

此功能允許您使用下劃線分隔整數文字的數字:

const int DecimalConstant = 123_456_789;

const int HexadecimalConstant = 0xAB_CD_EF;

• “數字分隔符”

二進位制字面量

此功能允許以二進位制格式宣告整數常量:

const int BinaryConstant = 0b1110_0011_1101_0001;

• “二進位制文字”

C# 7.1

ValueTask/ValueTask  和 IValueTaskSource

在 C# 中 封裝 future的主要方法是使用 [3] Task和Task 類。在大多數情況下,此範例執行良好,但在嚴格控制的效能場景中,Task / Task 物件的持續建立會對垃圾收集器施加不必要的壓力。

ValueTask和ValueTask 是允許使用類似任務的語義(包括 async/await)但不總是建立引用型別的例項來跟蹤非同步操作的兩種型別。

對於非同步函式,您希望該函式成為熱路徑的一部分,頻繁呼叫,並且該函式通常能夠同步完成,ValueTask很有意義:

// If we assume most users are not logged in, we can avoid allocating a Task object every time we invoke this method for most cases

// In the case where the user IS logged in, we wrap an actual Task<int> which will then be deferred to

public ValueTask<int> GetUserIDAsync(User u) {

if (!u.IsLoggedIn) return ValueTask.FromResult(0);

else return new ValueTask<int>(FetchUserIDFromDatabaseAsync(u)); // Assume FetchUserIDFromDatabaseAsync() returns a Task<int>

}

• “ValueTask 示例” 可以像常規Task或Task 一樣等待 返回的ValueTask或ValueTask 物件-但只能等待一次。

注意:C# 為宣告公共GetAwaiter()方法(或具有通過擴充套件方法定義的方法)的任何型別提供await支援,該方法返回具有一 小組先決條件公共成員 [4] 的物件。ValueTask和ValueTask 實現了這個介面。

注意:實際上,框架 快取了一些常見的 Task 結果 [5] 。 當方法可以同步完成時,這種方法可以消除不必要的垃圾。

ValueTask和ValueTask 都有可以採用 IValueTaskSource/IValueTaskSource [6] 型別的物件的建構函式。這些型別允許您重用/池化物件來處理非同步狀態機和繼續呼叫。這些型別只需要實現IValueTaskSource / IValueTaskSource

實現方法有以下三種:

非同步狀態機將呼叫GetStatus以獲取非同步操作的當前狀態。

非同步狀態機將呼叫GetResult以獲取非同步操作完成時的結果。

OnCompleted將由非同步狀態機呼叫,以將延續傳遞給您的實現,在非同步操作完成時必須呼叫該延續;或者如果已經完成則立即呼叫。 如上所述, 多次等待或從任何 ValueTask 獲取結果是錯誤的 [7] ;這允許我們假設GetResult每次操作只會被呼叫一次(超過這個次數是使用者的錯誤,可以被認為是不受支援的)。同樣,它還允許我們假設一旦呼叫GetResult,IValueTaskSource例項就可以重新用於下一個非同步操作。

傳遞給所有方法的短令牌可用於確保遵守此條件。

預設文字

這個小功能允許在指定型別的預設值時省略型別名稱:

// Before

const int Zero = default(int);



// After

const int Zero = default;

• “預設文字常量”

public string GetUserName(User u) {

// ...

}







// Before

GetUserName(default(User)); // Passes null



// After

GetUserName(default); // Passes null

• “預設文字方法呼叫”

非同步Main函式

此功能允許“一直向上”使用 async/await。它允許使Main()函式(應用程式的入口點)非同步。

public static async Task<int> Main(string[] args) {

try {

await Engine.Initialise();

return 0;

}

catch (Exception e) {

Log.Error(e);

return 1;

}

}

• “Async Main”

C# 7.2

在引數中,只讀結構,只讀引用返回

繼 ref locals 和 return 之後,此功能添加了一些更多功能來傳遞對結構的引用。這些功能主要是為效能敏感的場景提供的。只讀結構是其欄位永遠不能修改的結構(即它是不可變的):

readonly struct BigStruct {

public readonly int Alpha;

public readonly float Bravo;

public readonly int Charlie;

public readonly float Delta;

}

• “只讀結構” 除了幫助您保持不變性外,將結構宣告為只讀還有助於編譯器在使用in引數時避免防禦性副本。in引數與 ref 引數一樣,是通過引用傳遞的引數。然而,另外,in引數是隻讀的:

void Test(in Matrix4x4 viewMatrix) {

viewMatrix.M11 = 123f; // Won't compile even though Matrix4x4 is a mutable struct, 'in' parameters are readonly

}

•In 引數 儘管編譯器盡一切努力防止直接修改通過引用傳入的結構;並不總是可以保證不進行任何修改。因此,為了確保正確性,編譯器必須在某些情況下對引數進行防禦性複製,除非結構型別本身被標記為readonly。

因為in引數是一種效能特性,所以將它們與非只讀結構一起使用幾乎總是一個壞主意。有關詳細資訊,請參閱MSDN 上 的避免將可變結構作為 In 引數 [8]

當呼叫帶有in引數的方法時,in呼叫站點的說明符是可選的。但是,指定它有兩個用途:

// First case: Explicitly invoking an overloaded method that takes an [c]in[/c] parameter:



static void PrintFirstElement(Matrix4x4 m) => Console.WriteLine(m.M11);

static void PrintFirstElement(in Matrix4x4 m) => Console.WriteLine(m.M11);



static void Test() {

var m = GetMatrix();



PrintFirstElement(m); // Invokes first method, passes 'm' by value (i.e. copied)

PrintFirstElement(in m); // Invokes second method, passes 'm' by readonly reference

}

• “在方法過載呼叫時”

// Second case: Forcing the passing of an 'in' parameter to be a reference to live variable



static void PrintFirstElement(in Matrix4x4 m) => Console.WriteLine(m.M11);



static void Test() {

// Matrix4x4.Identity is a static property that returns a new Matrix4x4



PrintFirstElement(Matrix4x4.Identity); // Compiles, because the compiler creates a temporary variable on the stack that is what is referred to

PrintFirstElement(in Matrix4x4.Identity); // Fails, because we're creating a reference to something that only exists as a temporary variable

}

•“在呼叫中以顯式通過引用” 最後,只讀引用返回允許返回對不允許修改它所引用的變數的變數的引用。要使用這樣的引用(而不是獲取返回引用的副本),區域性變數也必須宣告為ref readonly:

static Matrix4x4 _viewMat;



static ref readonly Matrix4x4 GetMatrix() => ref _viewMat;



static void Test() {

ref readonly var mat = ref GetMatrix();

var matCopy = mat;



mat.M11 = 3f; // This line won't compile, we can not modify a readonly ref

matCopy.M11 = 3f; // This line is fine, 'matCopy' is a local stack copy of the variable pointed to by 'mat'

}

• “只讀 Ref Returns and Locals”

Ref struct、Span 、Memory

Ref struct是一種新的結構型別(即值型別),包含“內部指標”;即對物件的資料或偏移量的引用(與對物件本身的引用相反)。ref struct的例項只能存在於堆疊中;因此對它們的使用方式有一些限制(參見下面的第二個示例)。ref struct最突出的用法是 Span [9] 型別。跨度是對包含 0 個或多個相同型別元素的連續記憶體塊的引用。宣告和儲存此記憶體的方式無關緊要 - Span 始終可以指向資料。

static char[] _charArray = { 'A', 'l', 'p', 'h', 'a' };

static List<char> _charList = new List<char> { 'T', 'a', 'u' };



static void PrintCharSpanData(ReadOnlySpan<char> charSpan) {

Console.Write($"Given span is {charSpan.Length} characters long: ");

Console.WriteLine($"\"{new String(charSpan)}\"");

}



unsafe static void Test() {

var heapArraySpan = _charArray.AsSpan();



var listSpan = CollectionsMarshal.AsSpan(_charList);



Span<char> stackArraySpan = stackalloc char[] { 'O', 'm', 'e', 'g', 'a' };



const string UnmanagedDataString = "Epsilon";

var numBytesToAlloc = sizeof(char) UnmanagedDataString.Length;

var pointerSpan = new Span<char>((void) Marshal.AllocHGlobal(numBytesToAlloc), UnmanagedDataString.Length);

UnmanagedDataString.AsSpan().CopyTo(pointerSpan);



var singleCharOnStack = 'O';

var stackSpan = new Span<char>(&singleCharOnStack, 1);



var stringSpan = "Delta".AsSpan();



// =======



PrintCharSpanData(heapArraySpan); // Given span is 5 characters long: "Alpha"

PrintCharSpanData(listSpan); // Given span is 3 characters long: "Tau"

PrintCharSpanData(stackArraySpan); // Given span is 5 characters long: "Omega"

PrintCharSpanData(pointerSpan); // Given span is 7 characters long: "Epsilon"

PrintCharSpanData(stackSpan); // Given span is 1 characters long: "O"

PrintCharSpanData(stringSpan); // Given span is 5 characters long: "Delta"

}

• “Span  宣告和使用” 上面的示例演示了建立char跨度的六種不同方法。但無論如何建立Span ,它都可以以相同的方式使用:作為連續的字元範圍。

ReadOnlySpan 是另一種型別,顧名思義,它是一個Span ,但不允許修改它指向的資料。Span 可隱式轉換為ReadOnlySpan (假設型別引數T相同);這允許我們將Span 傳遞給PrintCharSpanData(),即使該方法採用ReadOnlySpan

上面的程式碼僅作為建立和使用Span  / ReadOnlySpan 的示例。有些操作是“不安全的”或在使用時需要小心。特別注意,手動分配的記憶體(使用AllocHGlobal)應該再次釋放,並且在訪問支援列表的陣列時(通過CollectionsMarshal ),重要的是在相關Span 的使用完成之前不修改列表. 因為Span 、ReadOnlySpan 和任何其他ref struct不得轉義堆疊(或內部引用可能無效),因此對其型別的變數有使用限制:

// Invalid: Ref struct types can not be the element type of an array

// Because arrays are stored on the heap

static readonly Span<int>[] _intSpanArray;



// Invalid: Ref struct types can not be fields or properties of any class or struct except ref structs

// Because class instances are stored on the heap, and struct instances may be boxed (i.e. a copy stored on the heap)

public Span<int> SomeSpan { get; set; }



// Invalid: Ref struct types can not implement interfaces

// Because using them as their interface type would always require boxing

readonly ref struct MyRefStruct : IEquatable<MyRefStruct> { }



// Invalid: Ref struct types can not be cast to object (or boxed in any way)

// Because boxed copies of structs are stored on the heap

var boxedSpan = (object) mySpan;



// Invalid: Ref struct types can not be type arguments

// Because usage of elements can not currently be verified as valid (and some usages will never be valid, i.e. List<T>)

var list = new List<Span<int>>();



// Invalid: Ref struct types can not be closed-over (captured) by a lambda/anonymous function

// Because captured variables must be stored in a heap object so that they're still available when the lambda is executed

var filtered = someEnumerable.Where(x => x[0] == mySpan[0]);



// Invalid: Ref struct types can not be used in an async method (locals or parameters)

// Because locals in async methods may be stored in heap objects to become part of the internal state machine built by the compiler

async Task SomeMethodAsync(Span<int> mySpan) { / ... / }

• “Ref Struct 使用限制” 由於這些限制,提供了另一種稱為Memory 的 非引用結構型別。Memory 必須僅封裝託管堆分配的、GC 跟蹤的記憶體。

static char[] _charArray = { 'A', 'l', 'p', 'h', 'a' };

static List<char> _charList = new List<char> { 'T', 'a', 'u' };



unsafe static void Test() {

// Create a Memory<T> that wraps a new array copy of the data,

// rather than pointing to the actual list data directly like we did with the Span<T> example:

var charListAsMemory = _charList.ToArray().AsMemory();



// Alternatively, create a Memory<T> that encapsulates just part of an existing array

// (this can also be done with Span<T>)

var charArraySubstringAsMemory = new Memory<char>(_charArray, 1, 3);



PrintCharMemoryData(charListAsMemory); // Given memory is 3 characters long: "Tau"

PrintCharMemoryData(charArraySubstringAsMemory); // Given memory is 3 characters long: "lph"

}



static void PrintCharMemoryData(ReadOnlyMemory<char> charMemory) {

Console.Write($"Given memory is {charMemory.Length} characters long: ");

Console.WriteLine($"\"{new String(charMemory.Span)}\""); // Can use the .Span property to create a Span<T> when required

}

• “Memory  例項化和使用示例” Span 和Memory 的 使用指南非常廣泛,但如果編寫使用它們的 API,則應閱讀這些指南。Microsoft 在此處有一個專門討論此主題的頁面: Memory 和 Span 使用指南 [10]

私有保護訪問修飾符

private protected訪問修飾符 將成員的可見性限制為僅在同一程式集中的派生類,而不是預先存在的受保護的內部訪問修飾符將可見性限制為僅派生類或同一程式集中的類。

C# 7.3

列舉、委託和非託管通用約束

列舉約束允許指定型別引數型別必須是列舉:

// 'Enum' constraint lets us ensure that T is an enum type

// 'struct' constraint is optional but lets us make 'valueToHighlight' nullable

static void PrintAllEnumNames<T>(T? valueToHighlight) where T : struct, Enum {

foreach (var value in (T[]) Enum.GetValues(typeof(T))) {

if (value.Equals(valueToHighlight)) Console.WriteLine($"{value} <----");

else Console.WriteLine(value.ToString());

}

}

• “列舉約束” 同樣,委託約束允許指定型別引數型別必須是委託:

// Example from MSDN: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#delegate-constraints



public static TDelegate TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)

where TDelegate : System.Delegate

=> Delegate.Combine(source, target) as TDelegate;

• “委託約束” 非託管泛型約束允許指定型別引數型別必須適合直接/基於指標的操作和/或“blittable”。使用此約束允許您將指標和其他“不安全”構造與您的通用型別變數一起使用:

// This method copies a T reference to a T value via pointer

static unsafe void Copy<T>(ref T src, T dest) where T : unmanaged => dest = src;



static unsafe void Test() {

int dest = 0;

int src = 3;

Copy(ref src, &dest);



Console.WriteLine(dest); // Prints '3'

}

• “非託管約束”

Stackalloc 初始化器

這些允許通過內聯初始化程式初始化堆疊分配的記憶體:

var intArray = stackalloc[] { 1, 2, 3 };

•“堆疊分配的 int 陣列的初始化”

References

[1] 一個專門的屬性:  https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.tupleelementnamesattribute

[2] 不是:  https://docs.microsoft.com/en-us/dotnet/api/system.numerics.matrix4x4?view=net-5.0

[3] future的主要方法是使用:  https://en.wikipedia.org/wiki/Futures_and_promises

[4] 小組先決條件公共成員:  https://source.dot.net/#System.Private.CoreLib/TaskAwaiter.cs,bec733c800db48c1

[5] 快取了一些常見的 Task 結果:  https://source.dot.net/#System.Private.CoreLib/Task.cs,5075

[6] IValueTaskSource/IValueTaskSource:  https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.sources?view=net-5.0

[7] 多次等待或從任何 ValueTask 獲取結果是錯誤的:  https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/#valid-consumption-patterns-for-valuetasks

[8] 的避免將可變結構作為 In 引數:  https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code#avoid-mutable-structs-as-an-in-argument

[9] Span:  https://docs.microsoft.com/en-us/dotnet/api/system.span-1?view=net-5.0

[10] Memory 和 Span 使用指南:  https://docs.microsoft.com/en-us/dotnet/standard/memory-and-spans/memory-t-usage-guidelines