天天看點

C# 7.0特性與vs2017

下面是關于在C#7.0語言中計劃功能的說明。其中大部分功能在Visual Studio “15” Preview 4中能運作。現在是最好的試用時期,請記錄下你們的想法。

C#7.0語言增加了許多的新功能,促使專注于資料消費,簡化代碼和性能。

或許最大的特征是元組(tuples) ,使得它易于有多個結果,并進而簡化代碼是以對資料的形狀為條件的模式比對。但也有許多其他的一些功能希望将它們結合起來,讓代碼運作更高效,更明确,進而獲得更多的創造性。如果有哪些運作不是你想要的或者有想改進的功能,在Visual Studio的視窗頂部使用“send feedback”功能将結果回報給我們。在我所描述的許多功能在Preview 4還沒有辦法充分運作,根據使用者的回報結果,我們将在釋出最終版是增加些新的功能。而必須要指出的是,現有計劃中的一些功能在最終版也可能會有所改變或取消。

如果你對這個功能設定感興趣并想學習它,在Roslyn GitHub site上可以找到許多的設計說明和相關讨論。

  其它翻譯版本(1)

輸出(out)變量

目前在C#中,使用out參數并不像我們想象中那麼流暢。在使用out參數調用方法時,你首先必須聲明變量傳遞給它。雖然你通常不會初始化這些變量(他們将通過該方法後所有被覆寫),也不能使用VAR來聲明他們,但是需要指定完整的類型:

public void PrintCoordinates(Point p)
{    int x, y; // have to "predeclare"
    p.GetCoordinates(out x, out y);
    WriteLine($"({x}, {y})");
}      

在C#7.0,我們增加了Out變量,作為out參數傳遞的點來聲明一個變量權:

public void PrintCoordinates(Point p)
{
    p.GetCoordinates(out int x, out int y);
    WriteLine($"({x}, {y})");
}      

需要注意的是,變量是在封閉塊範圍内,是以後續可以使用它們。大多數類型的語句不建立自己的适用範圍,是以out變量通常在聲明中被引入到封閉範圍。

  其它翻譯版本(1)

注:在Preview 4中,适用範圍規則更為嚴格:out變量的作用域為它們在聲明的說法。是以,上面的例子将會在下個版本中使用。

由于out變量直接聲明作為參數傳遞給out參數,編譯器通常可以告知類型(除非有沖突的過載)。是以這是很好用VAR,而不是一個類型來聲明它們:

p.GetCoordinates(out var x, out var y);      

out參數的一個常見的用途是Try...模式,其中out參數一個boolean return表示成功,out參數進行得到的結果:

public void PrintStars(string s)
{    
    if (int.TryParse(s, out var i)) 
    { 
        WriteLine(new string('*', i)); 
    }    
    else 
    {
         WriteLine("Cloudy - no stars tonight!"); 
    }
}              
注:Preview 4處理的比較好的地方在于隻是用if語句定義它。

計劃允許“wildcards”作為out參數以及在*的形式,忽視不重要的out參數:

p.GetCoordinates(out int x, out *); // I only care about x      
注:wildcards能否把它變成C#7.0還是個未知數。

  其它翻譯版本(1)

模式比對

C# 7.0 引入了模式的概念,抽象地說,這是一種文法成分可以用來測試一個值是否有一個一定的“形”以及在它起作用時從值裡面擷取到的額外資訊。

下面是 C# 7.0 中關于模式的例子:

  • c 的常量模式(c 是C#中的一個常量表達式),用于測試輸入的參數是否和 c 相等
  • T x 的類型模式(T 是一個類型,x 是一個辨別符),用于測試輸入的參數是否有類型 T,如果有,提取輸入參數的值到一個 T 類型的新 x 變量中。
  • var x 變量模式(x 是一個辨別符),通常會比對并簡單地将輸入參數的值放進一個新變量 x 中

這是個開始,模式是一種新的 C# 語言元素,而且我們将來可以把它們更多地增加到 C# 中。

  其它翻譯版本(1)

在 C# 7.0 中,我們正在使用模式以增強兩種已存在的語言結構:

  • is 表達式現在在右邊可以有一個模式,而不隻是一個類型
  • case 子句在 switch 語句中現在可以通過模式比對,而不僅僅是常量值

在将來的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));
}      

