1. 程式人生 > >[Unity指令碼執行時更新]C#6新特性

[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,帶你從入門到精通。