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

[Unity指令碼執行時更新]C#7.3新特性

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

本文是該系列《Unity指令碼執行時更新帶來了什麼?》的第8篇。
洪流學堂公眾號回覆runtime,獲取本系列所有文章。

Unity2017-2018.2中的4.x執行時已經支援到C#6,之前的文章已經介紹完畢。Unity2018.3將支援到C# 7.3,今天我們來看看C#7.3新特效能給程式碼帶來什麼吧,不過這些特性得等到Unity2018.3才可以用哦

C#7.3 新特性

通過一個相對較小的版本,C# 7.3解決了一些自C# 1和2以來長期懸而未決的問題。

過載解析

從C# 1.0開始,過載解析規則的設計就相當有問題。在某些情況下,它會選兩個或更多方法作為候選,雖然所有這些方法中只有一個會被使用。根據這些錯誤選出的方法的優先順序,編輯器要麼會報沒有匹配的方法,要麼會報匹配不明確。

C# 7.3把其中部分檢查移到了過載解析期間,而不是過載解析之後,這樣,錯誤的匹配就不會導致編譯器錯誤。改進後的過載候選提案概括了這些檢查:

  1. 當一個方法組既包含例項又包含靜態成員時,如果呼叫時沒有例項接收者或上下文,我們就會丟棄例項成員,如果呼叫時有例項接收者,我們就丟棄靜態成員。當沒有接收者時,我們只會在一個靜態上下文中包含靜態成員,否則會同時包含靜態和例項成員。當不確定接收者是例項還是型別時,我們會兩者都包含。在靜態上下文中,不能使用隱式的this例項接收者,它包含的方法體中沒有定義this,如靜態成員,它還包含不能使用this的地方,如欄位初始化器和建構函式初始化器。

  2. 當方法組包含一些泛型方法,而它們的型別引數不滿足約束時,這些成員會被從候選集中移除。

  3. 對於方法組轉換,那些返回型別與委託的返回型別不一致的候選方法會被從候選集中移除。

泛型約束:列舉、委託和非託管

自C# 2.0引入泛型以來,開發人員就一直在抱怨,無法把一個泛型型別指定為列舉。這個問題終於解決了,你現在可以使用enum關鍵字作為泛型約束了。同樣,你現在可以使用delegate關鍵字作為泛型約束了。

這些關鍵字可能並不是和你預期的那樣發揮作用。如果約束是T : enum,那麼有人可能就會使用Foo,而你的意思也許是讓他們使用System.Enum的子類。儘管如此,這應該可以覆蓋列舉和委託的大多數使用場景。

非託管型別約束提案使用了unmanaged關鍵字,用於說明泛型型別必須是“非引用型別,並且在任意巢狀層次上都不包含引用型別欄位。”這是為了用在底層互動程式碼中,當你需要“建立可供所有非託管型別重用的例程時”。非託管型別包括:

  • 基元型別sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、IntPtr或UIntPtr;
  • 任何列舉型別;
  • 指標型別
  • 只包含上述型別的使用者定義結構。

隱藏欄位的Attribute

雖然自實現的Property非常有用,但是它們有一些侷限,Attribute不適用於後備欄位,因為你看不到它。雖然通常來說這不是問題,但在處理序列化時就可能有問題了。

面向自實現Property欄位的Attribute提案用一種簡單的方法解決了這個問題。當把一個Attribute應用到一個自實現的Property時,只需在欄位定義時加上field:修飾符。

[Serializable]
public class Foo {

    [field: NonSerialized]
    public string MySecret { get; set; }
}

元組比較(==和!=)

雖然提案的名稱“支援元組型別==和!=比較”很好地概括了這項特性,但還有一些細節和邊際情況需要注意。最重要的是潛在的破壞性變化:

如果有人自己編寫了一個ValueTuple型別,並實現了比較操作符,之前,過載解析會找到它們。但是,新的元組情況出現在過載解析之前,我們會通過元組比較處理這種情況,而不是基於使用者定義的比較。

理想情況下, 這個自定義的ValueTuple型別會遵循與C# 7.3編譯器同樣的規則,但是,在如何處理巢狀元組和動態型別方面,可能會有微妙的差別。

初始化器中的表示式變數

在某種程度上,這看上去像個反特性。微軟不僅沒有增加功能,而是去掉了表示式變數的使用場景限制。

我們移除了在ctor初始化器中不能宣告表示式變數(out變數宣告和宣告方式)的限制。這樣宣告的變數其作用域是整個建構函式的函式體。

我們移除了在欄位或Property初始化器中不能宣告表示式變數(out變數宣告和宣告方式)的限制。這樣宣告的變數其作用域是整個初始化表示式。

我們移除了在會被翻譯成lambda表示式主體的查詢表示式子句中不能宣告表示式變數(out變數宣告和宣告方式)的限制。這樣宣告的變數其作用域是整個查詢子句表示式。

最初增加這些限制只是因為“沒有時間”。也許,這些限制縮短了了C# 7之前版本完工所需的測試時間。

棧分配陣列

C#中有一個很少使用單相當重要的特性,就是能夠通過stackalloc關鍵字在棧上分配陣列。與分配在堆上、會導致GC壓力的普通陣列相比,這可能會提供更好的效能。

int* block = stackalloc int[3] { 1, 2, 3 };

使用棧分配陣列有點危險。因為它需要持有一個指向棧的指標,而且只能用於不安全的上下文中。CLR會啟用緩衝區溢位檢測來緩解這種情況,那會導致“應用程式儘快終止”。

在C# 7.3中,你可以在建立陣列時對其初始化,就像你對普通陣列所做的那樣。該提案沒有提供細節,但微軟正考慮預初始化一個主陣列,當函式被呼叫時可以快速複製。理論上講,這比建立一個數組然後一個元素一個元素的初始化要快。

注意,棧分配陣列適用於需要大量小陣列供短暫使用的場景。不能把它用於大陣列或者深度遞迴函式,因為那可能會超出可用的棧空間。

棧分配Span

棧分配陣列的一個安全替代方案是棧分配Span。消除指標,也就消除了緩衝區溢位的可能性。反過來,這意味著你可以使用它而不必把方法標記為不安全的。

Span<int> block = stackalloc int[3] { 1, 2, 3 };

注意,Span依賴於NuGet包System.Memory。

可重新賦值的Ref區域性變數

Ref區域性變數現在可以和普通區域性變數一樣重新賦值了。

小結

本文講解了C#7.3的新特性中對Unity程式設計有影響的新特性,不過這些特性得等到Unity2018.3才可以用哦

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

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



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