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

語言: CN / TW / HK

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

http://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: http://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] 一個專門的屬性:  http://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.tupleelementnamesattribute

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

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

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

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

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

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

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

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

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