[Unity腳本運(yùn)行時(shí)更新]C#7新特性

洪流學(xué)堂,讓你快人幾步荆虱!本文首發(fā)于洪流學(xué)堂微信公眾號蒿偎。

本文是該系列《Unity腳本運(yùn)行時(shí)更新帶來了什么?》的第5篇怀读。
洪流學(xué)堂公眾號回復(fù)runtime诉位,獲取本系列所有文章。

Unity2017-2018.2中的4.x運(yùn)行時(shí)已經(jīng)支持到C#6菜枷,之前的文章已經(jīng)介紹完畢苍糠。Unity2018.3將支持到C# 7.3,今天我們先來看看C#7.0新特性能給代碼帶來什么吧啤誊,不過這些特性得等到Unity2018.3才可以用哦岳瞭。

C#7 新特性

C#7.0增加了許多新功能,并將重點(diǎn)放在數(shù)據(jù)消耗蚊锹,代碼簡化和性能上瞳筏。最大的更新有:元組,它可以輕松獲得多個(gè)結(jié)果牡昆;模式匹配可以簡化以數(shù)據(jù)類型為條件的代碼姚炕。但是還有許多其他大小不一的新功能。

Out變量

在舊版本的C#中丢烘,使用out參數(shù)并不是很流暢柱宦。在調(diào)用有out參數(shù)的方法之前,首先必須聲明要傳遞給它的變量播瞳。由于通常不會初始化這些變量(畢竟它們將被方法覆蓋)掸刊,也無法使用var來聲明它們,需要指定完整類型:

public void PrintCoordinates(Point p)
{
    int x, y; // 必須要預(yù)先聲明
    p.GetCoordinates(out x, out y);
    WriteLine($"({x}, {y})");
}

在C# 7.0中添加了out變量赢乓,可以直接在作為out參數(shù)傳遞的位置聲明變量:

public void PrintCoordinates(Point p)
{
    p.GetCoordinates(out int x, out int y);
    WriteLine($"({x}, {y})");
}

變量的作用范圍是當(dāng)前代碼塊內(nèi)忧侧,后續(xù)代碼可以使用它們石窑。

由于out變量直接聲明作為out參數(shù),因此編譯器通巢园兀可以告訴它們的類型應(yīng)該是什么(除非存在沖突的重載)尼斧,因此可以使用var而不是明確類型來聲明它們:

p.GetCoordinates(out var x, out var y);

out參數(shù)的常見用法是Try...模式,其中布爾返回值表示成功试吁,out參數(shù)包含獲得的結(jié)果:

public void PrintStars(string s)
{
    if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }
    else { WriteLine("Cloudy - no stars tonight!"); }
}

也允許丟棄out參數(shù)棺棵,使用下劃線_可以忽略你不需要的out參數(shù):

p.GetCoordinates(out var x, out _); // 我只在乎x

模式匹配

C# 7.0引入了*模式的概念,它是一個(gè)語法元素熄捍,可以測試一個(gè)值具有某種“形狀”烛恤,并可以從值中提取信息。

C#7.0中的模式示例如下:

  • 常量模式c(c是C#中的常量表達(dá)式)余耽,用于判斷輸入值是否等于c
  • 類型模式T x(T是一個(gè)類型并且x是一個(gè)標(biāo)識符)缚柏,它判斷輸入值的類型是否為T,如果是碟贾,則將輸入值提取到x中
  • Var模式var x(其中x是標(biāo)識符)币喧,它總是匹配,并簡單地將輸入的值放入與輸入x相同類型的新變量中袱耽。

這只是一個(gè)開始 - 模式是C#中一種新的語法杀餐,將來會將更多的元素添加到C#中。

在C#7.0中朱巨,模式增強(qiáng)了兩個(gè)現(xiàn)有的語言結(jié)構(gòu):

  • is 表達(dá)式現(xiàn)在可以在右側(cè)有一個(gè)模式史翘,而不僅僅是一個(gè)類型
  • case switch語句中的子句現(xiàn)在可以用模式匹配,而不僅僅是常量值

具有模式的is表達(dá)式

以下是使用is具有常量模式和類型模式的表達(dá)式的示例:

public void PrintStars(object o)
{
    if (o is null) return;     // 常量模式 "null"
    if (!(o is int i)) return; // 類型模式 "int i"
    WriteLine(new string('*', i));
}

模式變量冀续,模式引入的變量琼讽,類似于前面描述的out變量,因?yàn)樗鼈兛梢栽诒磉_(dá)式的中間聲明洪唐,并且可以在最近的作用范圍內(nèi)使用钻蹬。也像out變量一樣,模式變量是可變的凭需。我們經(jīng)常將out變量和模式變量共同稱為“表達(dá)式變量”脉让。

