如果沒看過上集,記得要看一下哦。這篇文章主軸介紹C# 6.0新增的擴展行為。
C# 6.0
相較於前幾版,這次的改動不大,主軸有兩個,一是補足5.0未完成的async/await無法使用於catch區段和Exception Filter部分,另一個是簡化程式碼,讓可讀性增加。
Initializers for auto-properties
6.0之後,針對自動完成的屬性(指由編譯器幫你產生get與set函式及對應的變數),你可以這樣寫。
public class Customer
{
public string First { get; set; } = "Jeffray";
public string Last { get; set; } = "Huang";
}
簡單的說就是指定那個對應函式的值了。有趣的是編譯器如何擴展,是呼叫產生的set函式來賦值,還是直接設定那個變數的值?有差嗎?當然有,一個有函式呼叫一個沒有(雖然最佳化後可能是一樣的)。
編譯器還是很聰明的。
Getter-only auto-properties
在6.0以前,如果想讓自動屬性對外只公開get機制,那麼會這樣寫。
public class Customer
{
public string First { get; private set; }
public string Last { get; private set; }
public Customer()
{
First = "Test";
}
}
在6.0可以簡化成下面這樣。
public class Customer
{
public string First { get; } = "Jane";
public string Last { get; } = "Doe";
}
除了簡化外,還有差別嗎?有的,編譯器產生的變數變成readonly了。
另外,set函式完全消失了,在6.0前set函式一定會有,只是設成private而已,下面是5.0的。
在5.0這樣寫會出現編譯錯誤。
6.0會變這樣。
Expression bodies on method-like members
這個功能針對單行內文,簡化了函式的敘述。
public class Customer
{
public string First { get; } = "Jane";
public string Last { get; } = "Doe";
public void Print() => Console.WriteLine(First + " " + Last);
}
不難猜出擴展的內容。
也可以用在屬性。
public class Customer
{
public string First { get; } = "Jane";
public string Last { get; } = "Doe";
public string FullName => First + " " + Last;
public void Print() => Console.WriteLine(First + " " + Last);
}
Using static
透過這個功能,6.0可以省略類別名稱,直接呼叫靜態函式。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
WriteLine("Hello World");
Read();
}
}
}
這種展開算是簡單了。
using static不涵蓋Extension Method,例如下面的例子。
Where是Extension Method,雖然他是static,但因為是Extension Method,所以被排除於using static之外,要照原來的寫法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static System.Linq.Enumerable;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int[] data = { 5, 6, 7, 8 };
data.Where(a => a > 5);
Console.Read();
}
}
}
Null-conditional operators
很早C# 就有??運算子,例如下面這樣。
int y = x ?? -1;
意思是當x是null的時候,就使用-1這個值,如果不是就使用x的值,這可以擴展應用成下面這樣。
string s = null;
Console.WriteLine(s ?? "nothing");
意思是當s是null時,印出nothing,當s非null時,印出內容。從語言角度來看,??運算子最後的型別是以左方的型別為準,所以下面的寫法是錯誤的。
string s = null;
Console.WriteLine(s ?? 12);
因為??運算子決議出來的型別是string,??右方是int,所以兩個型別不符。
C# 6.0新增了?運算子,運用範圍更加廣泛,印象中最早有這個運算子的語言就是Apple Swift(當然,範圍是我所知道的語言中)。
static void Main(string[] args)
{
string s = null;
string d = s?.Substring(1, 2);
Console.WriteLine(d ?? "nothing");
Console.ReadLine();
}
結果是nothing,由語言角度上來說,當?運算子左方為null時,右方就不會執行,而以最右方決議出來的型別預設值,以string這種參考型態物件來說是null,如果是int這種value型態物件則是會以Nullable封裝-1,例如下面這樣是錯誤的。
string s = null;
int d = s?.IndexOf("st");
要改成。
string s = null;
int? d = s?.IndexOf("st");
以最初的例子,展開後是這樣。
合併?跟??可以簡化最初的例子。
static void Main(string[] args)
{
string s = null;
Console.WriteLine(s?.Substring(1, 2) ?? "nothing");
Console.ReadLine();
}
如果連續使用?,就會變成這樣。
int? index = s?.Substring(1, 2)?.IndexOf('c');
只要記得使用最右方的?的右方型別為主就是了。在delegate類型上要注意,這樣寫是錯的。
public class Customer
{
public delegate void NameChangedDelegate(string value);
private string _name;
public event NameChangedDelegate NameChanged;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NameChanged ? (_name);
}
}
}
要改成這樣。
NameChanged?.Invoke(_name);
?這個運算子很方便,但要記住一個準則,一行敘述不管有幾個?,只要有一個是null,那麼回傳值就是null,型別則以最右方?運算子右方的型別為準。
String interpolation
簡單的說,就是string.Format的超級簡化版。
static void Main(string[] args)
{
int r = 15 + 20;
Console.WriteLine($"final value is {r}");
Console.Read();
}
展開後。
沒啥特別的,不過15 + 20被變成0x23囉(我是編譯器,我會最佳化哦)。
如果真的要印出{、}符號,用兩次就對了。
Console.WriteLine($"final value is {{r}} {r}");
nameof expressions
簡單的說就是取得變數的真正名稱。
public static int Sum(int x, int y)
{
Console.WriteLine(nameof(x));
return x + y;
}
static void Main(string[] args)
{
Console.WriteLine(Sum(15, 20));
Console.Read();
}
展開後是這樣。
跟你預期的一樣嗎? 老實說我原本預期的是Reflection,看來編譯器越來越聰明了。這功能在處理Exception超好用。
public static int Sum(int x, int y)
{
if (x == -1) throw new ArgumentException($"{nameof(x)} can't be -1");
return x + y;
}
static void Main(string[] args)
{
Console.WriteLine(Sum(-1, 20));
Console.Read();
}
例外會是這樣。
Index initializers
在C# 6.0之後,你可以更迅速的指定Dictionary、Hashtable特定元素的值。
static void Main(string[] args)
{
var numbers = new Dictionary<int, string>
{
[7] = "seven",
[9] = "nine",
[13] = "thirteen"
};
Console.WriteLine(numbers[7]);
Console.Read();
}
結果是seven,展開後。
在Json.NET中,這特別好用。
static void Main(string[] args)
{
var jobj = new JObject()
{
["Version"] = 1,
["Name"] = "smart software"
};
Console.WriteLine(jobj?["Version"]);
Console.Read();
}
Exception filters
指的是你可以在catch 之前呼叫一個傳回boolean值函式,例如下面這樣。
public class TestObject
{
bool Log(Exception ex)
{
Console.WriteLine(ex.Message);
return true;
}
public void Test(string s)
{
try
{
s.Substring(1, 6);
}
catch(Exception ex) when(Log(ex))
{
Console.WriteLine("invalid");
}
}
}
static void Main(string[] args)
{
TestObject o = new TestObject();
o.Test(null);
Console.Read();
}
結果是下面這樣。
當捕捉的例外發生時,Log函式會被呼叫,ex參數會被傳入,如果Log回傳值是true,那麼執行例外處理區段,如果是false,繼續拋出例外,下面為回傳false的狀態。
這東西的展開比較特別,他其實不是展開,所以你反組譯後會像下面這樣,這是無法編譯的。
真實的情況是C# 6.0編譯器支援了IL階級的try..catch..filter..endfilter機制,這個在以前的VB.NET與F#早就支援了。
Await in catch and finally blocks
在C# 5.0時,你不能在catch區段使用async/await,這在C# 6.0支援了。
public static async void Test()
{
HttpClient client = new HttpClient();
try
{
var content = await client.GetStringAsync("s://33333");
}
catch (Exception)
{
var next = await client.GetStringAsync("http://www.google.com");
}
}
展開的結果如下。
private void MoveNext()
{
int num = this.<>1__state;
try
{
string result;
Program.<Test>d__0 d__;
switch (num)
{
case 0:
break;
case 1:
goto Label_0139;
default:
this.<client>5__1 = new HttpClient();
this.<>s__3 = 0;
break;
}
try
{
TaskAwaiter<string> awaiter;
if (num != 0)
{
awaiter = this.<client>5__1.GetStringAsync(
"s://33333").GetAwaiter();
if (!awaiter.IsCompleted)
{
this.<>1__state = num = 0;
this.<>u__1 = awaiter;
d__ = this;
this.<>t__builder.AwaitUnsafeOnCompleted<
TaskAwaiter<string>, Program.<Test>d__0>(
ref awaiter, ref d__);
return;
}
}
else
{
awaiter = this.<>u__1;
this.<>u__1 = new TaskAwaiter<string>();
this.<>1__state = num = -1;
}
result = awaiter.GetResult();
awaiter = new TaskAwaiter<string>();
this.<>s__5 = result;
this.<content>5__4 = this.<>s__5;
this.<>s__5 = null;
this.<content>5__4 = null;
}
catch (Exception exception)
{
this.<>s__2 = exception;
this.<>s__3 = 1;
}
if (this.<>s__3 != 1)
{
goto Label_018A;
}
TaskAwaiter<string> awaiter2 =
this.<client>5__1.GetStringAsync(
"http://www.google.com").GetAwaiter();
if (awaiter2.IsCompleted)
{
goto Label_0156;
}
this.<>1__state = num = 1;
this.<>u__1 = awaiter2;
d__ = this;
this.<>t__builder.AwaitUnsafeOnCompleted<
TaskAwaiter<string>,
Program.<Test>d__0>(ref awaiter2, ref d__);
return;
Label_0139:
awaiter2 = this.<>u__1;
this.<>u__1 = new TaskAwaiter<string>();
this.<>1__state = num = -1;
Label_0156:
result = awaiter2.GetResult();
awaiter2 = new TaskAwaiter<string>();
this.<>s__7 = result;
this.<next>5__6 = this.<>s__7;
this.<>s__7 = null;
this.<next>5__6 = null;
Label_018A:
this.<>s__2 = null;
}
catch (Exception exception2)
{
this.<>1__state = -2;
this.<>t__builder.SetException(exception2);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult();
}
其實就是AsyncVoidMethodBuilder的串接,5.0不實作這個功能大概是因為展開的過程比較複雜或是時間關係吧。