C# 6 新語法 - 1

by vivid 17. 六月 2015 12:13

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:N150616102
出刊日期:2015/6/17
開發工具:Visual Studio 2015 RC
版本:.NET Framework 4.6、C# 6

在《Visual Studio 2015 IDE新功能》這篇文章中,介紹了Visual Studio 2015 RC版的IDE新功能,初窺工具所帶來的便利功能,而在這篇文章之中,我將介紹C# 6程式語言所提供的新功能。因為Visual Studio 2015中文版還未問市,因此在此篇文章之中,專有名詞的部分就儘量使用原文。

引用靜態成員(Using static members)

C# 5版之前,若使用using語法,只能夠引用到命名空間,若是為類別取個別名。若使用using語法引用命名空間,如此使用到此命名空間的類別就不用寫全名,例如以下程式,引用System之後,就可以直接使用Console類別,叫用它的方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CS6
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello");
        }
    }
}

而C# 6版的using static語法,可以引用到類別,我們將上列的程式碼改寫如下,在程式檔案上方,使用using和static關鍵字,引用到System.Console類別,後續程式碼就可以直接叫用Console類別的WriteLine方法,使得程式碼更為精簡:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace CS6 {
  class Program {
    static void Main ( string [ ] args ) {
     WriteLine ( "Hello" );
    }
  }
}

 

在撰寫程式時,有很多常用的類別都可以比照辦理,像是DateTime、Math類別等等,例如以下程式碼片段引用了常用的Console、DateTime、Math類別等等:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
using static System.DateTime;
using static System.Math;
namespace CS6 {
  class Program {
    static void Main ( string [ ] args ) {
      WriteLine ( "Hello" );
      WriteLine ( Now);
      WriteLine (PI);
    }
  }
}

 

當然使用此語法時要適當的自行處理不同命名空間下,有相同類別名稱的命名衝突問題。例如以下程式碼在A與B兩個命名空間下,都包含一個MyClass類別,其中有一個Print()方法,若同時使用using static語法引用到A與B命名空間,則在Main方法叫用到Print()方法時,這段程式碼將不能編譯:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static A.MyClass;
using static B.MyClass;
namespace ConsoleApplication1 {
  class Program {
    static void Main ( string [ ] args ) {
      Print ( );
    }
  }
}

namespace A {
  class MyClass {
   public static void Print() {
      Console.WriteLine ("A");
    }
  }
}

namespace B{
    class MyClass {
    public static void Print ( ) {
      Console.WriteLine ( "B" );
    }
  }
}

 

編譯的錯誤訊息如下:

clip_image002

圖 1:命名衝突錯誤。

Null conditional operator

在C#程式中若使用到Null物件進行轉型或某些邏輯判斷動作,可能會產生例外,導致程式無法順利執行,例如以下程式碼中定義一個Employee型別變數,初始化為Null,但下一行程式碼就使用到Employee型別的EmpName屬性:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApplication1 {
  class Program {
    static void Main( string[ ] args ) {
      Employee emp = null;
      Console.WriteLine( emp.EmpName );
    }
  }
  class Employee {
    public string EmpName;
  }
}

 

執行時,在上述的Console.WriteLine那行程式碼會因使用到未配置記憶體的物件,而產生例外錯誤,訊息請參考下圖所示:

clip_image004

圖 2:NullReferenceException錯誤。

我們需要在程式中進行判斷,在emp變數不為null的情況下,才存取Employee類別的EmpName屬性,修改程式碼如下:

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

namespace ConsoleApplication1 {
  class Program {
    static void Main ( string [ ] args ) {
      Employee emp = null;
      if ( emp != null ) {
        Console.WriteLine ( emp.EmpName );
      }

    }
  }
  class Employee {
    public string EmpName;
  }
}

 

在C# 6中可以使用  Null conditional operator 來解決這個問題,不必再自己判斷變數是否為Null,修改上面程式如下,只要一行程式碼,就可以判斷Null問題,再執行程式就不會有例外錯誤:

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

namespace ConsoleApplication1 {
  class Program {
    static void Main ( string [ ] args ) {
      Employee emp = null;
      Console.WriteLine ( emp?.EmpName );
    }
  }
  class Employee {
    public string EmpName;
  }
}


 

此外我們也可以搭配??運算子一起使用,在變數為Null時,直接設定預設值,改寫上例程式碼如下:

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

namespace ConsoleApplication1 {
  class Program {
    static void Main ( string [ ] args ) {
      Employee emp = null;
      var r = emp?.EmpName ?? "NoName";
      Console.WriteLine ( r );

    }
  }
  class Employee {
    public string EmpName;
  }
}

 

執行後,當emp為null時,便會印出預設值:

clip_image006

Null conditional operator 可以搭配事件的語法,來檢查事件是否被註冊,若事件已註冊,再觸發事件,在C# 5你需撰寫類似以下if的語法:

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

namespace ConsoleApplication1 {
  class MyButton {
    public event System.EventHandler Click;
    public void OnClick ( EventArgs e ) {
      if ( Click != null ) {
        Click ( this , e );
      }
    }
  }
  class Program {
    static void Main ( string [ ] args ) {
      var btn = new MyButton();
      btn.Click += ( sender , e ) => {
        Console.WriteLine ( "Btn Click" );       
      };
      btn.OnClick(EventArgs.Empty);
    }
  }
}

 

 

