以下將是 C# 7.0 中所有計劃的語言特性的描述。隨著 Visual Studio “15” Preview 4 版本的發布,這些特性中的大部分將活躍起來。現在是時候來展示這些特性,你也告訴藉此告訴我們你的想法!
C#7.0 增加了許多新功能,並專註於資料消費,簡化代碼和效能的改善。或許最大的特性就是元祖和模式比對,元祖可以很容易地擁有多個返回結果,而模型匹配可以根據資料的“形”的不同來簡化代碼。我們希望,將它們結合起來,從而使你的代碼更加簡潔高效,也可以使你更加快樂並富有成效。
請點擊 Visual Studio 視窗頂部的反饋按鈕,告訴我們哪些是你不期待的特性或者你關於提升這些特性的思考。還有許多功能沒有在 Preview 4 版本中實現。接下來我會描述一些我們發布的最終版本裡將會起作用的特性,和一些一旦不起作用機即會刪除掉的特性。我也是支援對這些計劃作出改變,尤其是作為我們從你那兒得到反饋的結果。當最終版本發布時,這些特性中的一些將會改變或者刪除。
如果你好奇這些特性的設計過程,你可以在 Roslyn GitHub site 上找到很多設計筆記和討論。
希望 C#7.0 能帶給你快樂!
輸出變數
在當前的 C# 中,使用輸出參數並不像我們想的那樣方便。在你調用一個無輸出參數的方法之前,首先必須聲明一個變數並傳遞給它。如果你沒有初始化這些變數,你就無法使用 var 來聲明它們,除非先指定完整的類型:
public void PrintCoordinates(Point p){int x, y; // have to "predeclare"p.GetCoordinates(out x, out y);WriteLine($"({x}, {y})");}
在 C#7.0 中,我們正在增加輸出變數和聲明一個作為能夠被傳遞的輸出實參的變數的能力:
public void PrintCoordinates(Point p){p.GetCoordinates(out int x, out int y);WriteLine($"({x}, {y})");}
注意,變數是在封閉塊的範圍內,所以後續也可以使用它們。大多數類型的聲明不建立自己的範圍,因此在他們中聲明的變數通常會被引入到封閉範圍。
Note:在 Preview 4 中,適用範圍規則更為嚴格:輸出變數的範圍是聲明它們的語句,因此直到下個版本發布時,上面的樣本才會起作用。
由於輸出變數直接被聲明為實參傳遞給輸出形參,編譯器通常會告訴他們應該是的類型(除非有衝突過載),所以使用 var 來代替聲明它們的方式是比較好的:
p.GetCoordinates(out var x, out var y);
輸出參數的一種常見用法是Try模式,其中一個布爾傳回值表示成功,輸出參數就會攜帶所獲的結果:
public void PrintStars(string s){if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }else { WriteLine("Cloudy - no stars tonight!"); }}
注意:這裡i只用在 if 語句來定義它,所以 Preview 4 可以將這個處理的很好。
我們計劃允許以 a* 為形式的“萬用字元”作為輸出參數,這會讓你忽略了你不關心參數:
p.GetCoordinates(out int x, out *); // I only care about x
Note:在 C#7.0 中是否會包含萬用字元還不確定。
模式比對
C# 7.0 引入了模式概念。抽象地講,模式是句法元素,能用來測試一個資料是否具有某種“形”,並在被應用時,從值中提取有效資訊。
C#7.0 中的模式樣本:
•C 形式的常量模式(C是C#中的常量運算式),可以測試輸入是否等於C
•T X 形式的類型模式(T是一種類型、X是一個標識符),可以測試輸入是否是T類型,如果是,會將輸入值提取成T類型的新變數X
•Var x 形式的 Var 模式(x是一個標識符),它總是匹配的,並簡單地將輸入值以它原本的類型存入一個新變數X中。
這僅僅是個開始 - 模式是一種新型的 C# 中的語言元素。未來,我們希望增加更多的模式到 C# 中。
在 C#7.0,我們正在加強兩個現有的具有模式的語言結構:
•is 運算式現在具有一種右手側的模式,而不僅僅是一種類型
•switch 語句中的 case 語句現在可以使用匹配模式,不只是常數值
在 C#的未來版本中,我們可能會增加更多的被用到的模式。
具有模式的 IS 運算式
下面是使用 is 運算式的樣本,其中利用了常量模式和類型模式:
public void PrintStars(object o){if (o is null) return; // constant pattern "null"if (!(o is int i)) return; // type pattern "int i"WriteLine(new string('*', i));}
正如你們看到,模式變數(模式引入的變數)和早前描述的輸出變數比較類似,它們可以在運算式中間聲明,並在最近的範圍內使用。就像輸出變數一樣,模式變數是可變的。
註:就像輸出變數一樣,嚴格範圍規則適用於Preview 4。
模式和 Try方法可以很好地協同:
if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }
具有模式的 Switch 語句
我們正在歸納 Switch 語句:
•可以設定任何類型的 Switch 語句(不只是原始類型)
•模式可以用在 case 語句中
•Case 語句可以有特殊的條件
下面是一個簡單的例子:
switch(shape){case Circle c:WriteLine($"circle with radius {c.Radius}");break;case Rectangle s when (s.Length == s.Height):WriteLine($"{s.Length} x {s.Height} square");break;case Rectangle r:WriteLine($"{r.Length} x {r.Height} rectangle");break;default:WriteLine("<unknown shape>");break;case null:throw new ArgumentNullException(nameof(shape));}
關於新擴充的 switch 語句,有幾點需要注意:
•Case 語句的順序現在變得重要:就像 catch 語句一樣,case 語句的範圍現在可以相交,第一個匹配上的會被選中。此外,就像 catch 語句一樣,編譯器通過去除明顯不會進入的 case 來協助你。在此之前,你甚至不需要告訴判斷的順序,所以這並不是一個使用 case 語句的巨大的改變。
•預設的語句還是最後被判斷:儘管 null 的 case 語句在最後語句之前出現,它也會在預設語句被選中之前被測試。這是與現有 Switch 語義相容的。然而,好的做法通常會將預設語句放到最後。
•Switch 不會到最後的 null 語句:這是因為當前 IS 運算式的例子具有類型匹配,不會匹配到 null。這保證了空值不會不小心被任何的類型模式比對上的情況;你必須更明確如何處理它們(或放棄它而使用預設語句)。
通過一個 case 引入模式變數:標籤僅在相應的 Switch 範圍內。
元組
這是一個從方法中返回多個值的常見模式。目前可選用的選項並非是最佳的:
•輸出參數:使用起來比較笨拙(即使有上述的改進),他們在使用非同步方法呼叫是不起作用的。
•System.Tuple<...> 傳回型別:冗餘使用和請求一個元組對象的分配。
•方法的定製傳輸類型:對於類型,具有大量的代碼開銷,其目的只是暫時將一些值組合起來。
•通過動態傳回型別返回匿名型別:很高的效能開銷,沒有靜態類型檢查。
在這點要做到更好,C#7.0 增加的元群組類型和元組文字:
(string, string, string) LookupName(long id) // tuple return type{... // retrieve first, middle and last from data storagereturn (first, middle, last); // tuple literal}
這個方法可以有效地返回三個字串,以元素的形式包含在一個元組值裡。
這種方法的調用將會收到一個元組,並且可以單獨地訪問其中的元素:
var names = LookupName(id);WriteLine($"found {names.Item1} {names.Item3}.");
Item1 等是元組元素的預設名稱,也可以被一直使用。但他們不具有描述性,所以你可以選擇添加更好的:
(string first, string middle, string last) LookupName(long id) // tuple elements have names
現在元組的接收者有多個具有描述性的名稱可用:
var names = LookupName(id);WriteLine($"found {names.first} {names.last}.");
你也可以直接在元組文字指定元素名稱:
return (first: first, middle: middle, last: last); // named tuple elements in a literal
一般可以給元群組類型分配一些彼此無關的名稱:只要各個元素是可分配的,元群組類型就可以自如地轉換為其他的元群組類型。也有一些限制,特別是對元組文字,即常見的和警示錯誤,如不慎交換元素名稱的情況下,就會出現錯誤。
Note:這些限制尚未在 Preview 4 中實現。
元組是實值型別的,它們的元素是公開的,可變的。他們有值相等,如果所有的元素都是成對相等的(並且具有相同的雜湊值),那麼這兩個元組也是相等的(並且具有相同的雜湊值)。
這使得在需要返回多個值的情況下,元組會非常有用。舉例來說,如果你需要多個 key 值的字典,使用元組作為你的 key 值,一切會非常順利。如果你需要在每個位置都具有多個值的列表,使用元組進行列表搜尋,會工作的很好。
Note:元組依賴於一組基本類型,卻不包括在 Preview 4 中。為了使該特性工作,你可以通過 NuGet 擷取它們:
•按右鍵 Solution Explorer 中的項目,然後選擇“管理的NuGet包......”
•選擇“Browse”選項卡,選中“Include prerelease”,選擇“nuget.org”作為“Package source”
•搜尋“System.ValueTuple”並安裝它。
解構
消耗元組的另一種方法是將解構它們。一個解構聲明是一個將元組(或其他值)分割成部分並單獨分配到新變數的文法:
(string first, string middle, string last) = LookupName(id1); // deconstructing declarationWriteLine($"found {first} {last}.");
在解構聲明中,您可以使用 var 來聲明單獨的變數:
(var first, var middle, var last) = LookupName(id1); // var inside
或者將一個單獨的 var 作為一個縮寫放入圓括弧外面:
var (first, middle, last) = LookupName(id1); // var outside
你也可以使用解構任務來解構成現有的變數
(first, middle, last) = LookupName(id2); // deconstructing assignment
解構不只是應用於元組。任何的類型都可以被解構,只要它具有(執行個體或擴充)的解構方法:
public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }
輸出參數構成瞭解構結果中的值。
(為什麼它使用了參數,而不是返回一個元組?這是為了讓你針對不同的值擁有多個重載)。
class Point{public int X { get; }public int Y { get; }public Point(int x, int y) { X = x; Y = y; }public void Deconstruct(out int x, out int y) { x = X; y = Y; }}(var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY);
這是一種常見的模式,以一種對稱的方式包含了構建和解構。
對於輸出變數,我們計劃在解構中加入萬用字元,來化簡你不關心的變數:
(var myX, *) = GetPoint(); // I only care about myX
Note:萬用字元是否會出現在C#7.0中,這仍是未知數。
局部函數
有時候,一個輔助函數可以在一個獨立函數內部起作用。現在,你可以以一個局部函數的方式在其它函數內部聲明這樣的函數:
public int Fibonacci(int x){if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));return Fib(x).current;(int current, int previous) Fib(int i){if (i == 0) return (1, 0);var (p, pp) = Fib(i - 1);return (p + pp, p);}}
閉合範圍內的參數和局部變數在局部函數的內部是可用的,就如同它們在 lambda 運算式中一樣。
舉一個例子,迭代的方法實現通常需要一個非迭代的封裝方法,以便在調用時檢查實參。(迭代器本身不啟動運行,直到 MoveNext 被調用)。局部函數非常適合這樣的情境:
public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter){if (source == null) throw new ArgumentNullException(nameof(source));if (filter == null) throw new ArgumentNullException(nameof(filter));return Iterator();IEnumerable<T> Iterator(){foreach (var element in source) {if (filter(element)) { yield return element; }}}}
如果迭代器有一個私人方法傳遞給過濾器,那麼當其它成員意外的使用迭代器時,迭代器也變得可用(即使沒有參數檢查)。此外,還會採取相同的實參作為過濾器,以便替換範圍內的參數。
注意:在 Preview 4,局部函數在調用之前,必須被聲明。這個限制將會被鬆開,以便使得局部函數從定義分配中讀取時,能夠被調用。
文字改進
C#7.0 允許 _ 出現,作為數字分隔號:
var d = 123_456; var x = 0xAB_CD_EF;
你可以將 _ 放入任意的數字之間,以提高可讀性,它們對值沒有影響。
此外,C#7.0 引入了二進位文字,這樣你就可以指定二進位模式而不用去瞭解十六進位。
var b = 0b1010_1011_1100_1101_1110_1111;
引用返回和局部引用
就像在 C# 中通過引用來傳遞參數(使用引用修改器),你現在也可以通過引用來返回參數,同樣也可以以局部變數的方式儲存參數。
public ref int Find(int number, int[] numbers){for (int i = 0; i < numbers.Length; i++){if (numbers[i] == number) {return ref numbers[i]; // return the storage location, not the value}}throw new IndexOutOfRangeException($"{nameof(number)} not found");}int[] array = { 1, 15, -39, 0, 7, 14, -12 };ref int place = ref Find(7, array); // aliases 7's place in the arrayplace = 9; // replaces 7 with 9 in the arrayWriteLine(array[4]); // prints 9
這是繞過預留位置進入大資料結構的好方法。例如,一個遊戲也許會將它的資料儲存在大型預分配的陣列結構中(為了避免記憶體回收機制暫停)。方法可以將直接引用返回成一個結構,通過它的調用者可以讀取和修改它。
也有一些限制,以確保安全:
•你只能返回“安全返回”的引用:一個是傳遞給你的引用,一個是指向對象中的引用。
•本地引用會被初始化成一個本機存放區,並且不能指向另一個儲存。
非同步傳回型別
到現在為止,C# 的非同步方法呼叫必須返回 void,Task 或 Task<T>。C#7.0 允許其它類型以這種能從一個方法中返回的方式被定義,因為它們可以以非同步方法呼叫被返回的方式來定義其它類型。
例如我們計劃建立一個 ValueTask<T> 結構類型的資料。建立它是為了防止非同步啟動並執行結果在等待時已可用的情境下,對 Task<T> 進行分配。對於許多執行個體中設計緩衝的非同步情境,這可以大大減少分配的數量並顯著地提升效能。
Note:非同步傳回型別尚未在 Preview 4 中提供。
更多的 expression bodied 成員:
expression bodied 的方法和屬性是對 C# 6.0 的巨大提升。C# 7.0 為 expression bodied 事件列表增加了訪問器,結構器和終結器。
class Person{private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();private int id = GetId();public Person(string name) => names.TryAdd(id, name); // constructors~Person() => names.TryRemove(id, out *); // destructorspublic string Name{get => names[id]; // gettersset => names[id] = value; // setters}}
Note:這些額外增加的 expression bodied 的成員尚未在 Preview 4 中提供。
這是社區共用的樣本,而不是 Microsoft C# 編譯團隊提供的,還是開源的!
Throw 運算式
在運算式中間拋出一個異常是很容易的:只需為自己的代碼調用一個方法!但在 C#7.0 中,我們允許在任意地方拋出一個運算式:
class Person{public string Name { get; }public Person(string name) => Name = name ?? throw new ArgumentNullException(name);public string GetFirstName(){var parts = Name.Split(" ");return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");}public string GetLastName() => throw new NotImplementedException();}
Note:Throw 運算式尚未在Preview 4中提供。
以上所述是小編給大家介紹的C#7.0中新特性匯總,希望對大家有所協助,如果大家有任何疑問請給我留言,小編會及時回複大家的。在此也非常感謝大家對雲棲社區網站的支援!