public class PathInfo{ public string DirectoryName { get; } public string FileName { get; } public string Extension { get; } public string Path { get { return System.IO.Path.Combine( DirectoryName, FileName, Extension); } } public PathInfo(string path) { DirectoryName = System.IO.Path.GetDirectoryName(path); FileName = System.IO.Path.GetFileNameWithoutExtension(path); Extension = System.IO.Path.GetExtension(path); } public void Deconstruct( out string directoryName, out string fileName, out string extension) { directoryName = DirectoryName; fileName = FileName; extension = Extension; } // ...}顯然,可以和在 C# 1.0 一樣調(diào)用 Deconstruct 方法。但是,C# 7.0 提供了可以顯著簡化調(diào)用的語法糖。如果存在解構(gòu)函數(shù)的聲明,則可以使用新的 C# 7.0“類似元組”的語法調(diào)用它(參見圖 2)。圖 2 解構(gòu)函數(shù)調(diào)用和賦值PathInfo pathInfo = new PathInfo(@"//test/unc/path/to/something.ext");{ // Example 1: Deconstructing declaration and assignment. (string directoryName, string fileName, string extension) = pathInfo; VerifyExpectedValue(directoryName, fileName, extension);}{ string directoryName, fileName, extension = null; // Example 2: Deconstructing assignment. (directoryName, fileName, extension) = pathInfo; VerifyExpectedValue(directoryName, fileName, extension);}{ // Example 3: Deconstructing declaration and assignment with var. var (directoryName, fileName, extension) = pathInfo; VerifyExpectedValue(directoryName, fileName, extension);}請(qǐng)注意,C# 第一次如何允許同時(shí)向不同值的多個(gè)變量賦值。這與將所有變量都初始化為同一值 (null) 的空賦值聲明不同:string directoryName, filename, extension = null;通過新的類似元組的語法,賦予每個(gè)變量一個(gè)不同的值,該值與其名稱不對(duì)應(yīng),但與它出現(xiàn)在聲明和解構(gòu)語句中的順序相對(duì)應(yīng)。正如你所期望的,out 參數(shù)的類型必須與被分配的變量類型相匹配,并且允許使用 var,因?yàn)榇祟愋涂梢詮?Deconstruct 參數(shù)類型中推斷出來。但是,請(qǐng)注意,雖然可以在圓括號(hào)外面放置一個(gè) var(如圖 2 中的示例 3 所示),但此時(shí)即使所有變量的類型均相同,也不能拉出字符串。請(qǐng)注意,此時(shí) C# 7.0 類似元組的語法要求圓括號(hào)內(nèi)至少出現(xiàn)兩個(gè)變量。例如,即使存在類似如下的解構(gòu)函數(shù),也不允許使用 (FileInfo path) = pathInfo;:public void Deconstruct(out FileInfo file)換句話說,不能對(duì)僅有一個(gè) out 參數(shù)的 Deconstruct 方法使用 C# 7.0 解構(gòu)函數(shù)。使用元組
正如我所說過的,前面的每個(gè)示例都利用了 C# 7.0 類似元組的語法。此類語法的特點(diǎn)就是用圓括號(hào)括住分配的多個(gè)變量(或?qū)傩裕N抑允褂眯g(shù)語“類似元組的”,是因?yàn)樗羞@些解構(gòu)函數(shù)示例實(shí)際上在內(nèi)部均未使用任何元組類型。(實(shí)際上,由于已分配的對(duì)象是表示封裝的組成部分的實(shí)例,因此,不允許通過解構(gòu)函數(shù)語法分配元組,也可以說這樣做不太必要。)借助 C# 7.0,現(xiàn)在有了一種特別簡化的語法,可以使用元組,如圖 3 所示。只要允許使用類型說明符,就可以使用這種語法,其中包括聲明、強(qiáng)制轉(zhuǎn)換運(yùn)算符和類型參數(shù)。圖 3 聲明、實(shí)例化并使用 C# 7.0 元組語法[TestMethod]public void Constructor_CreateTuple(){ (string DirectoryName, string FileName, string Extension) pathData = (DirectoryName: @"//test/unc/path/to", FileName: "something", Extension: ".ext"); Assert.AreEqual<string>( @"//test/unc/path/to", pathData.DirectoryName); Assert.AreEqual<string>( "something", pathData.FileName); Assert.AreEqual<string>( ".ext", pathData.Extension); Assert.AreEqual<(string DirectoryName, string FileName, string Extension)>( (DirectoryName: @"//test/unc/path/to", FileName: "something", Extension: ".ext"), (pathData)); Assert.AreEqual<(string DirectoryName, string FileName, string Extension)>( (@"//test/unc/path/to", "something", ".ext"), (pathData)); Assert.AreEqual<(string, string, string)>( (@"//test/unc/path/to", "something", ".ext"), (pathData)); Assert.AreEqual<Type>( typeof(ValueTuple<string, string, string>), pathData.GetType());}[TestMethod]public void ValueTuple_GivenNamedTuple_ItemXHasSameValuesAsNames(){ var normalizedPath = (DirectoryName: @"//test/unc/path/to", FileName: "something", Extension: ".ext"); Assert.AreEqual<string>(normalizedPath.Item1, normalizedPath.DirectoryName); Assert.AreEqual<string>(normalizedPath.Item2, normalizedPath.FileName); Assert.AreEqual<string>(normalizedPath.Item3, normalizedPath.Extension);}static public (string DirectoryName, string FileName, string Extension) SplitPath(string path){ // See http://bit.ly/2dmJIMm Normalize method for full implementation. return ( System.IO.Path.GetDirectoryName(path), System.IO.Path.GetFileNameWithoutExtension(path), System.IO.Path.GetExtension(path) );}如果你不太熟悉元組,可以在輕量級(jí)語法中將多個(gè)類型組合成一個(gè)包含類型,然后在對(duì)其進(jìn)行實(shí)例化的方法外面使用。之所以說是輕量級(jí),是因?yàn)楹投x類/結(jié)構(gòu)不同,元組可通過內(nèi)聯(lián)和動(dòng)態(tài)方式“聲明”。但是,與也支持內(nèi)聯(lián)聲明和實(shí)例化的動(dòng)態(tài)類型不同,元組可以從其包含成員的外部訪問,它們實(shí)際上可以包含在 API 中。雖然外部 API 支持,但元組沒有兼容版本的擴(kuò)展(除非類型參數(shù)本身正好支持推導(dǎo)),因此,在公共 API 中應(yīng)謹(jǐn)慎使用。因此,更好的辦法是對(duì)公共 API 中的返回內(nèi)容使用標(biāo)準(zhǔn)類。在 C# 7.0 之前,該框架已有元組類 System.Tuple<…>(在 Microsoft .NET Framework 4 中引入)。但 C# 7.0 與之前的解決方案不同,因?yàn)樗鼘⒄Z義意圖嵌入到聲明中并引入一個(gè)元組值類型: System.ValueTuple<…>。我們現(xiàn)在來看看語義意圖。請(qǐng)注意,在圖 3 中,C# 7.0 元組語法可讓你為元組包含的每個(gè) ItemX 元素聲明別名。例如,圖 3 中的 pathData 元組實(shí)例已定義強(qiáng)類型 DirectoryName: string、FileName: string 和 Extension: string 屬性,因此,可以調(diào)用(例如)pathData.DirectoryName。這是一項(xiàng)重大改進(jìn),因?yàn)樵?C# 7.0 之前,唯一可用的名稱是 ItemX 名稱,其中 X 將針對(duì)每個(gè)元素增加。現(xiàn)在,雖然 C# 7.0 元組的元素屬于強(qiáng)類型,但這些名稱本身在類型定義中并未區(qū)分。因此,可以分配兩個(gè)使用不同別名的元組,你將得到一條警告,通知你將忽略右邊的名稱:// Warning: The tuple element name 'AltDirectoryName1' is ignored// because a different name is specified by the target type...(string DirectoryName, string FileName, string Extension) pathData = (AltDirectoryName1: @"//test/unc/path/to", FileName: "something", Extension: ".ext");同樣,可以將元組分配到尚未定義部分別名元素名稱的其他元組:// Warning: The tuple element name 'directoryName', 'FileNAme' and 'Extension'// are ignored because a different name is specified by the target type...(string, string, string) pathData = (DirectoryName: @"//test/unc/path/to", FileName: "something", Extension: ".ext");必須確定,每個(gè)元素的類型和順序都定義類型兼容性。僅忽略元素名稱。然而,即使在名稱不同時(shí)被忽略,它們?nèi)匀辉?IDE 中提供 IntelliSense。請(qǐng)注意,無論是否定義元素名稱的別名,所有元組均有 ItemX 名稱,其中 X 對(duì)應(yīng)于元素的數(shù)量。ItemX 名稱很重要,因?yàn)樗鼈兪窃M從 C# 6.0 開始起可用,即使沒有別名元素的名稱也是如此。需要注意的另一點(diǎn)就是,基礎(chǔ) C# 7.0 元組類型是 System.ValueTuple。如果正針對(duì)其進(jìn)行編譯的框架中未提供此類型,可以通過 NuGet 包訪問它。有關(guān)元組內(nèi)部元素的詳細(xì)信息,請(qǐng)參閱 intellitect.com/csharp7tupleiinternals。具有 Is 表達(dá)式的模式匹配
有時(shí)會(huì)存在基類(例如 Storage),以及一系列的派生類、DVD、UsbKey、HardDrive、FloppyDrive 等。要對(duì)每個(gè)類實(shí)施 Eject 方法,請(qǐng)使用以下多個(gè)選項(xiàng):As 運(yùn)算符使用 As 運(yùn)算符轉(zhuǎn)換并賦值檢查結(jié)果是否為 null執(zhí)行 eject 操作Is 運(yùn)算符使用 Is 運(yùn)算符檢查類型轉(zhuǎn)換類型并為其賦值執(zhí)行 eject 操作Cast顯式轉(zhuǎn)換并賦值捕獲可能的異常執(zhí)行操作看起來不怎么樣啊!還有第四種、效果更好的方法,即使用你通過虛擬函數(shù)分派的多形性。但是,僅在具有 Storage 類的源代碼并且可以添加 Eject 方法時(shí),才可以使用這種方法。我假設(shè)的選項(xiàng)不適用于這個(gè)討論,因此需要模式匹配。上述這些方法存在的問題都是語法相當(dāng)冗長,總是要求為需要轉(zhuǎn)換的每個(gè)類提供多個(gè)語句。C# 7.0 提供模式匹配,用作一種將測試和賦值合并為單個(gè)操作的方法。因此,圖 4 中的代碼簡化為如圖 5 中所示的代碼。圖 4 無模式匹配的類型轉(zhuǎn)換// Eject without pattern matching.public void Eject(Storage storage){ if (storage == null) { throw new ArgumentNullException(); } if (storage is UsbKey) { UsbKey usbKey = (UsbKey)storage; if (usbKey.IsPluggedIn) { usbKey.Unload(); Console.WriteLine("USB Drive Unloaded."); } else throw new NotImplementedException(); } else if(storage is DVD) // ... else throw new NotImplementedException();}圖 5 有模式匹配的類型轉(zhuǎn)換// Eject with pattern matching.public void Eject(Storage storage){ if (storage is null) { throw new ArgumentNullException(); } if ((storage is UsbKey usbDrive) && usbDrive.IsPluggedIn) { usbDrive.Unload(); Console.WriteLine("USB Drive Unloaded."); } else if (storage is DVD dvd && dvd.IsInserted) // ... else throw new NotImplementedException(); // Default}這兩種轉(zhuǎn)換方式的區(qū)別并不重要,但如果要經(jīng)常執(zhí)行(例如,針對(duì)每個(gè)派生類型),則前一種語法存在一種繁瑣的 C# 特性。C# 7.0 的改進(jìn)之處是將類型測試、聲明和賦值組合為一個(gè)操作,呈現(xiàn)早期的語法,但不推薦使用。在前一種語法中,檢查類型而不分配標(biāo)識(shí)符會(huì)導(dǎo)致失敗而恢復(fù)“默認(rèn)設(shè)置”,否則會(huì)很麻煩。相比之下,除了類型檢查,分配還考慮到其他條件。請(qǐng)注意,圖 5 中的代碼開始模式匹配 is 運(yùn)算符,也支持 null 比較運(yùn)算符:if (storage is null) { ... }使用 Switch 語句的模式匹配
雖然支持使用 is 運(yùn)算符的模式匹配實(shí)現(xiàn)了改進(jìn),但 switch 語句的模式匹配支持無疑更重要,至少在有多個(gè)可轉(zhuǎn)換的兼容類型時(shí)如此。這是因?yàn)?C# 7.0 包括 case 語句和模式匹配,此外,如果滿足 case 語句中的類型模式,就可以在 case 語句中提供、分配和訪問標(biāo)識(shí)符。圖 6 提供了一個(gè)示例。圖 6 Switch 語句中的模式匹配public void Eject(Storage storage){ switch(storage) { case UsbKey usbKey when usbKey.IsPluggedIn: usbKey.Unload(); Console.WriteLine("USB Drive Unloaded."); break; case DVD dvd when dvd.IsInserted: dvd.Eject(); break; case HardDrive hardDrive: throw new InvalidOperationException(); case null: default: throw new ArgumentNullException(); }}在該示例中,請(qǐng)注意如何在 case 語句中自動(dòng)聲明和分配如 usbKey 和 dvd 的局部變量。正如你所期望的,范圍僅限于 case 語句中。但也許與變量聲明和賦值一樣重要的是附加條件,可以用一個(gè) when 子句附加到 case 語句。結(jié)果是 case 語句完全可以篩選無效的方案,無需在 case 語句內(nèi)部使用額外的篩選器。這帶來額外的好處是:如果事實(shí)上沒有完全滿足前一個(gè) case 語句,也允許計(jì)算下一個(gè) case 語句。這也意味著 case 語句不再僅限于常量,此外,switch 表達(dá)式可以是任何類型,不再僅限于 bool、char、string、integral 和 enum。新的 C# 7.0 模式匹配 switch 語句功能引入的另一個(gè)重要特征就是,case 語句順序很重要并在編譯時(shí)驗(yàn)證。(這與該語言的早期版本形成對(duì)比,早期版本中沒有模式匹配,case 語句順序也不重要。) 例如,如果我在派生自 Storage 的模式匹配 case 語句之前引入了 Storage 的 case 語句(UsbKey、DVD 和 HardDrive),則 case Storage 會(huì)隱藏所有其他的類型模式匹配(派生自 Storage)。如果 case 語句來自隱藏計(jì)算結(jié)果中的其他派生類型 case 語句的基類,將導(dǎo)致隱藏的 case 語句中出現(xiàn)編譯錯(cuò)誤。這樣,case 語句順序要求就類似于 catch 語句。讀者將會(huì)記得 null 值中的 is 運(yùn)算符返回 false。因此,對(duì)于值 null 的 switch 表達(dá)式,類型模式匹配 case 語句不匹配。為此,null case 語句的順序無關(guān)緊要;此行為在模式匹配之前與 switch 語句匹配。此外,為了支持與 C# 7.0 之前的 switch 語句的兼容性,默認(rèn)總是最后評(píng)估 case,而不考慮它出現(xiàn)在 case 語句順序中的位置。(也就是說,由于 case 總是在最后評(píng)估,可讀性通常也會(huì)將它放在最后。) 此外,goto case 語句仍僅適用于常量 case 標(biāo)簽,不適用于模式匹配。本地函數(shù)
雖然已經(jīng)可以聲明委托并為其分配一個(gè)表達(dá)式,但是 C# 7.0 通過允許在另一個(gè)成員內(nèi)部完全聲明本地函數(shù),做出了進(jìn)一步改進(jìn)。請(qǐng)考慮圖 7 中的 IsPalindrome 函數(shù)。圖 7 本地函數(shù)示例bool IsPalindrome(string text){ if (string.IsNullOrWhiteSpace(text)) return false; bool LocalIsPalindrome(string target) { target = target.Trim(); // Start by removing any surrounding whitespace. if (target.Length <= 1) return true; else { return char.ToLower(target[0]) == char.ToLower(target[target.Length - 1]) && LocalIsPalindrome( target.Substring(1, target.Length - 2)); } } return LocalIsPalindrome(text);}在該實(shí)現(xiàn)中,我先檢查傳遞到 IsPalindrome 的參數(shù)不是 null 或僅為空格。(我已使用模式匹配與 “text is null” 進(jìn)行 null 檢查。) 接下來,我聲明函數(shù) LocalIsPalindrome,其中,我以遞歸方式將第一個(gè)和最后一個(gè)字符進(jìn)行比較。這種方法的好處是,我不在可能會(huì)錯(cuò)誤調(diào)用的類范圍內(nèi)聲明 LocalIsPalindrome,進(jìn)而繞過 IsNullOrWhiteSpace 檢查。換句話說,本地函數(shù)提供其他的范圍限制,但僅在周圍函數(shù)內(nèi)部。圖 7 中的參數(shù)驗(yàn)證方案是一種通用的本地函數(shù)用例。我經(jīng)常遇到的另一個(gè)方案發(fā)生在單元測試內(nèi),例如在測試 IsPalindrome 函數(shù)時(shí)(參見圖 8)。圖 8 單元測試通常使用本地函數(shù)[TestMethod]public void IsPalindrome_GivenPalindrome_ReturnsTrue(){ void AssertIsPalindrome(string text) { Assert.IsTrue(IsPalindrome(text), $"'{text}' was not a Palindrome."); } AssertIsPalindrome("7"); AssertIsPalindrome("4X4"); AssertIsPalindrome(" tnt"); AssertIsPalindrome("Was it a car or a cat I saw"); AssertIsPalindrome("Never odd or even");}返回 IEnumerable<T> 的 Iterator 函數(shù)以及 yield 返回元素是另一種通用的本地函數(shù)用例。作為對(duì)該主題的總結(jié),以下列出了大家需要注意的有關(guān)本地函數(shù)的幾個(gè)要點(diǎn):本地函數(shù)不允許使用可訪問性修飾符(public、PRivate、protected)。本地函數(shù)不支持重載。即使簽名未重疊,也不能在名稱相同的同一種方法中使用兩個(gè)本地函數(shù)。編譯器將針對(duì)永不調(diào)用的本地函數(shù)發(fā)出警告。本地函數(shù)可以訪問封閉范圍內(nèi)的所有變量,包括局部變量。此行為與本地定義的 lambda 表達(dá)式相同,除了本地函數(shù)不分配表示結(jié)束的對(duì)象外,其他方面都與本地定義的 lambda 表達(dá)式相同。本地函數(shù)存在于整個(gè)方法的范圍內(nèi),而不考慮是在聲明之前還是之后調(diào)用它們。通過引用返回
從 C# 1.0 開始,可以通過引用 (ref) 將參數(shù)傳遞給函數(shù)。結(jié)果就是對(duì)參數(shù)本身的任何改變都將傳回給調(diào)用方。請(qǐng)考慮以下 Swap 功能:static void Swap(ref string x, ref string y)在這種情況下,被調(diào)用方法可以用新值更新原始調(diào)用方的變量,從而交換第一和第二參數(shù)中存儲(chǔ)的內(nèi)容。從 C# 7.0 開始,除了 ref 參數(shù),還可以通過函數(shù)返回傳回一個(gè)引用。例如,考慮返回圖像中與紅眼相關(guān)聯(lián)的第一像素的函數(shù),如圖 9 所示。圖 9 Ref 返回和 Ref 局部聲明public ref byte FindFirstRedEyePixel(byte[] image){ //// Do fancy image detection perhaps with machine learning. for (int counter = 0; counter < image.Length; counter++) { if(image[counter] == (byte)ConsoleColor.Red) { return ref image[counter]; } } throw new InvalidOperationException("No pixels are red.");}[TestMethod]public void FindFirstRedEyePixel_GivenRedPixels_ReturnFirst(){ byte[] image; // Load image. // ... // Obtain a reference to the first red pixel. ref byte redPixel = ref FindFirstRedEyePixel(image); // Update it to be Black. redPixel = (byte)ConsoleColor.Black; Assert.AreEqual<byte>((byte)ConsoleColor.Black, image[redItems[0]]);}通過返回圖像引用,調(diào)用方然后能夠?qū)⑾袼馗聻椴煌念伾Mㄟ^數(shù)組檢查更新時(shí)發(fā)現(xiàn),該值現(xiàn)在為 black。使用 by reference 參數(shù)的替代方法如下所示,有人可能會(huì)說這種方法不太明顯、可讀性較低:public bool FindFirstRedEyePixel(ref byte pixel);通過引用返回有兩個(gè)重要的限制,并且這兩個(gè)限制都由對(duì)象生命周期造成。對(duì)象引用不應(yīng)被視為垃圾收集,因?yàn)閷?duì)象仍然被引用,當(dāng)它們不再有任何引用時(shí),不應(yīng)消耗內(nèi)存。首先,只能返回以下內(nèi)容的引用:字段、其他引用返回屬性或函數(shù),或作為參數(shù)傳遞到引用返回函數(shù)的對(duì)象。例如,F(xiàn)indFirst-RedEyePixel 返回對(duì)圖像數(shù)組中項(xiàng)目的引用,它是函數(shù)的參數(shù)。同樣,如果圖像存儲(chǔ)為類中的字段,則可以通過引用返回該字段:byte[] _Image;public ref byte[] Image { get { return ref _Image; } }其次,ref 局部變量初始化為內(nèi)存中的某個(gè)存儲(chǔ)位置,且不能修改為指向不同的位置。(不能具有指向一個(gè)引用的指針和修改引用 - 對(duì)于那些有 C++ 背景的人,是指向指針的指針。)以下是需要了解的幾個(gè)按引用返回特征: 如果你要返回一個(gè)引用,則顯然必須返回。因此,這意味著在圖 9 的示例中,即使沒有紅眼像素存在,仍需要返回 ref 字節(jié)。唯一的解決方法是引發(fā)一個(gè)異常。相比之下,by reference 參數(shù)方法可讓你保持參數(shù)不變,并返回一個(gè)指示成功的布爾值。在許多情況下,這種方法可能更可取。聲明一個(gè)引用局部變量時(shí),需要初始化。這涉及到為它分配從函數(shù)或引用返回到變量的 ref:ref string text; // Error雖然可以在 C# 7.0 中聲明引用局部變量,但不允許聲明 ref 類型的字段:class Thing { ref string _Text; /* Error */ }不能為自動(dòng)實(shí)施的屬性聲明 by reference 類型:class Thing { ref string Text { get;set; } /* Error */ }允許使用返回引用的屬性:class Thing { string _Text = "Inigo Montoya"; ref string Text { get { return ref _Text; } } }不能使用值(如 null 或常量)初始化引用局部變量。它必須通過返回成員或局部變量/字段的 by reference 分配: ref int number = null; ref int number = 42; // ERROR輸出變量
從 C# 的第一個(gè)版本開始,調(diào)用包含輸出參數(shù)的方法時(shí),始終要求在調(diào)用方法之前預(yù)先聲明輸出參數(shù)標(biāo)識(shí)符。但 C# 7.0 刪除了這個(gè)特性,并且允許以內(nèi)聯(lián)方式聲明輸出參數(shù)以及方法調(diào)用。圖 10 顯示了一個(gè)例子。圖 10 輸出參數(shù)的內(nèi)聯(lián)聲明public long DivideWithRemainder( long numerator, long denominator, out long remainder){ remainder = numerator % denominator; return (numerator / denominator);}[TestMethod]public void DivideTest(){ Assert.AreEqual<long>(21, DivideWithRemainder(42, 2, out long remainder)); Assert.AreEqual<long>(0, remainder);}請(qǐng)注意,在 DivideTest 方法中,從測試中對(duì) DivideWithRemainder 的調(diào)用如何在 out 修飾符之后包含一個(gè)類型說明符。此外,了解剩余部分如何自動(dòng)繼續(xù)包含在方法的范圍內(nèi),如第二個(gè) Assert.AreEqual 調(diào)用證明。很好!文本改進(jìn)
與以前的版本不同,C# 7.0 包含數(shù)字二進(jìn)制文本格式,如下例所示:long LargestSquareNumberUsingAllDigits = 0b0010_0100_1000_1111_0110_1101_1100_0010_0100; // 9,814,072,356long MaxInt64 { get; } = 9_223_372_036_854_775_807; // Equivalent to long.MaxValue還要注意對(duì)下劃線 “_” 用作數(shù)字分隔符的支持。它只是用來提高可讀性,可以放在數(shù)字位數(shù)(二進(jìn)制、十進(jìn)制或十六進(jìn)制數(shù)字)之間的任何位置。通用的異步返回類型
有時(shí)在實(shí)施異步方法時(shí),能夠同步返回結(jié)果,縮短一個(gè)長時(shí)間運(yùn)行的操作,因?yàn)榻Y(jié)果幾乎是瞬時(shí)的,甚至是已知的。例如,考慮一個(gè)異步方法,用于確定目錄 (bit.ly/2dExeDG) 中文件的總大小。事實(shí)上,如果該目錄中沒有文件,則該方法可以立即返回,而不執(zhí)行長時(shí)間運(yùn)行的操作。直到 C# 7.0,異步語法的要求規(guī)定此類方法的返回結(jié)果應(yīng)當(dāng)是 Task<long>,因此,即使不需要這樣的 Task 實(shí)例,也要實(shí)例化 Task。(要實(shí)現(xiàn)這一點(diǎn),通用模式是從 Task.FromResult<T> 返回結(jié)果。)在 C# 7.0 中,編譯器不再限制異步方法返回到 void、Task 或 Task<T>。現(xiàn)在可以定義自定義類型,例如 .NET Core Framework 提供的 System.Threading.Tasks.ValueTask<T> struct,它們與異步方法返回值兼容。有關(guān)更多信息,請(qǐng)參閱 itl.tc/GeneralizedAsyncReturnTypes。更多的 Expression-Bodied 成員
C# 6.0 引入了函數(shù)和屬性的 expression-bodied 成員,從而簡化了實(shí)現(xiàn)瑣碎的方法和屬性的語法。在 C# 7.0 中,將 expression-bodied 實(shí)現(xiàn)添加到了構(gòu)造函數(shù)、訪問器(get 和 set 屬性實(shí)現(xiàn)),甚至終結(jié)器中(請(qǐng)參見圖 11)。圖 11 在訪問器和構(gòu)造函數(shù)中使用 Expression-Bodied 成員class TemporaryFile // Full IDisposible implementation // left off for elucidation.{ public TemporaryFile(string fileName) => File = new FileInfo(fileName); ~TemporaryFile() => Dispose(); Fileinfo _File; public FileInfo File { get => _File; private set => _File = value; } void Dispose() => File?.Delete();}我希望使用 expression-bodied 成員,這對(duì)于終結(jié)器特別常見,因?yàn)樽畛R姷膶?shí)現(xiàn)是調(diào)用 Dispose 方法,如上圖所示。我很高興地在此說明,對(duì) expression-bodied 成員的額外支持是由 C# 社區(qū)實(shí)施的,而不是 Microsoft C# 團(tuán)隊(duì)。而且還是開源,耶!警告: 此功能在 Visual Studio 2017 RC 中尚未實(shí)現(xiàn)。Throw 表達(dá)式:
圖 11 中的臨時(shí)類可以得到增強(qiáng),在 expression-bodied 成員內(nèi)包括參數(shù)驗(yàn)證;因此,我可以將構(gòu)造函數(shù)更新為:public TemporaryFile(string fileName) => File = new FileInfo(filename ?? throw new ArgumentNullException());如果沒有 throw 表達(dá)式,C# 對(duì) expression-bodied 成員的支持就不能進(jìn)行任何參數(shù)驗(yàn)證。但是,通過 C# 7.0 支持 throw 作為一個(gè)表達(dá)式,而不僅僅是一個(gè)語句,因此,可以在更大的包含表達(dá)式中報(bào)告錯(cuò)誤內(nèi)聯(lián)。警告: 此功能在 Visual Studio 2017 RC 中尚未實(shí)現(xiàn)。總結(jié)
我承認(rèn),當(dāng)我開始寫這篇文章,以為它會(huì)短得多。然而,由于我花了更多的時(shí)間編程和測試這些功能,因此,我發(fā)現(xiàn)有更多的方式實(shí)現(xiàn) C# 7.0,而不僅僅是通過閱讀功能標(biāo)題和遵照語言開發(fā)。在許多情況下,聲明變量、二進(jìn)制文本、throw表達(dá)式等等,沒有太多地涉及理解和使用功能。但有幾種情況(例如,按引用返回、解構(gòu)函數(shù)和元組)需要比最初所預(yù)期的更多地了解功能。在后一種情況下,不僅要了解語法,還要知道功能何時(shí)是相關(guān)的。C# 7.0 繼續(xù)在快速減少的特性列表(預(yù)先聲明的輸出標(biāo)識(shí)符和缺少 throw 表達(dá)式)中削弱,而與此同時(shí)進(jìn)行擴(kuò)展,以包括對(duì)之前在語言級(jí)別看不到的功能的支持(元組和模式匹配)。希望這個(gè)介紹可以幫助你快速進(jìn)入 C# 7.0 編程領(lǐng)域。有關(guān)本文內(nèi)容之后的 C# 7.0 開發(fā)的更多信息,請(qǐng)查看我在 intellitect.com/csharp7 上的博客,以及我的《Essential C# 7.0》一書的更新(預(yù)計(jì)將在 Visual Studio 2017 投入生產(chǎn)后不久面世)。
Mark Michaelis 是 IntelliTect 的創(chuàng)始人,擔(dān)任首席技術(shù)架構(gòu)師和培訓(xùn)師。在近二十年的時(shí)間里,他一直是 Microsoft MVP,并且自 2007 年以來一直擔(dān)任 Microsoft 區(qū)域總監(jiān)。Michaelis 還是多個(gè) Microsoft 軟件設(shè)計(jì)評(píng)審團(tuán)隊(duì)(包括 C#、Microsoft Azure、SharePoint 和 Visual Studio ALM)的成員。他在開發(fā)者會(huì)議上發(fā)表了演講,并撰寫了大量書籍,包括最新的“必備 C# 6.0(第 5 版)”(itl.tc/EssentialCSharp)。可通過他的 Facebook facebook.com/Mark.Michaelis、博客IntelliTect.com/Mark、Twitter @markmichaelis 或電子郵件 mark@IntelliTect.com 與他取得聯(lián)系。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注