正如你所看到的,模式變量(變量通過模式引入)與先前描述的 out 變量有些類似,他們可以在表達式中被聲明,而且可以在它們最近的周圍範圍内被使用。也像 out 變量那樣,模式變量是易變的,

注: 就像 out 變量一樣,嚴格的範圍規則适用于 Preview 4.

模式和 Try 方法通常會一起出現:

if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }      

  其它翻譯版本(1)

帶模式的 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 子句不再是必然不相交的,第一個子句比對的将被選擇。是以這裡重要的是上面代碼中 square case 比 rectangle case 來得要早。也是和 catch 子句一樣,編譯器會通過标記明顯不能到達的情況來幫助你。在這之前,你永遠無法知道評價的順序,是以這不是一個重大改變的特性。
  • 預設子句總是最後被評價:即使上面代碼中 null 子句是最後才來,它會在預設子句被選擇前被檢查。這是為了與現有 switch 語義相相容。然而,好的做法通常會讓你把預設子句放到最後。
  • null 子句在最後不可到達:這是因為類型模式遵循目前的 is 表達式的例子并且不會比對空值。這保證了空值不會偶然被任何的類型模式捎來第一搶購。你必須更明确如何處理它們(或為預設子句留下他們)。

通過 case ...: 标簽引入的模式變量僅存在于相對應的 switch 部分的範圍内。

  其它翻譯版本(1)

元組

這是常見的希望從一個方法傳回多個值的做法。目前可用的選項不是最佳的:

  • Out 參數。使用笨拙(即便有上面描述到的提升),它們不使用異步的方法運作。
  • System.Tuple<...>  傳回類型。使用累贅并且需要一個元組對象的配置設定。
  • 為每個方法定制傳輸類型:大量的代碼為了類型開銷的目的僅是臨時收集一些值
  • 匿名類型通過傳回一個 dynamic 傳回類型。高性能開銷并且沒有靜态類型檢查。

為了在這方面做得更好,C# 添加了tuple types 和 tuple literals:

(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}.");      

你也可以在  tuple literals 中直接指定名字:

return (first: first, middle: middle, last: last); // named tuple elements in a literal      

通常來說,你可以互相配置設定元組類型無關的名字,隻要獨立的元素是可以被配置設定的,元組類型會自如 轉換成其他元組類型。特别是對于 tuple literals ,存在一些限制,這會警告或提示在常見的錯誤的情況下提示,例如偶然交換元素的名字。

注意:這些限制還沒在 Preview 4 中實作

元組是值類型,而且他們的元素隻是公開、易變的域。他們的值相等,代表這兩個元組是相等的(都有相同的哈斯碼)如果它們的元素都結對比對(都有相同的哈斯碼)。

這使得元組對于在多種傳回值下的很多情況十分有用。舉例來說,如果你需要一個有多種鍵的詞典,使用元組作為你的鍵,然後一切東西就會如常工作。如果你需要在每個位置有一個有多種值的清單,使用元組,查找清單等等,程式會正常運作。

注意:元組依賴一系列底層類型,它們在 Preview 4 中不被引入。為了将來的工作,你可以通過 NuGget 輕易擷取它們: 在 Solution Explorer 中右鍵點選項目,并選擇“Manage NuGet Packages…” 選擇“Browse”頁籤,檢查“Include prerelease” 并且選擇“nuget.org”作為“Package source” 搜尋“System.ValueTuple”并安裝它

解 構

另一種消除元組(tuple)的方法是解構元組。通過一個解構聲明文法,把一個元組(或者其他的值)拆分為幾部分,并且重新定義為新的變量。

(string first, string middle, string last) = LookupName(id1); // deconstructing decla
rationWriteLine($"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      
   注:仍然還沒有确定是否将通配符引入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版本中,本地函數必須在它們被調用之前聲明。這個限制将被放松,能調用讀取直接指派的局部變量。

轉載于:https://www.cnblogs.com/licin/p/6524508.html

c#