範例中的MyButton類別包含一個Click事件,而onClick方法中判斷是否此事件被其它物件註冊,若有,才觸發Click事件。現在C# 6 可以這樣寫:

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

namespace ConsoleApplication1 {
  class MyButton {
    public event System.EventHandler Click;
    public void OnClick ( EventArgs e ) {
      Click?.Invoke ( this , e );
    }
  }
  class Program {
    static void Main ( string [ ] args ) {
      var btn = new MyButton();
      btn.Click += ( sender , e ) => {
        Console.WriteLine ( "Btn Click" );       
      };
      btn.OnClick(EventArgs.Empty);
    }
  }
}

 

此範例的執行結果如下圖所示:

clip_image008

圖 3:觸發事件。

字串格式化

在C# 5之前,若要格式化字串,常常使用到字串參數,例如以下字串中的{0}代表第一個參數,{1}代表第二個參數:

"Now is {0}:{1}"

以主控台程式為例,我們可以使用以下程式碼進行字串格式化,WriteLine方法的第二個參數值會代入{0}參數;而,WriteLine方法的第三個參數值會代入{1}參數:

using System;
namespace CS6 {
  class Program {
    static void Main ( string [ ] args ) {
      var h = DateTime.Now.Hour;
      var m = DateTime.Now.Minute;
      Console.WriteLine ( "Now is {0}:{1}" , h , m ); //Now is 5:57
    }
  }
}

 

現在在C# 6之中,你可以直接在參數中引用變數的值,只要在字串前方加上 「$」符號,就可以使用到 h與m變數,並把h與m變數的值,直接代入{h}與{m}參數:

using System;
namespace CS6 {
  class Program {
    static void Main ( string [ ] args ) {
      var h = DateTime.Now.Hour;
      var m = DateTime.Now.Minute;
      Console.WriteLine ( $"Now is {h}:{m}"); //Now is 5:57
    }
  }
}

此外也可以自訂顯示格式,例如以下程式碼,不足兩位數時前方將自動補「0」:

using System;
namespace CS6 {
  class Program {
    static void Main ( string [ ] args ) {
      var h = DateTime.Now.Hour;
      var m = DateTime.Now.Minute;
      Console.WriteLine ( $"Now is {h:00}:{m:00}" ); //Now is 05:57
    }
  }
}

 

而以下的範例程式碼,展示貨幣的格式化語法,新的語法讓使用字串格式化的動作變得更為簡單:

using System;
namespace CS6 {
  class Program {
    static void Main ( string [ ] args ) {
      double money =12345.67;
      Console.WriteLine ( $"Money is {money:c}" ); //Money is $12,345.67
      Console.WriteLine ( $"Money is {money:#,###.000}" ); //Money is 12,345.670
    }
  }
}

 

Index initializers

在C#中可以宣告Dictionary<T,T>類型的泛型集合,讓集合存放key=value配對出現的資料,後續可以根據key取得對應的值,例如以下範例程式碼定義一個key與value都為string型別的泛型集合,並叫用集合的Add方法新增兩個項目到集合之中,並根據ID、Name將對應的值讀出:

using static System.Console;
using System;
using System.Collections.Generic;

namespace ConsoleApplication1 {
  class Program {
    static void Main ( string [ ] args ) {
      var col = new Dictionary<string , string>( );
      col.Add ( "ID" , "1" );
      col.Add ( "Name" , "Mary" );
      WriteLine ( col [ "ID" ] );  //1
      WriteLine ( col [ "Name" ] ); //Mary
    }
  }
}


 

在C# 3可以使用Collection Initializer搭配Object Initializer,宣告集合那行順帶初始化集合成員,程式就變更短了,改寫上例程式碼如下:

using System;
using System.Collections.Generic;

namespace ConsoleApplication1 {
  class Program {
    static void Main ( string [ ] args ) {
      var col =new Dictionary<string , string>( ) {
        { "ID", "1"},
          { "Name","Mary"}
      };

      Console.WriteLine ( col [ "ID" ] );  //1
      Console.WriteLine ( col [ "Name" ] ); //Mary
    }
  }
}

 

而在C# 6可以改用Index initializers來簡化語法,以下程式碼改寫上例,改用新語法達到相同效果,程式又更短了:

using System;
using System.Collections.Generic;

namespace ConsoleApplication1 {
  class Program {
    static void Main ( string [ ] args ) {
      var col =new Dictionary<string , string>( ) {
        ["ID"] = "1",
        ["Name"] = "Mary"
      };

      Console.WriteLine ( col [ "ID" ] );  //1
      Console.WriteLine ( col [ "Name" ] ); //Mary
    }
  }
}

 

Exception Filters

Visual Basic、F#都已提供Exception Filters語法,現在C# 6也支援了。當你使用try..catch語法攔截例外錯誤時,可以在catch區段搭配 when關鍵字,設定篩選條件,以篩選要攔截的例外錯誤,參考以下程式碼範例:

using System;
using System.IO;

namespace CS6 {
  class Program {
    static void Main ( string [ ] args ) {
      int i = 0;
      try {
        int j = 10 / i;
        int k = int.Parse("x");
      
      } catch ( Exception ex ) when (ex.Message == "Input string was not in a correct format.") {
        Console.WriteLine ( "Format Error" );
      } catch ( Exception ex ) when (ex.Message == "Attempted to divide by zero.") {
        Console.WriteLine ( "Error" );
      }
    }
  }
}

Tags:

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

新增評論




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






NET Magazine國際中文電子雜誌

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

月分類Month List