模式和try方法一起用經(jīng)常能達(dá)到神器的效果:

if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }

使用模式的Switch語句

有了模式以后,switch就更強(qiáng)大了:

  • 可以對任意類型進(jìn)行switch(不只是原始類型)
  • 模式可以在case子句中使用
  • case可以有額外條件

例如:

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

關(guān)于這個(gè)新擴(kuò)展的switch語句有幾點(diǎn)需要注意:

  • case的順序現(xiàn)在很重要:就像catch一樣功炮,case不必是不相交的,匹配的第一個(gè)case會被選中术唬。此外薪伏,就像使用catch一樣,編譯器會幫你標(biāo)記永遠(yuǎn)無法訪問到的case粗仓。C#7.0之前嫁怀,你不用考慮case的順序设捐,所以這個(gè)特性不會改變已有代碼的行為。
  • default語句會在最后匹配:即使上面的case null是最后一個(gè)塘淑,也會在default之前匹配萝招。這是為了與現(xiàn)有的switch語法兼容,但是良好的做法通常會將default放在最后存捺。
  • 最后的case null不會匹配不到:這是因?yàn)閕s表達(dá)式中的類型模式不匹配null槐沼,這可以確保不會因?yàn)橄瘸霈F(xiàn)的任何類型模式而意外地捕獲null。你必須明確地處理它們(或?qū)⑺鼈儽A舻絛efault里處理)捌治。
  • case ...:標(biāo)簽引入的模式變量的作用范圍是對應(yīng)的case范圍內(nèi)岗钩。

Tuple 元組

你可能希望從方法返回多個(gè)值,在舊版C#中可能的解決方案有:

  • out參數(shù):使用很笨重(即使有上面的改進(jìn))肖油,也不能使用異步方法兼吓。
  • System.Tuple<...>返回類型:使用麻煩并需要分配元組對象。
  • 為每個(gè)方法自定義返回類型:一個(gè)類型需要寫大量代碼森枪,只是為了臨時(shí)分組
  • 通過dynamic返回的匿名類型:性能開銷大视搏,無靜態(tài)類型檢查。

這些解決方案都不是很好县袱,為了更好的實(shí)現(xiàn)這一目標(biāo)浑娜,C#7.0添加了元組類型和元組語法。

(string, string, string) LookupName(long id) // 返回元組類型
{
    ... // 獲取first显拳,middle棚愤,last,代碼略
    return (first, middle, last); // 元組
}

該方法現(xiàn)在高效地返回三個(gè)字符串杂数,封裝在元組中宛畦。

該方法的調(diào)用者將接收一個(gè)元組,并可以單獨(dú)訪問元組中的元素:

var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");

Item1等等是元組元素的默認(rèn)名字揍移,總是可以使用次和。但它們不是很具描述性,所以你可以選擇添加更好的名字:

(string first, string middle, string last) LookupName(long id) // tuple elements have names

現(xiàn)在那伐,該元組的接收者可以使用描述性名稱:

var names = LookupName(id);
WriteLine($"found {names.first} {names.last}.");

您還可以直接在元組定義中指定元素名稱:

return (first: first, middle: middle, last: last); // named tuple elements in a literal

通常踏施,只要元組各個(gè)元素可賦值,元組就可以自由轉(zhuǎn)換為其他元組類型罕邀,無論元組元素的名稱是否一樣畅形。

元組是值類型,它們的元素是公共的诉探,可變的字段日熬。它們具有值相等性,這意味著如果兩個(gè)元組的所有元素對應(yīng)相等(并且具有相同的哈希碼)肾胯,則它們是相等的(并且具有相同的哈希碼)竖席。

這使得元組可用于多個(gè)返回值之外的許多其他情況耘纱。例如,如果你需要一個(gè)包含多個(gè)鍵的字典毕荐,可以使用元組作為key束析。如果你需要在每個(gè)位置具有多個(gè)值的列表,可以使用元組憎亚。

Deconstruction 解構(gòu)

使用元組的另一種方法是解構(gòu)后使用员寇。解構(gòu)聲明可以將元組(或其他值)分割成單獨(dú)的部分來接收對應(yīng)的變量。

