.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.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.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" );
}
}
}
編譯的錯誤訊息如下:
圖 1:命名衝突錯誤。
Null conditional operator
在C#程式中若使用到Null物件進行轉型或某些邏輯判斷動作,可能會產生例外,導致程式無法順利執行,例如以下程式碼中定義一個Employee型別變數,初始化為Null,但下一行程式碼就使用到Employee型別的EmpName屬性:
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那行程式碼會因使用到未配置記憶體的物件,而產生例外錯誤,訊息請參考下圖所示:
圖 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時,便會印出預設值:
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);
}
}
}
此範例的執行結果如下圖所示:
圖 3:觸發事件。
字串格式化
在C# 5之前,若要格式化字串,常常使用到字串參數,例如以下字串中的{0}代表第一個參數,{1}代表第二個參數:
"Now is {0}:{1}"
以主控台程式為例,我們可以使用以下程式碼進行字串格式化,WriteLine方法的第二個參數值會代入{0}參數;而,WriteLine方法的第三個參數值會代入{1}參數:
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}參數:
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」:
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" );
}
}
}
}