C# 8新功能概覽 - 2

by vivid 25. 十二月 2019 06:10

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:
N191221402
出刊日期: 2019/12/25

在這篇文章中,將延續《C# 8新功能概覽 - 1》一文的內容,介紹一些C# 8版新增的語法。

 

可為 Null 的參考型別(Nullable reference types)

在啟用nullable annotation context後,參考型別的變數都被視為不可為null (nonnullable reference type。若參考型別的變數允許設定null,在宣告變數時,型別之後方可以加上「?」號,此種型別變數稱為

可為 Null 的參考型別(Nullable reference types

那麼為什麼要有可為 Null 的參考型別(Nullable reference types)呢? 我們參考一個簡單的範例,以下「string」型別的「nullable1」變數初始化成null,但下一行程式碼叫用「ToString」方法,在程式執行階段會直接產生「System.NullReferenceException」例外錯誤:

using System;
namespace ConsoleApp1 {
  class Program {
    static void Main( string [] args ) {
      string nullable1 = null;
      nullable1.ToString(); // System.NullReferenceException
    }
  }
}

因此為了避免例外錯誤,你需要撰寫一些防護措失,避免在變數為「null」時叫用了「ToString」方法,例如以下程式碼:

using System;
namespace ConsoleApp1 {
  class Program {
    static void Main( string [] args ) {
      string nullable1 = null;
      if ( nullable1 != null ) {
        nullable1.ToString();
      }

    }
  }
}


或讓程式更精簡一些,使用「?.」運算子,來程式更簡短些,參考以下範例程式碼:

using System;
namespace ConsoleApp1 {
  class Program {
    static void Main( string [] args ) {
      string nullable1 = null;
      nullable1?.ToString();
    }
  }
}

不過有時程式設計師可能會忘記使用這些防呆措失來避免例外錯誤的產生,C# 8可為 Null 的參考型別(Nullable reference types)便可以幫助我們解決這個問題,讓編譯器在程式撰寫階段就自動幫忙做檢查。只要在程式中使用「#nullable enable」這行程式啟用nullable annotation context,而參考型別的變數值可能為null時,編譯器就會顯示警告訊息,參考以下範例程式碼:

using System;
namespace ConsoleApp1 {
  class Program {
    static void Main( string [] args ) {
#nullable enable
      string? nullable2 = null;
       nullable2.ToString(); // Warning CS8602  Dereference of a possibly null reference.ConsoleApp1

#nullable disable
      string? nullable3 = null; //CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context
      nullable3.ToString();

      string nullable1 = null;
      nullable1?.ToString();

    }
  }
}


 

「nullable2.ToString()」這行程式編譯時會顯示警告訊息,請參考下圖所示:

clip_image002

圖 1:編譯階段顯示警告訊息。

「#nullable disable」這行程式則停用nullable annotation context,此時若宣告可為 Null 的參考型別,如範例中的「nullable3」,則編譯器會自動顯示警告訊息,請參考下圖所示:

clip_image004

圖 2:「#nullable disable」。

非同步資料流(Asynchronous stream)

C# 8 版可以使用非同步方式來建立以及使用資料流(Stream)。自.NET Standard 2.1版之後,新增三個介面,「IAsyncDisposable」、「IAsyncEnumerable<T>」、「IAsyncEnumerator<T>」。這些介面可視為「IEnumerable<T>」非同步的版本,.NET Core 3.0已經實作了這三個介面,同樣地,「IDisposable」介面也有一個非同步的版本「IAsyncDisposable」,有了這些介面,意味著你可以在「foreach」與「using」關鍵字之前加上「await」關鍵字,如此將可以用來存取非同步資料流(Asynchronous stream)。

一個回傳非同步資料流的方法必需是一個非同步方法,例如以下範例程式碼,「GetUserList」方法每隔100毫秒回傳序列中一個使用者的名稱,「GetUserList」方法宣告時加上「async」關鍵字;方法回傳型別是「IAsyncEnumerable<T>」,例如範例中的「IAsyncEnumerable<string>」,在方法中透過「yield return」關鍵字回傳資料流中的項目,例如範例中回傳的是陣列項目。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ConsoleApp1 {
  class Program {
    async static Task Main( string [] args ) {
      await foreach ( var name in GetUserList() ) {
        Console.WriteLine( name );
      }
    }
    //C# 8  Asynchronous streams
    static async IAsyncEnumerable<string> GetUserList() {
      string [] list = { "Mary" , "Candy" , "Lulu" , "Lisa" };
      for ( int i = 0 ; i < list.Length ; i++ ) {
        await Task.Delay( 100 );
        yield return list [i];
      }
    }
  }
}

 

 

在「Main」方法使用「foreach」存取非同步資料流中的項目時,需要在「foreach」前方加上「await」關鍵字,這樣每當你讀取序列中的項目時,就會以非同步方式來處理。

現在「using」關鍵字之前也可以加上「await」關鍵字,參考以下範例程式碼:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2 {
  class Program {
    static async Task Main( string [] args ) {
      await WriteAsync( @"c:\temp\a.txt" );
      await ReadAsync( @"c:\temp\a.txt" );
    }
    //C# 8  Asynchronous streams
    static async Task ReadAsync( string file ) {
      await using ( var fs = new FileStream( file , FileMode.Open ) ) {
         using ( var sr = new StreamReader( fs ) ) {
          string s = await sr.ReadToEndAsync();
          Console.WriteLine( s );
        }
      }
    }
    static async Task WriteAsync( string file ) {
      await using ( var fs = new FileStream( file , FileMode.Open ) ) {
        await using ( var sw = new StreamWriter( fs ) ) {
          await sw.WriteLineAsync( " Hello World! " );
          await sw.WriteLineAsync( " Hello World! " );
        }
      }
    }
  }
}

 

「FileStream」與「StreamWriter」都實作了「IAsyncDisposable」介面,因此在使用「using」宣告時,可以加上「await」關鍵字;而「StreamReader」類別並未實作「IAsyncDisposable」介面,也沒有適當的「DisposeAsync」方法,因此不可以加上「await」關鍵字,開發工具會提醒你這個錯誤,請參考下圖所示:

clip_image006

圖 3:語法錯誤。

索引和範圍(Indices and range)

C# 8 新增兩個運算子:hat(^)與range (..),透過簡易的語法來存取序列,例如陣列(Array)或Span<T>中的項目。例如你想把陣列中5到10的項目取出,range運算子便是一個很好的選擇。

.NET新增兩個型別來支援這兩個運算子:

· System.Index:代表序列中的索引。

· System.Range :代表一個範圍。

索引位置若為 「n」,則「^n」表示「length-n」,「length」為序列項目個數。參考以下範例程式碼,有一個字串陣列包含以下序列項目,使用索引「0」(list[0])可以存取到陣列中第一個項目;陣列的「Length」目前為「4」,而索引「^4」(list[^4]),套用「length-n」(4 - 4 = 0),同樣可以存取到陣列中第一個項目,依此類推,索引「1」(list[1])可以存取到陣列中第二個項目;索引「^3」(list[^3]),套用「length-n」(4 - 3 = 1),同樣可以存取到陣列中第二個項目:

string [] list = { "Mary" , "Candy" , "Lulu" , "Lisa" };

Console.WriteLine( $" {list[0]} - {list[^4]} " ); // Mary - Mary

Console.WriteLine( $" {list[1]} - {list[^3]} " ); // Candy - Candy

Console.WriteLine( $" {list[2]} - {list[^2]} " ); // Lulu - Lulu

Console.WriteLine( $" {list[3]} - {list[^1]} " ); // Lisa – Lisa

若使用「^0」,則表示要存取索引「4」的項目,由於目前陣列最大索引為「3」,以下範例程式碼則會產生例外錯誤:

var outOfRange = list [^0]; // Exception

我們也可以指定開始與結尾的索引,例如取回索引「1」到索引「3」的項目,但不包含索引「3」,則會取回陣列中第二與第三個項目:

var secondAndThird = list [1..3];

Console.WriteLine( string.Join( " , " , secondAndThird )); // Candy , Lulu

也可以搭配hat(^)運算子指定開始與結尾的索引,以下範例程式碼將取回陣列最後兩個項目;

var lastTwo = list [^2..^0];

Console.WriteLine( string.Join( " , " , lastTwo ) ); // Lulu , Lisa

若不指定開始索引,則預設以索引「0」開始,以下範例程式碼將取回陣列索引「0」到索引「3」的項目,但不包含索引「3」:

var three = list [..3];

Console.WriteLine( string.Join( " , " , three ) ); // Mary , Candy , Lulu

而以下範例程式碼將取回陣列中所有的項目:

var all = list [..^0];

Console.WriteLine( string.Join( " , " , all ) ); // Mary , Candy , Lulu , Lisa

若不指定結尾索引,則取回自開始索引之後的所有項目,以下範例程式碼將取回陣列索引「2」之後的所有項目:

var from3 = list [2..];

Console.WriteLine( string.Join( " , " , from3 ) ); // Lulu , Lisa

範圍可以儲存在Range結構型別的變數,例如以下範例程式碼取回索引「1」到索引「4」的項目,但不包含索引「4」:

Range r = 1..4;

var secondToFour = list [r];

Console.WriteLine( string.Join( " , " , secondToFour ) ); // Candy , Lulu , Lisa

以下附上範例完整程式碼:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ConsoleApp1 {
  class Program {
    static void Main( string [] args ) {
      string [] list = { "Mary" , "Candy" , "Lulu" , "Lisa" };
      Console.WriteLine( $" {list[0]} - {list[^4]} " ); // Mary - Mary
      Console.WriteLine( $" {list[1]} - {list[^3]} " ); // Candy - Candy
      Console.WriteLine( $" {list[2]} - {list[^2]} " ); // Lulu - Lulu
      Console.WriteLine( $" {list[3]} - {list[^1]} " ); // Lisa - Lisa
     
      //var outOfRange = list [^0]; // Exception

      var secondAndThird = list [1..3];
      Console.WriteLine( string.Join( " , " , secondAndThird )); // Candy , Lulu

      var lastTwo = list [^2..^0];
      Console.WriteLine( string.Join( " , " , lastTwo ) ); //  Lulu , Lisa

      var three = list [..3];
      Console.WriteLine( string.Join( " , " , three ) ); // Mary , Candy , Lulu

      var all = list [..^0];
      Console.WriteLine( string.Join( " , " , all ) ); // Mary , Candy , Lulu , Lisa

      var from3 = list [2..];
      Console.WriteLine( string.Join( " , " , from3 ) ); // Lulu , Lisa

      Range r = 1..4;
      var secondToFour= list [r];
      Console.WriteLine( string.Join( " , " , secondToFour ) ); // Candy , Lulu , Lisa

    }
  }
}

 

Null 聯合指派運算子(Null-coalescing assignment operator)

C# 8 新增Null 聯合指派運算子(??=,null-coalescing assignment operator )可以在左方變數為「null」的情況下,將右方的運算元(Operand)指派給左方。

參考以下範例程式碼,在沒有Null 聯合指派(Null-coalescing assignment)之前,我們可能需要撰寫程式檢查List<string>集合變數的值是否為「null」,若為「null」,則建立集合物件實體,這樣叫用「Add」方法將字串加入集合時才不會產生例外錯誤:

static void AssignVar1() {
  List<string> list = null;
  if ( list == null ) {
    list = new List<string>();
  }
  list.Add( "Hello" );
  Console.WriteLine( string.Join( " , " , list ) );
}

另一種方式是透過Null 聯合運算子(??,null-coalescing operator),可以得到相同執行結果,參考以下範例程式碼:

static void AssignVar2() {
  List<string> list = null;
  list = list ?? new List<string>();
  list.Add( "Hello" );
  Console.WriteLine( string.Join( " , " , list ) );
}

C# 8 則新增Null 聯合指派運算子(??=,Null-coalescing assignment operator),程式可以改寫如下,可以得到相同執行結果:

static void AssignVar3() {
  List<string> list = null;
  list ??= new List<string>();
  list.Add( "Hello" );
  Console.WriteLine( string.Join( " , " , list ) );
}

 

字串插值

字串插值的語法也有改良,若要插值的字串包含多行文字,在C# 7版需先出現「$」號,再出現「@」號,順序不可換,而C# 8版則兩個符號順序可以對調,兩種寫法都可以,參考以下範例程式碼:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ConsoleApp1 {
  class Program {
    static void Main( string [] args ) {
      var h = DateTime.Now.Hour;
      var m = DateTime.Now.Minute;
      Console.WriteLine(
          $@"Now is
            {h}:{m}"
          );

      Console.WriteLine(
         @$"Now is
            {h}:{m}"
         );

      //Now is
      //  5:57
    }
  }
}

 

模式比對(Pattern matching)

模式比對(Pattern matching)可以更容易根據類型(Shape)來驗證與檢查物件。C# 7加入Type Pattern、Constant Pattern,而C# 8 則新增了遞迴模式(Recursive pattern包含Switch 運算式(Switch expression)、位置模式(Positional pattern)、屬性模式(Property pattern)、Tuple模式(Tuple pattern)等等。

Switch 運算式(Switch expression)

Switch 運算式(Switch expression)類似傳統的C# 「switch」陳述式(switch statement)語法,可以根據不同的條件,來產生不同的值,但少掉許多C# 「switch」陳述式必要的「case」、「break」關鍵字,以及大括號 { } 的程式碼,程式可以短,再更短。

「_」符號用來匹配任何運算式(Expression),稱做「Discard Pattern」,通常搭配「Switch 運算式」使用,類似「switch陳述式」的「default」區塊程式碼,參考以下範例程式碼,根據使用者輸入的文字來決定咖啡的大小:

using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApp1 {
  class Class1 {
    static void Main( string [] args ) {
      Console.WriteLine( "請輸入咖啡的大小(L/M/S):" );
      string s = Console.ReadLine();
      string result = string.Empty;
      switch ( s.ToUpper() ) {
        case "L":
          result = "大杯";
          break;
        case "M":
          result = "中杯" ;
          break;
        case "S":
          result = "小杯" ;
          break;
        default:
          result = "Error…" ;
          break;
      }
      Console.WriteLine( result );
    }
  }
}

讓我們使用「switch 運算式」改寫這個範例程式碼:

using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApp1 {
  class Class1 {
    static void Main( string [] args ) {
      Console.WriteLine( "請輸入咖啡的大小(L/M/S):" );
      string s = Console.ReadLine();
      string result = s.ToUpper() switch {
        "L" => "大杯",
        "M" => "中杯",
        "S" => "小杯",
        _ => "Error…"
      };
      Console.WriteLine( result );
    }
  }
}

 

「Switch 運算式」中變數的位置出現在「switch」關鍵字的左方,好讓你不會和「switch陳述式(Statement)」語法混淆在一起。「switch陳述式」中的「case」與「:」號,就改用「=>」符號取代,而「default」就改用「_」符號取代。「=>」符號右方則是一個運算式(expression)。

位置模式(Positional pattern)

位置模式(Positional pattern)也稱做「Deconstruction Pattern」,搭配有實作「Deconstruct」方法的型別來使用。「Deconstruct」方法的主要用途是將多個物件的屬性值,一次指派給多個變數。位置模式(Positional pattern)可以讓你檢視物件的屬性值,把這些值當做模式(pattern)進行比對。

參考以下範例程式碼,有三個「Rectangle」,物件,我們想要透過「DisplayInfo」找出「Area」屬性值為「200」且「Perimeter」屬性值為「60」的矩型比對結果,並印出訊息,範例中採用「switch運算式」來進行比對:

using System;

class Program {
  static void Main( string [] args ) {

    Rectangle rect1 = new Rectangle( 10 , 20 );
    (double area1, double perimeter1) = rect1;
    Console.WriteLine( $"Rectangle 1 Area = {area1}" ); // Rectangle 1 Area = 200
    Console.WriteLine( $"Rectangle 1 Perimeter = {perimeter1}" ); // Rectangle 1 Perimeter = 60


    Rectangle rect2 = new Rectangle( 20 , 10 );
    (double area2, double perimeter2) = rect2;
    Console.WriteLine( $"Rectangle 2 Area = {area2}" ); // Rectangle 2 Area = 200
    Console.WriteLine( $"Rectangle 2 Perimeter = {perimeter2}" ); // Rectangle 2 Perimeter = 60


    Rectangle rect3 = new Rectangle( 5 , 40 );
    (double area3, double perimeter3) = rect3;
    Console.WriteLine( $"Rectangle 3 Area = {area3}" ); // Rectangle 3 Area = 200
    Console.WriteLine( $"Rectangle 3 Perimeter = {perimeter3}" ); // Rectangle 3 Perimeter = 90


    Console.WriteLine( $" {DisplayInfo( rect1 )}  Area = {area1}  Perimeter = {perimeter1}" ); // Yes  Area = 200  Perimeter = 60
    Console.WriteLine( $" {DisplayInfo( rect2 )}  Area = {area2}  Perimeter = {perimeter2}" ); // Yes  Area = 200  Perimeter = 60
    Console.WriteLine( $" {DisplayInfo( rect3 )}  Area = {area3}  Perimeter = {perimeter3}" ); // No ( 200 - 90 )   Area = 200  Perimeter = 90

  }
  static string DisplayInfo( Rectangle rect ) => rect switch
  {
    _ when rect.Area == 200 && rect.Perimeter == 60 => "Yes",
    _ => $"No ( { rect.Area } - { rect.Perimeter } ) "
  };

}


public class Rectangle {
  public Rectangle( int len , int width ) {
    this.Area = len * width;
    this.Perimeter = ( len + width ) * 2;
  }
  public void Deconstruct( out double area , out double perimeter ) {
    area = this.Area;
    perimeter = this.Perimeter;
  }
  public double Area { get; set; }
  public double Perimeter { get; set; }
}

若改用使用位置模式(Positional pattern),程式碼看起來像這樣,顯然程式碼更為簡短了:

using System;

class Program {
  static void Main( string [] args ) {

    Rectangle rect1 = new Rectangle( 10 , 20 );
    (double area1, double perimeter1) = rect1;
    Console.WriteLine( $"Rectangle 1 Area = {area1}" ); // Rectangle 1 Area = 200
    Console.WriteLine( $"Rectangle 1 Perimeter = {perimeter1}" ); // Rectangle 1 Perimeter = 60


    Rectangle rect2 = new Rectangle( 20 , 10 );
    (double area2, double perimeter2) = rect2;
    Console.WriteLine( $"Rectangle 2 Area = {area2}" ); // Rectangle 2 Area = 200
    Console.WriteLine( $"Rectangle 2 Perimeter = {perimeter2}" ); // Rectangle 2 Perimeter = 60


    Rectangle rect3 = new Rectangle( 5 , 40 );
    (double area3, double perimeter3) = rect3;
    Console.WriteLine( $"Rectangle 3 Area = {area3}" ); // Rectangle 3 Area = 200
    Console.WriteLine( $"Rectangle 3 Perimeter = {perimeter3}" ); // Rectangle 3 Perimeter = 90


    Console.WriteLine( $" {DisplayInfo( rect1 )}  Area = {area1}  Perimeter = {perimeter1}" ); // Yes  Area = 200  Perimeter = 60
    Console.WriteLine( $" {DisplayInfo( rect2 )}  Area = {area2}  Perimeter = {perimeter2}" ); // Yes  Area = 200  Perimeter = 60
    Console.WriteLine( $" {DisplayInfo( rect3 )}  Area = {area3}  Perimeter = {perimeter3}" ); // No ( 200 - 90 )   Area = 200  Perimeter = 90

  }

  static string DisplayInfo( Rectangle rect ) => rect switch
  {
    (200, 60 ) => "Yes",
    var (a, p) => $"No ( {a} - {p} )"
  };
}


public class Rectangle {
  public Rectangle( int len , int width ) {
    this.Area = len * width;
    this.Perimeter = ( len + width ) * 2;
  }
  public void Deconstruct( out double area , out double perimeter ) {
    area = this.Area;
    perimeter = this.Perimeter;
  }
  public double Area { get; set; }
  public double Perimeter { get; set; }
}

 

屬性模式(Property pattern)

位置模式(Positional pattern)有一個小問題:解構(Deconstruct)之後,資料的順序為何,以上例來說出現在第一個位置的是「Area」還是「Perimeter」? 要記憶這些順序有點困難。我們可以改用屬性模式(Property pattern)來降低困擾,它又稱做「object pattern」。

我們來看一下以下範例程式碼,「Rectangle」新增一個「Id」屬性,以及一個可傳三個參數的建構函式(Constructor),我們將三個矩型放到陣列之中:

using System;

class Program {
  static void Main( string [] args ) {

    Rectangle rect1 = new Rectangle( 10 , 20 , "rect1" );
    (double area1, double perimeter1) = rect1;
    Console.WriteLine( $"Rectangle 1 Area = {area1}" ); // Rectangle 1 Area = 200
    Console.WriteLine( $"Rectangle 1 Perimeter = {perimeter1}" ); // Rectangle 1 Perimeter = 60


    Rectangle rect2 = new Rectangle( 20 , 10 );
    (double area2, double perimeter2) = rect2;
    Console.WriteLine( $"Rectangle 2 Area = {area2}" ); // Rectangle 2 Area = 200
    Console.WriteLine( $"Rectangle 2 Perimeter = {perimeter2}" ); // Rectangle 2 Perimeter = 60


    Rectangle rect3 = new Rectangle( 5 , 40 , "rect3" );
    (double area3, double perimeter3) = rect3;
    Console.WriteLine( $"Rectangle 3 Area = {area3}" ); // Rectangle 3 Area = 200
    Console.WriteLine( $"Rectangle 3 Perimeter = {perimeter3}" ); // Rectangle 3 Perimeter = 90

    Rectangle [] list = { rect1 , rect2 , rect3 };

    foreach ( var item in list ) {
      if ( item.Area == 200 && item.Perimeter == 60 ) {
        Console.WriteLine( $" Id : {item.Id}  Area = {area1}  Perimeter = {perimeter1}" );
      }
    }

    Console.WriteLine();
    Console.WriteLine( "id is not null" );
    foreach ( var item in list ) {
      if ( item is { Id: { } } ) {
        Console.WriteLine( $" Id : {item.Id}  Area = {area1}  Perimeter = {perimeter1}" );
      }
    }

    Console.WriteLine();
    Console.WriteLine( "id is not null and area is 200" );

    foreach ( var item in list ) {
      if ( item is { Id: { }, Area: 200 } ) {
        Console.WriteLine( $" Id : {item.Id}  Area = {item.Area}  Perimeter = {perimeter1}" );
      }
    }

    Console.WriteLine();
    Console.WriteLine( "id is not null and area is 200 , assign Perimeter to per" );
    foreach ( var item in list ) {
      if ( item is { Id: { }, Area: 200, Perimeter: double per } ) {
        Console.WriteLine( $" Id : {item.Id}  Area = {item.Area}  Perimeter = {per}" );
      }
    }
  }
}


public class Rectangle {
  public Rectangle( int len , int width ) {
    this.Area = len * width;
    this.Perimeter = ( len + width ) * 2;
  }
  public Rectangle( int len , int width , string id ) {
    this.Area = len * width;
    this.Perimeter = ( len + width ) * 2;
    this.Id = id;
  }
  public void Deconstruct( out double area , out double perimeter ) {
    area = this.Area;
    perimeter = this.Perimeter;
  }
  public string Id { get; set; }
  public double Area { get; set; }
  public double Perimeter { get; set; }
}

 

 

第一段「foreach」程式是沒有使用模式比對的標準寫法,找出「Area」屬性值為「200」且「Perimeter」屬性值為「60」的物件。

第二段「foreach」程式中使用到「item is { Id: { } }」,意思是要找出「Id」屬性值不為「null」(使用{ } pattern)的物件。

第三段「foreach」程式中使用到「item is { Id: { }, Area: 200 }」,意思是要找出「Id」屬性值不為「null」(使用{ } pattern),且「Area」屬性值為「200」的物件。

第四段「foreach」程式中使用到「item is { Id: { }, Area: 200, Perimeter: double per }」,意思是要找出「Id」屬性值不為「null」(使用{ } pattern),且「Area」屬性值為「200」的物件,並且將「Perimeter」屬性值指派給「per」變數。這個範例程式的執行結果參考如下:

Rectangle 1 Area = 200

Rectangle 1 Perimeter = 60

Rectangle 2 Area = 200

Rectangle 2 Perimeter = 60

Rectangle 3 Area = 200

Rectangle 3 Perimeter = 90

Id : rect1 Area = 200 Perimeter = 60

Id : Area = 200 Perimeter = 60

id is not null

Id : rect1 Area = 200 Perimeter = 60

Id : rect3 Area = 200 Perimeter = 60

id is not null and area is 200

Id : rect1 Area = 200 Perimeter = 60

Id : rect3 Area = 200 Perimeter = 60

id is not null and area is 200 , assign Perimeter to per

Id : rect1 Area = 200 Perimeter = 60

Id : rect3 Area = 200 Perimeter = 90

Tuple模式(Tuple pattern)

有時你需要依賴多個判斷條件來執行程式碼,「Tuple pattern」可以搭配「switch expression」來處理這個問題,例如,我們常常要判斷台灣 3+2郵遞區號所在的位置,此時就可以撰寫以下程式碼:

using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApp1 {
  class Class1 {
    static void Main( string [] args ) {
      Console.WriteLine( ShowPostcalCode("111","43")); // 士林區     力行街
      Console.WriteLine( ShowPostcalCode("106","88")); // 大安區 仁愛路
      Console.WriteLine( ShowPostcalCode("104","14")); // 中山區     松江路
    }
    static string ShowPostcalCode( string first3 , string last2 ) => ( first3, last2 ) switch
    {
      ("111", "43" ) => "士林區     力行街",
      ("103", "42" ) => "大同區     民生西路",
      ("106", "88" ) => "大安區 仁愛路",
      ("104", "61" ) => "中山區 中山北路",
      ("104", "14" ) => "中山區     松江路",
      (_, _ ) => "unknow"
    };
  }
}

Tags:

.NET Core | .NET Magazine國際中文電子雜誌 | C# | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List