(string first, string middle, string last) = LookupName(id1); // deconstructing declaration
WriteLine($"found {first} {last}.");

也可以使用var:

(var first, var middle, var last) = LookupName(id1); // var inside

var 也可以放到外面虽填。

var (first, middle, last) = LookupName(id1); // var outside

也可以解構(gòu)到已有的變量中丁恭,稱作解構(gòu)賦值

(first, middle, last) = LookupName(id2); // deconstructing assignment

解構(gòu)不僅僅可以用于元組,任何類型都可以被解構(gòu)斋日,只要它有一個(gè)如下類型的解構(gòu)方法(實(shí)例方法或擴(kuò)展方法):

public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }

out參數(shù)構(gòu)成解構(gòu)所產(chǎn)生的值牲览。
(為什么使用out參數(shù)而不是返回元組?這樣就可以為不同數(shù)量的值設(shè)置多個(gè)重載)恶守。

class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) { X = x; Y = y; }
    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

(var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY);

以這種方式使構(gòu)造函數(shù)和解構(gòu)函數(shù)“對稱”是一種常見的模式第献。

就像輸出變量一樣,解構(gòu)中可以“丟棄”你不關(guān)心的部分:

(var myX, _) = GetPoint(); // I only care about myX

本地函數(shù)

有時(shí)輔助函數(shù)只在使用它的單個(gè)方法中有意義⊥酶郏現(xiàn)在可以在函數(shù)體內(nèi)將這些函數(shù)聲明為本地函數(shù)

public int Fibonacci(int x)
{
    if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));
    return Fib(x).current;

    (int current, int previous) Fib(int i)
    {
        if (i == 0) return (1, 0);
        var (p, pp) = Fib(i - 1);
        return (p + pp, p);
    }
}

封閉范圍內(nèi)的參數(shù)和局部變量在局部函數(shù)內(nèi)部可用庸毫,就像它們在lambda表達(dá)式中一樣。

作為示例衫樊,作為迭代器實(shí)現(xiàn)的方法通常需要非迭代器包裝器方法飒赃,以便在調(diào)用時(shí)檢查參數(shù)。(迭代器本身在MoveNext調(diào)用之前不會開始運(yùn)行)科侈。本地函數(shù)非常適合這種情況:

public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (filter == null) throw new ArgumentNullException(nameof(filter));

    return Iterator();

    IEnumerable<T> Iterator()
    {
        foreach (var element in source) 
        {
            if (filter(element)) { yield return element; }
        }
    }
}

如果Iterator旁邊是私有方法Filter载佳,則其他成員可能意外地直接調(diào)用(無需參數(shù)檢查)。此外臀栈,這個(gè)私有方法Filter需要接收所有相同的參數(shù)蔫慧,而不是直接使用作用范圍內(nèi)的參數(shù)。

字面值改進(jìn)

C# 7.0 中可以使用下劃線 _ 作為數(shù)字字面值的分隔符:

var d = 123_456;
var x = 0xAB_CD_EF;

可以將它們放在數(shù)字之間的任何位置以提高可讀性权薯。它們對值沒有影響姑躲。

此外盟蚣,C#7.0引入了二進(jìn)制字面值黍析,因此你可以直接使用位模式,而不必使用心算來計(jì)算十六進(jìn)制表示屎开。

var b = 0b1010_1011_1100_1101_1110_1111;

Ref返回和臨時(shí)變量

就像在C#中通過ref引用(使用修飾符)傳遞內(nèi)容一樣橄仍,現(xiàn)在可以通過引用返回它們,并且還可以通過引用將它們存儲在局部變量中。

public ref int Find(int number, int[] numbers)
{
    for (int i = 0; i < numbers.Length; i++)
    {
        if (numbers[i] == number) 
        {
            return ref numbers[i]; // return the storage location, not the value
        }
    }
    throw new IndexOutOfRangeException($"{nameof(number)} not found");
}

int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // aliases 7's place in the array
place = 9; // replaces 7 with 9 in the array
WriteLine(array[4]); // prints 9

這對于將占位符傳遞到大數(shù)據(jù)結(jié)構(gòu)非常有用侮繁。例如,游戲經(jīng)常把數(shù)據(jù)保存在一個(gè)大的預(yù)分配結(jié)構(gòu)體數(shù)組中(以避免GC造成的性能影響)如孝。方法現(xiàn)在可以直接將引用返回給這樣的結(jié)構(gòu)體宪哩,調(diào)用者可以通過讀取和修改這個(gè)結(jié)構(gòu)體。

