洪流學(xué)堂羽嫡,讓你快人幾步污茵!本文首發(fā)于洪流學(xué)堂微信公眾號。
本文是該系列《Unity腳本運行時更新帶來了什么疚鲤?》的第4篇掂咒。
洪流學(xué)堂公眾號回復(fù)runtime
才沧,獲取本系列所有文章。
Unity2017-2018.2中的4.x運行時已經(jīng)支持到C#6绍刮,Unity2018.3將支持到C# 7.3温圆,看看C#6新特性能給代碼帶來什么吧。
C#6 新特性
String填空
String.Format
非常常用孩革,但使用起來很麻煩而且容易出錯岁歉。在格式字符串中需要使用類似{0}的占位符,還得單獨提供對應(yīng)的參數(shù):
var s = String.Format("{0} is {1} year{{s}} old", p.Name, p.Age);
字符串填空可以讓你直接將表達式放在字符串的“空”中膝蜈,就是在最前面加上$
:
var s = $"{p.Name} is {p.Age} year{{s}} old";
和 String.Format
類似锅移,可選的對齊和格式都可以指定:
var s = $"{p.Name,20} is {p.Age:D3} year{{s}} old";
填空的內(nèi)容可以是任何表達式:
var s = $"{p.Name} is {p.Age} year{(p.Age == 1 ? "" : "s")} old";
請注意,條件表達式是在括號里饱搏,因此:"s"
不會與格式說明符混淆非剃。
自動屬性的增強
自動屬性的初始化
現(xiàn)在可以給自動屬性賦初始值了。
public class Customer
{
public string First { get; set; } = "Jane";
public string Last { get; set; } = "Doe";
}
這個初始化直接賦值給屬性的后備字段(自動生成的隱藏字段)推沸,并沒有通過set方法备绽。初始化的時機和字段初始化的時機一致券坞。
和字段初始化一致,自動屬性初始化時無法引用this
疯坤,畢竟初始化是在對象完全初始化之前進行的报慕。
自動屬性可以只設(shè)置Get
自動屬性現(xiàn)在可以只設(shè)置Get,不設(shè)置Set
public class Customer
{
public string First { get; } = "Jane";
public string Last { get; } = "Doe";
}
只有Get方法的自動屬性的后備字段被隱式聲明為readonly
(盡管僅用于反射)压怠。這個屬性可以在屬性聲明時直接初始化眠冈,就像上面代碼一樣。也可以在類的構(gòu)造函數(shù)中初始化菌瘫,會直接賦值給后備字段蜗顽。
public class Customer
{
public string Name { get; }
public Customer(string first, string last)
{
Name = first + " " + last;
}
}
表達式化的方法體
現(xiàn)在Lambda表達式可以用于成員方法的方法體。
表達式化的成員方法
方法雨让、運算符可以用lambda的箭頭來定義表達式主體雇盖。
public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public static implicit operator string(Person p) => p.First + " " + p.Last;
效果與帶有單個return語句的塊代碼完全相同。
對于返回void的方法以及返回Task的異步方法栖忠,箭頭語法仍然適用崔挖,但箭頭后面的表達式必須是語句表達式(就像lambdas的規(guī)則一樣):
public void Print() => Debug.Log(First + " " + Last);
表達式化的成員屬性
屬性和索引器可以有g(shù)etter和setter。表達式主體可用于編寫只有g(shù)etter的屬性和索引器庵寞,其中g(shù)etter的主體由表達式主體提供:
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);
注意這里沒有get
關(guān)鍵字狸相。
Using static
該功能允許導(dǎo)入類型的所有可訪問的靜態(tài)成員,使其在后續(xù)代碼中無需使用類型限定符即可使用:
using UnityEngine;
using static UnityEngine.Debug;
using static UnityEngine.Mathf;
class CS6Updates : MonoBehaviour
{
void Start()
{
Log(Sqrt(3 * 3 + 4 * 4));
}
}
如果你經(jīng)常需要使用一些靜態(tài)方法時捐川,這個新功能就很棒脓鹃,可以減少很多的代碼量。如上面代碼中本來應(yīng)該寫Debug.Log
和Mathf.Sqrt
古沥。
擴展方法
擴展方法是靜態(tài)方法瘸右,但使用的時候是實例方法。using static不會將擴展方法引入到全局范圍內(nèi)岩齿,還是需要通過實例方法去調(diào)用太颤。
using static System.Linq.Enumerable; // 具體類型,不是命名空間
class Program
{
static void Main()
{
var range = Range(5, 17); // Ok: not extension
var odd = Where(range, i => i % 2 == 1); // Error, not in scope
var even = range.Where(i => i % 2 == 0); // Ok
}
}
Null條件運算符
有時候代碼中會充斥著null檢查盹沈。null條件運算符可以讓你僅在對象非null的情況下訪問對象成員栋齿,否則返回null。
int? length = customers?.Length; // null if customers is null
Customer first = customers?[0]; // null if customers is null
null條件運算符經(jīng)常和空接合運算符??
一起使用:
int length = customers?.Length ?? 0; // 0 if customers is null
null條件運算符采用就近原則襟诸,我們先看一下以下的代碼:
int? first = customers?[0].Orders.Count();
上面的代碼等價于(除了 customers
只會計算一次):
int? first = (customers != null) ? customers[0].Orders.Count() : null;
null條件運算符可以鏈?zhǔn)接嬎悖?/p>
int? first = customers?[0].Orders?.Count();
注意調(diào)用帶括號的委托類型變量時不能直接使用 ?
,這會導(dǎo)致很多語法歧義基协。你可以使用Invoke調(diào)用:
if (predicate?.Invoke(e) ?? false) { … }
觸發(fā)事件時建議這么調(diào)用:
PropertyChanged?.Invoke(this, args);
在觸發(fā)事件之前歌亲,這是一種檢查null的簡單且線程安全的方法。它是線程安全的原因是該功能僅計算左側(cè)一次澜驮,并將其保存在臨時變量中陷揪。
nameof表達式
有些時候你可能想知道一個變量的變量名是什么。
使用字符串可以達到這個目的,但是容易出錯悍缠。nameof表達式本質(zhì)上是一種奇特的字符串文字卦绣,其中編譯器檢查你是否具有給定名稱的內(nèi)容,并且Visual Studio知道它引用的內(nèi)容飞蚓,因此導(dǎo)航和重構(gòu)起作用滤港。
if (x == null) throw new ArgumentNullException(nameof(x));
WriteLine(nameof(person.Address.ZipCode)); // prints "ZipCode"
索引初始化
對象和集合初始化對于初始化對象的字段、屬性或為集合提供一組初始數(shù)據(jù)非常有用趴拧。但使用索引初始化字典和其他對象不太優(yōu)雅溅漾。對象初始化現(xiàn)在可以使用一個新語法,可以通過索引將值設(shè)置為Key著榴。
var numbers = new Dictionary<int, string> {
[7] = "seven",
[9] = "nine",
[13] = "thirteen"
};
異常過濾器
try { … }
catch (MyException e) when (myfilter(e))
{
…
}
如果括號內(nèi)表達式的計算結(jié)果為true添履,則運行catch塊,否則異常將不被catch脑又。
異常過濾器比捕獲和重新拋出更好用暮胧,因為它可以保持堆棧不受破壞。如果稍后的異常導(dǎo)致堆棧被轉(zhuǎn)儲问麸,你可以看到它最初來自哪里往衷,而不僅僅是它重新拋出的最后一個位置。
“濫用”異常過濾器也是常見且被接受的一種方式:例如日志記錄口叙。他們可以在不攔截異常的情況下檢查“飛過”的異常炼绘。在這些情況下,過濾器通常會調(diào)用一個錯誤返回的輔助函數(shù)來執(zhí)行:
private static bool Log(Exception e) { /* log it */ ; return false; }
…
try { … } catch (Exception e) when (Log(e)) {}
catch和finally中的異步
Resource res = null;
try
{
res = await Resource.OpenAsync(…); // You could do this.
…
}
catch(ResourceException e)
{
await Resource.LogAsync(res, e); // Now you can do this …
}
finally
{
if (res != null) await res.CloseAsync(); // … and this.
}
小結(jié)
本文講解了C#6的新特性中對Unity編程有影響的新特性妄田。
洪流學(xué)堂公眾號回復(fù)runtime
俺亮,獲取本系列所有文章。
把今天的內(nèi)容分享給其他Unity開發(fā)者朋友疟呐,或許你能幫到他脚曾。