C#7.0中有哪些新特性?

如下將是 C# 7.0 中全部計劃的語言特性的描述。隨着 Visual Studio 「15」 Preview 4 版本的發佈,這些特性中的大部分將活躍起來。如今是時候來展現這些特性,你也告訴藉此告訴咱們你的想法!express

C#7.0 增長了許多新功能,並專一於數據消費,簡化代碼和性能的改善。或許最大的特性就是元祖和模式匹配,元祖能夠很容易地擁有多個返回結果,而模型匹配能夠根據數據的「形」的不一樣來簡化代碼。咱們但願,將它們結合起來,從而使你的代碼更加簡潔高效,也可使你更加快樂並富有成效。安全

請點擊 Visual Studio 窗口頂部的反饋按鈕,告訴咱們哪些是你不期待的特性或者你關於提高這些特性的思考。還有許多功能沒有在 Preview 4 版本中實現。接下來我會描述一些咱們發佈的最終版本里將會起做用的特性,和一些一旦不起做用機即會刪除掉的特性。我也是支持對這些計劃做出改變,尤爲是做爲咱們從你那兒獲得反饋的結果。當最終版本發佈時,這些特性中的一些將會改變或者刪除。數據結構

若是你好奇這些特性的設計過程,你能夠在 Roslyn GitHub site 上找到不少設計筆記和討論。異步

但願 C#7.0 能帶給你快樂!ide

 

輸出變量函數

在當前的 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 storage
    return (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 declaration
WriteLine($"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 array
place = 9; // replaces 7 with 9 in the array
WriteLine(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 *);              // destructors
    public string Name
    {
        get => names[id];                                 // getters
        set => 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的最新特性,纔有助於咱們使用C#進行開發,固然在開發時,也能夠藉助一些使用C#編寫的開發工具。如ComponentOneStudio Enterprise,這是一款專一於企業應用的.NET全功能控件套包,支持WinForms、WPF、UWP、ASP.NET MVC等多個平臺,幫助、在縮減成本的同時,提早交付豐富的桌面、Web和移動企業應用。

文章來源:Mads Torgersen - MSFT

原文連接:https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

相關文章
相關標籤/搜索