有一些限制來確保這是安全的:

  • 只能返回“可以安全返回”的引用:一個(gè)是傳遞進(jìn)來的引用第晰,另一個(gè)是指向?qū)ο笾械淖侄蔚囊谩?/li>
  • Ref本地變量初始化在特定的存儲位置锁孟,并且不能改變指向另一個(gè)。

異步返回類型的廣泛支持

到目前為止茁瘦,C#中的異步方法必須返回void品抽,Task或者Task<T>。C#7.0允許以特定方式定義其他類型甜熔,以便從異步方法返回它們圆恤。

例如,我們現(xiàn)在有一個(gè)ValueTask<T>結(jié)構(gòu)體類型腔稀。它的是為了防止在Task<T>等待時(shí)異步操作的結(jié)果已經(jīng)可用的情況下再分配對象盆昙。例如,對于涉及緩沖的許多異步場景焊虏,這可以大大減少分配數(shù)量并導(dǎo)致顯著的性能提升淡喜。

你還可以通過許多其他方式創(chuàng)建自定義“類任務(wù)”類型。創(chuàng)建它們并不是很簡單诵闭,但它們很可能會開始出現(xiàn)在框架和API中炼团,然后調(diào)用者就可以返回await它們了,就像現(xiàn)在的Task一樣疏尿。如果你想具體了解:https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md

更多表達(dá)式化的成員體

C# 6.0中引入了表達(dá)式化的方法瘟芝、屬性等,但是沒有允許在所有的成員中使用润歉。C#7.0將訪問器模狭,構(gòu)造函數(shù)和析構(gòu)函數(shù)加入了進(jìn)來:

class Person
{
    private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
    private int id = GetId();

    public Person(string name) => names.TryAdd(id, name); // constructors
    ~Person() => names.TryRemove(id, out _);              // finalizers
    public string Name
    {
        get => names[id];                                 // getters
        set => names[id] = value;                         // setters
    }
}

拋出表達(dá)式

在表達(dá)式中間拋出異常很容易:只需調(diào)用執(zhí)行此操作的方法!但是在C#7.0中踩衩,可以直接在表達(dá)式某些地方throw:

class Person
{
    public string Name { get; }
    public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));
    public string GetFirstName()
    {
        var parts = Name.Split(" ");
        return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
    }
    public string GetLastName() => throw new NotImplementedException();
}

小結(jié)

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

洪流學(xué)堂公眾號回復(fù)runtime驱富,獲取本系列所有文章锚赤。

把今天的內(nèi)容分享給其他Unity開發(fā)者朋友,或許你能幫到他褐鸥。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末线脚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浑侥,老刑警劉巖姊舵,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寓落,居然都是意外死亡括丁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進(jìn)店門伶选,熙熙樓的掌柜王于貴愁眉苦臉地迎上來史飞,“玉大人,你說我怎么就攤上這事仰税」棺剩” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵陨簇,是天一觀的道長吐绵。 經(jīng)常有香客問我,道長塞帐,這世上最難降的妖魔是什么拦赠? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮葵姥,結(jié)果婚禮上荷鼠,老公的妹妹穿的比我還像新娘。我一直安慰自己榔幸,他們只是感情好允乐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著削咆,像睡著了一般牍疏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拨齐,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天鳞陨,我揣著相機(jī)與錄音,去河邊找鬼瞻惋。 笑死厦滤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的歼狼。 我是一名探鬼主播掏导,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼羽峰!你這毒婦竟也來了趟咆?” 一聲冷哼從身側(cè)響起添瓷,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎值纱,沒想到半個(gè)月后鳞贷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虐唠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年悄晃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凿滤。...
    茶點(diǎn)故事閱讀 39,754評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖庶近,靈堂內(nèi)的尸體忽然破棺而出翁脆,到底是詐尸還是另有隱情,我是刑警寧澤鼻种,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布反番,位于F島的核電站,受9級特大地震影響叉钥,放射性物質(zhì)發(fā)生泄漏罢缸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一投队、第九天 我趴在偏房一處隱蔽的房頂上張望枫疆。 院中可真熱鬧,春花似錦敷鸦、人聲如沸息楔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽值依。三九已至,卻和暖如春碟案,著一層夾襖步出監(jiān)牢的瞬間愿险,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工价说, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辆亏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓熔任,卻偏偏與公主長得像褒链,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子疑苔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容