[Unity腳本運行時更新]C#6新特性

洪流學堂,讓你快人幾步!本文首發於洪流學堂微信公衆號。

本文是該系列《Unity腳本運行時更新帶來了什麼?》的第4篇。
洪流學堂公衆號回覆runtime,獲取本系列所有文章。

Unity2017-2018.2中的4.x運行時已經支持到C#6,Unity2018.3將支持到C# 7.2,看看C#6新特性能給代碼帶來什麼吧。

C#6 新特性## String填空

String.Format 非常常用,但使用起來很麻煩而且容易出錯。在格式字符串中需要使用類似{0}的佔位符,還得單獨提供對應的參數:

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";

填空的內容可以是任何表達式:

var s = $"{p.Name} is {p.Age} year{(p.Age == 1 ? "" : "s")} old";

請注意,條件表達式是在括號裏,因此:"s"不會與格式說明符混淆。

自動屬性的增強

自動屬性的初始化

現在可以給自動屬性賦初始值了。

public class Customer
{
    public string First { get; set; } = "Jane";
    public string Last { get; set; } = "Doe";
}

這個初始化直接賦值給屬性的後備字段(自動生成的隱藏字段),並沒有通過set方法。初始化的時機和字段初始化的時機一致。

和字段初始化一致,自動屬性初始化時無法引用this,畢竟初始化是在對象完全初始化之前進行的。

自動屬性可以只設置Get

自動屬性現在可以只設置Get,不設置Set

public class Customer
{
    public string First { get; } = "Jane";
    public string Last { get; } = "Doe";
}

只有Get方法的自動屬性的後備字段被隱式聲明爲readonly(儘管僅用於反射)。這個屬性可以在屬性聲明時直接初始化,就像上面代碼一樣。也可以在類的構造函數中初始化,會直接賦值給後備字段。

public class Customer
{
    public string Name { get; }
    public Customer(string first, string last)
    {
        Name = first + " " + last;
    }
}

表達式化的方法體

現在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的規則一樣):

public void Print() => Debug.Log(First + " " + Last);

表達式化的成員屬性

屬性和索引器可以有getter和setter。表達式主體可用於編寫只有getter的屬性和索引器,其中getter的主體由表達式主體提供:

public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id); 

注意這裏沒有get關鍵字。

Using static

該功能允許導入類型的所有可訪問的靜態成員,使其在後續代碼中無需使用類型限定符即可使用:

using UnityEngine;
using static UnityEngine.Debug;
using static UnityEngine.Mathf;

class CS6Updates : MonoBehaviour
{
    void Start()
    {
        Log(Sqrt(3 * 3 + 4 * 4));
    }
}

如果你經常需要使用一些靜態方法時,這個新功能就很棒,可以減少很多的代碼量。如上面代碼中本來應該寫Debug.LogMathf.Sqrt

擴展方法

擴展方法是靜態方法,但使用的時候是實例方法。using static不會將擴展方法引入到全局範圍內,還是需要通過實例方法去調用。

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條件運算符經常和空接合運算符??一起使用:

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條件運算符可以鏈式計算:

int? first = customers?[0].Orders?.Count();

注意調用帶括號的委託類型變量時不能直接使用 ? ,這會導致很多語法歧義。你可以使用Invoke調用:

if (predicate?.Invoke(e) ?? false) { … }

觸發事件時建議這麼調用:

PropertyChanged?.Invoke(this, args);

在觸發事件之前,這是一種檢查null的簡單且線程安全的方法。它是線程安全的原因是該功能僅計算左側一次,並將其保存在臨時變量中。

nameof表達式

有些時候你可能想知道一個變量的變量名是什麼。

使用字符串可以達到這個目的,但是容易出錯。nameof表達式本質上是一種奇特的字符串文字,其中編譯器檢查你是否具有給定名稱的內容,並且Visual Studio知道它引用的內容,因此導航和重構起作用。

if (x == null) throw new ArgumentNullException(nameof(x));
WriteLine(nameof(person.Address.ZipCode)); // prints "ZipCode"

索引初始化

對象和集合初始化對於初始化對象的字段、屬性或爲集合提供一組初始數據非常有用。但使用索引初始化字典和其他對象不太優雅。對象初始化現在可以使用一個新語法,可以通過索引將值設置爲Key。

var numbers = new Dictionary<int, string> {
    [7] = "seven",
    [9] = "nine",
    [13] = "thirteen"
};

異常過濾器

try { … }
catch (MyException e) when (myfilter(e))
{
    …
}

如果括號內表達式的計算結果爲true,則運行catch塊,否則異常將不被catch。

異常過濾器比捕獲和重新拋出更好用,因爲它可以保持堆棧不受破壞。如果稍後的異常導致堆棧被轉儲,你可以看到它最初來自哪裏,而不僅僅是它重新拋出的最後一個位置。

“濫用”異常過濾器也是常見且被接受的一種方式:例如日誌記錄。他們可以在不攔截異常的情況下檢查“飛過”的異常。在這些情況下,過濾器通常會調用一個錯誤返回的輔助函數來執行:

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.
}

小結

本文講解了C#6的新特性中對Unity編程有影響的新特性。

洪流學堂公衆號回覆runtime,獲取本系列所有文章。

把今天的內容分享給其他Unity開發者朋友,或許你能幫到他。



《鄭洪智的Unity2018課》,傾盡我8年的開發經驗,結合最新的Unity2018,帶你從入門到精通。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章