因為您可以用,並且也是您的最佳選擇!之所以可用,是因為 C# 能夠很好地在 Mac、Linux、Android 和 iOS 上運行(對了,還有 Windows);它可以在您最喜愛的編輯器上運行;它在一個穩定的企業級平台上經過了充分的時間驗證;最為重要的是:它是完全開源的!之所以是您的最佳選擇,是因為 C# 是編程語言創新方面的領導者,是原生跨平台移動應用程序的最佳選擇,並且還有很多的優點超乎您的想象。在本次 GOTO Copenhagen 2016 大會講演上,Mads Torgersen 邀請您一起來探索 C# 的核心,探究為什么它仍然散發着活力,並探尋未來 C# 的發展趨勢。
概述
我是 Mads Torgersen,就職於微軟的 C# 部門。我現在年紀大了,因此我隨身都穿着這件 T恤,上面印着我正在負責的項目和語言名稱,以防我忘掉它們。這里我想談一談 C#,為什么我要推薦用它來作為大家的首選編程語言呢(即使到目前為止您還沒有接觸過 C#)。
Stack Overflow - 最受歡迎和喜愛的技術
Stack Overflow 每年都會進行一次調查,詢問很多開發者們都關心的問題(當然,在很多方面這些問題都是很具有傾向性,是很不科學的)。您必須在 Stack Overflow 上才能參與。
C# 是一門被廣泛使用的編程語言(排行第四,排行前三當中有一門實際上並不屬於編程語言——我說的不是 JavaScript,我說的是 SQL)。可以看出,C# 是一門主流語言。
他們同樣還問開發者們:是否還想繼續使用目前正在用的語言,並讓人們投票出他們最喜愛的技術。C# 同樣也在這個列表當中。這說明人們都很喜歡 C# 這門語言。此外還有其他人們也喜歡的語言,但是您還可以注意到,這些語言中的大部門要么就是受眾較少,要么就是非常專業化,很多都是某種狂熱信仰的一部分了。在這兩個列表當中,只有少數才是用途廣泛、受人們高度喜愛的。很高興能看到 C# 位於這個列表的三大最受歡迎的技術之一,其中兩個是編程語言,並且* Python 也在這里面*。
我們不斷思考我們的所作所為,怎樣才可能是正確的呢,怎樣才能讓我們在多年以后仍然喜歡它。似乎並不是所有人都用過 C#,因為很多人所在的公司已經有 10 年多的歷史了,里面存在了很多的遺留代碼。目前 C# 仍然保持着活力,我們希望它能將這份活力保持下去。我們同樣也有各式各樣的想法,而這驅動了 C# 的演進。
我們非常渴望去演進 C#。如果您看過現代語言的演變進程的話(從少到多),就會明白我們積極保持語言現代化的目的所在了。作為參與編程語言演變的一份子,我們有些時候是推動者,有些時候是跟隨者,無論如何,我們都試圖讓 C# 成為程序員們如今的絕佳選擇之一。我們不應該搞所謂的「限定」,只局限於某幾個平台,因為過去十年當中就有人這么做了,結果可想而知。
我還想提一提 F#,因為這相當於是我們的姊妹語言,它非常受歡迎,因為它很輕巧、也很強大。F# 是一門功能強大的語言,我們在與 F# 團隊的合作當中獲益良多,並且它也給我們提供了很多設計靈感。
(說明:F#是由微軟發展的為微軟.NET語言提供運行環境的程序設計語言。它是基於Ocaml的,而Ocaml是基於ML函數程序設計語言的。函數編程是解決許多棘手問題的最好方法,但是,純函數編程並不適合常規編程。因此,函數編程語言逐漸吸收了命令式、面向對象的編程模式,不僅保持了函數編程范式,同時也混合了其他需要的功能,使函數編程編寫各種類型的程序都很容易。F# 就是這種嘗試的成功代表,比其他函數編程語言做得更多。F#主要是為了解決特定的某些復雜問題,所以本身定位使得VS沒有提供F#的ASP.NET/WPF/GDI+的模板,若要使用需要自己配置。所以,一般情況下都是用C#。)
時代在改變 - 為什么要選擇 C#
在越來越多的場景當中,您都可以使用 C#來進行編程。我們正在努力地做出一種改變。C# 在 Windows 當中是一種很重要的主要編程語言,但同時,我們在其他平台上仍然還非常稚嫩。至少大多數平台是這樣。現在 C# 已經是所有平台上可選的編程語言之一,這非常鼓舞人心,然而我們同時也有些頑固,此外這些平台上也出現了各式各樣新穎的語言。這使得我們迫切地希望其他平台上也能夠使用我們的語言。
我們已經很多次對我們的語言進行了演進。實現 C# 底層的編譯器和 IDE 技術(名為 Roslyn 項目)為 C# 的編程啟用了獨一無二的場景。其中一個好處是,我們將 C# 的核心從 Windows 和 Visual Studio 當中剝離了出來,這意味着 C# 能夠很容易地在其他 IDE 當中使用。您可以用自己喜愛的 IDE 或者編輯器來編寫 C# 代碼。
我們已經將 C# 從完全的專有技術轉變為了完全開放源代碼的技術了。這意味着每個人都能給 C# 貢獻代碼了,當然也已經很多人參與到這個項目當中來了。我們現在正在同社區展開交流,現在 C# 的演進非常迅速。因為現在這更像是一個協同項目了,而不是「微軟說怎樣就怎樣了」。這非常讓人興奮。現在語言的變革已經不再是三年才一代了。「這是我們努力的成果,希望您能喜歡它」,我們現在每天都在與社區討論未來的方向問題。我們隨時隨地都能夠在網上、Github 上得到反饋。因此,我們語言設計的質量也越來越高。
讓我們從 C# 的各個項目開始一一介紹。
無處不 C# - Xamarin
Xamarin 以前是一家獨立的公司。我們六個月之前收購了他們。這是一種使用 C# 來構建跨平台應用的技術,用來制作原生的 Android 和 iOS 應用。它可以讓您使用相同的語言、相同的源代碼來構建絕大多數應用組件,從而能夠為多種不同的移動平台編寫應用。
它適用於 iOS 和 Android,同樣也可以用在 Mac 之上,順便提一下,Windows 也可以使用。它可以創建高品質的原生 UI。有許多大型應用正在使用這門技術,因為它可以極大地減少單獨在這些平台上編碼的工作量。它也允許您使用與后台相同的語言,例如說 Java,不過 Swift 和 Objective-C 還未支持。它以支持的平台量取勝,是一門實現應用的絕佳語言。
它基於 Mono 項目,這是多年以前從微軟離職的員工所實現的開源項目,並且一直維護,致力於能夠在其他平台上也能夠使用 C#。雖然在微軟當中的我們有些固步自封,但是他們卻在我們之前看到了 C# 跨平台的潛力,並實現了這個偉大的跨平台項目。Xamarin 正是基於此而生的,您在 iOS 和 Android 應用商店當中看到的許多應用都是基於 C# 的,要么是使用 Xamarin,要么是使用 Unity,這應該是業界領先的游戲引擎。
無處不 C# - Unity
Unity 也是一個基於 Mono 的項目,它的 2D、3D 游戲引擎是用 C# 來編寫的。有很多游戲是用 C# 編寫的。
無處不 C# - .NET 核心
在微軟,我們正努力完善 .NET 核心,這是對整個 .NET 技術棧、運行時以及代碼庫等內容的全新實現,旨在保證輕量、並且可供服務端使用,並且可用作雲和服務器工作負載。它是跨平台的,適用於 Linux、Mac 和 Windows。我們在此之上還放置了 ASP.NET 框架,這是一個被廣泛使用的 Web 框架,目前您就可以在非 Windows 的機器上運行 ASP.NET 了,並且它還完全開源了!為什么我們要建立一個單獨的核心呢?這將有助於您能夠構建更輕量的服務器。
首先,我們移除了 UI 框架,但是 UI 框架能夠獨立部署。例如,您可以將運行時環境同代碼一並發布;雲端無需安裝各式各樣的依賴文件。它擁有一個更優秀的架構,更適合微服務的部署。它同樣也致力於使我們的服務端平台更加現代化。這些不同的 .NET 運行在不同的平台上,如果沒有統一的部署,那么隨着版本的擴散,一切就會變得非常的混亂,尤其是您作為第三方庫提供方的時候。您需要某種東西能夠跨平台運行,以便解決您的問題。
我們同樣也實現了一個名為 「.NET 標准庫」的東西,我們提供了一個所有 .NET 平台全兼容的 API。如果您需要使用它的話,您可以直接在工具庫當中鏈接這個 .NET 標准庫即可,它將可以在所有平台上運行。您也可以收回在 .NET 生態系統中隨處可用代碼的能力。我們將隨時對標准庫進行升級,包括引入新的核心基本庫,這樣您任意鏈接所需要的標准庫即可。因此 C# 能夠在很多地方運行,希望這能夠說服大家來嘗試 C#,因為這比以前有着更大的適用范圍。能夠實現這個着實讓人興奮。
Roslyn - C# 語言引擎
我想要多談論一些底層的內容,也就是談一談 Roslyn 項目。
我們對 C# 引擎進行了現代化。以前只有一個簡單的 C# 編譯器。它使用 C++ 編寫的,之后我們有了 Visual Studio,然后將 C# 集成了進去。然而這兩者使用 C# 的方式都不同,沒有任何的代碼共享,因此也沒有辦法知道對方的相關信息。如果有人想要使用 C# 相關內容的話,那就需要為之編寫一套工具,或者為它們自己的 IDE 來添加支持,這是一個很大的工作量。因為人們不得不重頭開始編寫,因為 C# 引擎對它們而言是個黑盒。這對我們來說也並不是讓人滿意的。
因此我們決定,是時候重寫編譯器了,我們不僅重寫了 C#,並且還采用了一些新的架構。用來實現 C# 語義的工具只能有一個。我們需要構建一個大家都可以使用的工具,以便滿足他們想通過 C# 實現某些功能的願望。這個工具與平台無關,無論是什么樣的平台,比如說批處理過程、編譯器,還是某些類似 IDE 的交互式環境,都可以使用這個工具進行。這是一個很難的目標,它花費了我們大量的時間來實現,並且也投入了大量的人力。但是現在,我們推出了 Roslyn API,它切實滿足了我們所設定的目標。
大家都需要知道,絕大多數 C# 工具已完成 Roslyn 引擎的遷移,不過仍然有一些沒有。場下還有一位演講者 Ted Brangs 的項目就是例外,因為他是出於某些技術原因的考慮。我們的想法是,這里是您需要用於實現 IDE 和編輯器的代碼庫。如果您需要使用不同類型的分析工具,那么可以導入它們來對代碼中的問題進行分析。如果您想要對代碼進行操作的話,例如代碼遷移、代碼補全或者重構之類的操作,那么您也可以使用它。如果您需要生成源代碼,那么您也可以使用它。如果您需要實現更多交互式場景,例如腳本或者 REPL(比如說現在我們在 Visual Studio 當中包含了一個 C# REPL,它是在 Roslyn 的基礎上構建的),那么這個引擎仍然能夠編譯代碼並完成輸出。
這里是您所能夠實現的一個范例,也就是人們能夠用 Roslyn 做些什么。這可能會導致編程語言相關的工具呈現爆炸式增長,因為人們現在可以更快地來對 C# 進行處理了。它能夠與 C# 很好地協同工作。現在您已經有很多可以輕易得到的信息了,比如說語法、語義等內容,您就可以按照自己的想法,向所需要的地方添加特定的位碼了。有一個社區項目充分利用了這點,那就是 OmniSharp。
(請記住:C#語言引擎--Roslyn)
OmniSharp - 隨時隨地可編輯 C#
OmniSharp 是一個特別的項目,旨在讓 C# 能夠在您喜愛的編輯器上運行。他們實現這個功能的方法很聰明:由於 C# 現在已經可以在任何地方運行了,因此他們采用 Roslyn C# 引擎,然后在一個單獨的進程中運行,不管您在進行開發的機器是什么(例如一台 Mac)。他們使用一個單獨的進程來運行引擎,然后剩下所需要做的就是添加一個很簡單的集成層,然后加入到特定編輯器中的集成層當中,這樣雙方便可以通過進程來進行通信,從而讓編輯器了解到 C# 的全部信息。
每次您按下了一個鍵,例如輸入了點語法,然后這個時候您需要顯示代碼補全,這個時候它就會詢問旁邊的進程:「用戶輸入了點語法,那么我需要在代碼補全列表當中顯示什么呢?」。Roslyn 知曉所有的語義,它隨后會告訴編輯器:「這五種方法是可用的,顯示它們即可」。
通過這種分離方式,使得所有的語義分析過程被包裹在單獨的進程當中進行,然后通過標准的數據傳輸方式來進行數據的交換。這使得我們可以在很多的編輯器當中去實現表現良好的 C#,並且還可以提供語義感知功能(當然,這種做法褒貶不一)。
我需要再提一點,對於微軟的 Visual Studio 而言,我們使用 OmniSharp 來實現其 C# 模式,因為這是一個擴展,可以在任何地方加載。它不會內置在編輯器當中。C# 就像其他語言一樣,對 Visual Studio 而言就是一個擴展組件。
(個人認為:OmniSharp項目對Roslyn引擎進行了封裝,以更方便的使第三方編輯器調用,比如自己開發一個C#代碼編輯器)
演示 - Roslyn 分析器
讓我們舉一個更具體的例子。為了幫助人們將重點放在語言實現之外的地方,我們創建了這個框架,名為 Analyzer,通過它可以輕松地對人們的源代碼進行分析、診斷,最后輸出結果。這樣,我們便可以提供代碼修正建議了。
如果您需要:
1、您的組織有需要強制執行的代碼格式
2、經常執行重構
3、想要與大家分享代碼
4、需要讓代碼修正自動化
那么這個工具正是您所需要的。
這個項目您可以在 Visual Studio 當中安裝,然后就可以開始使用了。當您打開某個項目的時候,它就已經自帶了為這個項目而建立的樣板代碼。具體而言,當您的項目像這樣進入調試模式的時候,然后分析器便會提取您的代碼,然后進行代碼修正,最后將結果輸出。分析器可以以批處理代碼的方式運行,也可以將其作為 Nuget 包進行分發。它是以 Visual Studio 擴展程序的身份出現的,在 Visual Studio 的完整版本當中,它是作為調試模式的一部分運行的。現在我運行了 Visual Studio,然后它便開始執行代碼修正了。這個是我在完整版本當中的 Visual Studio 所編寫的操作。
現在讓我們在這個完整版本的 Visual Studio 中打開一些代碼。我還沒有完全實現完這些分析器的功能。這里是一些我們想要進行操作的示例代碼。出於簡便起見,我想要實現的東西是語法分析,這里我可以定義各種各樣的語義規則。Roslyn 引擎提供了完整的信息供我使用,我可以定義 if 或者 else 語句當中沒有柯里化(curlies)語句是非法的代碼樣式。
我們需要實現那種老式的、固化的代碼風格,也就是始終需要添加柯里化的語句,因為接下來編輯代碼的時候,並不會得到太多的 BUG。我們需要在某些情況下阻止這種代碼的出現,出於時間考慮,我這里只對 if 進行實現,我們當然也可以將其應用到 else 規則當中來。這里讓我們來實現一個小型的代碼分析器。
這里我不會停止使用這個完整版本。我需要放置一個斷點。每當我們看到 if 語句的時候,最開始需要做的就是要注冊它,我們需要調用這個方法 AnalyzeNode。每當 Visual Studio 中的源代碼分析器命中了一個 if 語句,那么就會自動前往這里,然后我就可以執行一些操作了,隨后它就會繼續分析代碼,直到命中下一個斷點。現在我能夠得到這段代碼當中的全部信息了,接下來我就可以添加操作了。我得到的東西是一個 context 對象。
讓我們看一下這個 context 當中的內容。如果這個 if 語句不符合要求,我就可以報告一個診斷過程。我可以得到當前語法樹的相關節點,通常就是這個 if 語句當中的內容。讓我們開始處理吧,我們將鼠標懸停在這個上面,由於我們位於調試模式,因此這里可以看到一個實際傳入的對象。我們可以看到這里的確就是一個 if 語句。讓我們使用 Roslyn 構建的對象模型,將其轉換為 if 語句模型。
這里我能夠得到一個語法樹節點,它恰好是 IfStatementSyntax 的派生類。我們可以聲明 var ifStatement 賦值為該值。現在這就是我們之后唯一所需要調用的對象了,我這里將不再檢查它是否是一個 if 語句。如果這個語句內部的東西不呈現柯里化的話,那么這個東西將被稱為 Block,這就說明這段代碼是不符合規范的。如果它不屬於 SyntaxKind.Block 的話,接下來我就會提示錯誤了。我會告訴用戶:「這里不對」。現在我需要匯報診斷結果。不過現在我還沒有實現,我需要進行一點重構操作,來為之生成一個局部變量。
我可以通過 Diagnostic.Create 創建一個診斷,它需要我提供一些參數。首先是一個被稱為 Rule 的描述符,然后我需要指定 location。也就是當問題出現的時候,我需要在代碼當中顯示波浪線。然后我需要指明當前違背了何種規則。然后指明不符合規則的位置。讓我們再對這段實現進行一下重構,生成一個局部變量。這就是我在調試模式全部所需要做的。
那么什么是所謂的「位置」呢?也就是我當前正在處理的這個節點:if 語句。那么我們需要在那里放置提示信息呢?讓我們放在這個 if 關鍵字上吧。if 語句這里擁有一個 if 關鍵字,因為這是一個具體的語法樹。它擁有里面代碼的全部實現細節,包括所有的位置。讓我們從中取得相關的位置。這里通過 GetLocation 方法來獲取。我們得到 if 關鍵字的位置,然后將這個位置傳入到這個方法當中。我編寫了一些代碼。讓我們移除這個斷點,然后繼續在調試器當中運行。我們等待一會兒,看看發生了什么,好的,現在您會看到 if 語句當中出現了波浪線。
這就是我的全部操作:編寫三四行代碼就可以識別出問題所在,並告訴框架在哪里顯示這個問題。為了向您證明它能夠起作用,我把這段代碼注釋掉,您會發現現在波浪線消失了。
當您試圖實現更為復雜的操作的時候,就變得有點困難了,但是這仍然是一個相對簡單的語言模型,因為它包含了完整的語法和綁定語義,人們可以用它來構建工具,然后與別人分享,這樣便能夠讓每個人在編輯 C# 的過程當中遵循一致的原則,不管它們所用的編輯器是什么,只要是基於 Roslyn 的就行。無論人們位於哪個平台,他們都能夠從中收益匪淺。
我寫的這個分析器同樣可以在批處理模式當中運行。它可以成為編譯過程當中的一部分,可以標記警告或者錯誤,就如同編譯器所做的那樣。我還可以實現修復工具(但我之后的時間不打算演示這個功能),它可以基於我們制定的規則對代碼進行修復。
這就是 Roslyn 這邊的演示,它是如何幫助人們獲得更好的編碼體驗的,一個更好的 C# 開發體驗。它給予了我們更好的代碼庫,更好的架構,顯然在 C# 當中,我們可以對它進行 Dogfood 測試,以更好地幫助我們演進語言本身。
C# 的演進
如今我們對 C# 進行演進較以前已經容易很多了,並且我們可以讓社區通過貢獻代碼而參與到 C# 的演進當中來。然而,這些演進的版本(我不會把所有版本都列述一遍)——展現了我們在創新過程當中的破壞性。
我想以 Async 為例。我們編寫了 LINQ,也就是我們在 C# 版本 3 的時候引入的查詢操作。我們采取了一個很有趣的冷門語言所存在的概念,並嘗試將其主流化,將這些概念引入到 C# 這個主流語言當中,從而幫助它們開擴更廣闊的市場。C# 當中的 LINQ 查詢就是您能夠在其他函數式編程語言當中找到的一個例子。
我們把它們與 Lambda 表達式結合在一起。現在世界上絕大多數語言都可以使用 Lambda 表達式。當然幾年以前這並不常見。如今 Java 也可以使用 Lambda 了。當然,我們還是回到 C# 版本 2 來,在這個版本中我們引入了泛型,僅僅只是慢了 Java 一拍,但是泛型已經成為了 C# 必不可少的一部分。因為在 C# 當中,泛型是深嵌入運行時當中的。Java 則是采取了比較謹慎的方法,讓泛型直接編譯成所需要的東西。
當您將泛型機制實現在運行時當中的時候,這對獲取語義而言是 100% 有好處的,然而這同樣也意味着性能特性將截然不同,尤其是當語言中存在值類型的時候,當 C# 從版本 1 升級的時候就遇到這個問題,Java 可能會在未來采取這種方式來實現泛型。當語言當中存在值類型的時候,您希望泛型能夠識別出它們,並且專門為這些值類型設定特定的規則,也就是在使用泛型的時候就無需對其進行封裝和分配內容空間。這樣泛型就能夠讓代碼更快,而不是產生拖累。
自我們升級之后,泛型便成為了很多語言的標配特性。由於泛型的引進,我們借此便能夠正確地實現查詢功能。由於泛型是深嵌入在運行時當中實現的,因此我們可以使用反射機制,這樣我們便可以實現一些奇怪的代碼引用,然后將 C# 代碼轉換為 SQL。這都是基於類型可以變換的機理才得以實現,並且甚至在運行時都是可以如此使用。
我們將動態值集成到了靜態類型的系統當中,這種類型的類型是動態的、變化的,我們稱之為 Dynamic。再次強調,其底層實現仍然是基於泛型來實現的,這使得其運行效率高效,並且避免了很多無謂的封裝操作。Async 則是深度依賴於泛型機制。在 C# 版本 6 當中,我們引入了 Roslyn,因此對於實現任何特定的語言特性而言,不再是一件難事。我們現在擁有了更多的靈活性,現在我們便可以實現那些還未實現的微小特性了,我們通過實現這些特性,可以使得開發工作更簡單、更優雅、更簡潔、更輕松。
之后我們在 C# 版本 6 當中引入了 swath ,也就是現在的 C# 版本。然后在之后的 C# 版本 7 當中,我們將再次引入一些更重要、更底層的特性,我們想要從函數式編程語言當中進行大量借用特性,我認為我們下一步可能是需要引入相關的特性,來處理那些不必用面向對象的方式處理的數據。大家都知道我們一開始是一門非常純粹的面向對象語言,然后只是傾向於添加一些函數式的特性來作為面向對象的一些擴充,並且我們也在嘗試將這兩者很好地結合起來。這個靈感來自於 Scala,並且 JVM 也在這么做,嘗試將函數式和面向對象結合起來,但是我們絕對不會拋棄面向對象這個根本。
(Scala是一門多范式的編程語言,一種類似java的編程語言,設計初衷是實現可伸縮的語言,並集成面向對象編程和函數式編程的各種特性。)
演示:Async
我打算跳過這個 Async 的演示,因為大多數人可能都知道它的作用是什么了。那么讓我們來談一談即將到來的 C# 版本 7。
(C# 1.0 with Visual Studio.NET
C# 2.0 with Visual Studio 2005
C# 3.0 with Visual Studio 2008,2010(.net 2.0, 3.0, 3.5)
C# 4.0 with Visual Studio 2010(.net 4)
C# 5.0 with Visual Studio 2012(.net 4.5),2013(.net 4.5.1, 4.5.2)
C# 6.0 with Visual Studio 2015(.net 4.6, 4.6.1, 4.6.2)
C# 7.0 with Visual Studio 2017(.net 4.6.2, 4.7)
C#版本是VS版本決定的,也可與相應的.net framework版本對應起來)
C# 版本 7
讓我們從元組 (tuple) 開始。首先這里我有一個完整的程序。然后里面有一些數字值。因為數字是非常容易理解的,但是可能不是所有人都能夠知曉其中的含義:我們現在擁有的是二進制的字面量。這是一個很微小的特性。當您教孩子編程的時候,這就非常有用了,這些位於數字下方的則是位 (bit)。我現在要再添加一個。我們這里同樣還有數字分隔符,就像其他語言所做的那樣,您可以將這些分隔符放在需要的地方,從而讓數字更容易閱讀。
我這里打算實現一個名為 Tally(計數器) 的方法,它將數組當中的數字累加起來,得出計算結果。這樣我們便可以對這些數字調用這個 Tally 方法。當然目前這個方法我還沒有實現,讓我們使用重構來生成這個方法。這是一個靜態方法。目前它的返回值為 Void。或許它應該返回其他的東西。我覺得它應該要返回這個累加值,或者直接返回數字的總數?我覺得兩者都是非常重要的。但是現在在 C# 當中您只能返回一樣東西,但是在不久的將來,您就可以返回兩個返回值了,甚至三個、四個、更多。只要您想,您實際上可以創建一個超大的元組,但是這可能是個糟糕的主意。
現在讓我們返回兩個 int 值。這是一個元組類型。它表示里面有兩個 int 類型,這應該很容易理解。這里是元組字面量,我們還是先返回一些虛擬的值。這里面包含了一些所需的值,通過括號和逗號包裹起來,當然這個語法應該比較正常。當我使用這個方法的時候,我可以獲取它的返回值,然后我會發現我得到了一個元組類型。
那么我們該如何使用元組呢?讓我們將其輸出出來。插入字符串。總和可能位於這里的第一個位置。讓我們來看一下元組有些什么:Item1 和 Item2。很顯然,我們知道它們分別代表了什么,我們可以直接使用這兩個名稱。雖然這個名字比較糟糕,但是能用就行。C# 當中的元組還可以為不同的元素賦予不同的名稱。這里我將指定各個元素的名稱。這是什么意思呢?
當我獲取到這個元組的時候,它便可以告訴我它里面的元素是什么。這同樣也意味着我來到這里,輸入點語法,就可以看到這些預覽而已;最終的版本應該需要隱藏掉這些糟糕的名字。這里我們會看到擁有了很顯然的名字,因為這里您可以看到 sum,我們可以直接使用它來獲取元組當中對應的值。之前那些是底層當中的真實名稱,但是編譯器知道跟蹤這些別名,並使用它們來替代顯示。
元組當中擁有元素別名是非常重要的,因為您很可能記不住這個元組是姓在前還是名在前。因此元組需要提供這些信息,這樣才能夠讓人容易理解。您需要有獲取別名的能力。
當然,您很可能會希望在獲取到元組的時候,就立刻將其解構,將元組當中的值分開,當然您也可以在 C# 當中做到這一點。您可以在這里聲明 sum, count,然后元組便會立刻解構為 sum 變量和 count 變量。這樣我們便可以不再使用 t. 前綴,而是直接使用 sum 和 count。
讓我們現在來實現這個方法。這里我們不再返回一個虛擬值,讓我們返回一個真實的結果。這里我們對這些數字執行 foreach 操作;這里我們將其稱為 values。接下來我們在每次遍歷的時候更新結果值。
result 這個名字太長了,我想將其命名為 r。讓我們聲明 r = 這個新的元組字面量。這里我希望能夠獲取舊有的值。我希望 r 同樣也有元素別名。讓我們前去給其增加別名。您可以在元組字面量當中為其賦別名。這里的語法和命名參數所做的相同。現在 r 擁有了 s 和 c 兩個元素別名。我們可以調用 r.s,便可以在這里獲取到之前的總和值,然后加上新的值 v,然后這里的 r.c 總數需要加 1。
您可能會在想,這不是很復雜么,很浪費空間。這不是每次都直接分配一個新的數組?或者說在每次遍歷的時候偶會創建一個新的元組么?這樣做的話,在資源受限的設備或者需要花錢的雲端當中是不是很不好?為這些元組分配內存空間是不是非常浪費?
這並不會導致空間的浪費,因為元組不屬於對象。元組被實現為值類型,是以 C# 當中的結構體實現的。因此它們不會分配內存空間。它們只是直接更新某些在堆上的東西。這些值類型是使用 copy 來傳遞的,而不是通過引用來傳遞。元組沒有標識,它們當中僅僅只是包含值而已(我覺得元組應該是這樣的)。它們應該只是短暫的存在。因此它們不應該擁有生命周期,這樣才能更有效率。
元組不僅是值類型,它們同樣也是可變類型。我知道在函數式陣營當中的人們會很反對這種做法,但是我還是堅持元組是可變類型。您可以修改元組里面的值。而不是這樣子寫:r.s += value。作為一個單獨的語句 r.c++ 就很好了,就不用更多的重復了。此外我還可以交換元組當中元素的位置,這並不是危險的操作,因為在線程之間沒有共享的可變狀態,因為這是一個結構體。沒有對象去共享它。您可以隨意將其傳遞到任何地方,它是用拷貝操作執行的,不存在危險的情況。
為什么我們總要強調面向對象呢?為什么一切都必須要封裝起來呢?元組沒有屬性,僅僅只是字段。它們是包含某些可變公共字段的結構體。獲取非常簡單,您可以很輕松地明白自己的做法。這就是元組的正確用法,因為它們不是抽象類型;它們不會封裝任何東西——它們僅僅只是值而已。
關於元組的其他幾件事是:由於元組是一種類型,因此它可以判斷相等。例如,您可以將元組作為字典當中的鍵來使用。如果您想要讓兩個類型都作為某個字典的鍵,那么使用元組是再好不過的,這樣一切都相安無事。哈希值和其他所用的東西在數據結構當中都能夠正常的工作。
當然,這也是從異步方法當中獲取多個值的絕佳方法,因為如果操作的是異步的話,您可以返回 Task 的元組,這樣當所有的操作結束之前,您就可以依次等待。得到元組之后,就可以對其解構並繼續前進。元組是很好的傳輸工具。對於 async 方法以及其他方法而言,如果有多個返回值的話是非常糟糕的,因為您沒辦法輸出多個參數,但是現在通過元組您可以實現這個操作了!
(如果需要使用同一類型的多個對象,可以使用集合和數組;如果需要使用不同類型的多個對象,可以使用元組(Tuple)類型。.NET Framework定義了8個泛型Tuple類和一個靜態Tuple類,它們用作元組的工廠。元組用靜態Tuple類的靜態Create()方法創建。Create()方法的泛型參數定義了要實例化的元組類型。)
未來展望:更多的模式
我們開始向 C# 當中添加模式匹配 (pattern matching)。
1 if (o is Point(5, var y)) { WriteLine($"Y: {y}"); } // 遞歸模式
2
3 state = match (state, request) // 匹配表達式,匹配元組
4 { 5 (Closed, Open) => Opened, 6 (Closed, Lock) => Locked, 7 (Opened, Close) => Closed, 8 (Locked, Unlock) => Closed, 9 };
這里我們從函數式陣營當中引入了一個全新的理念,我們正在逐步實現這個功能。您會在未來的 C# 版本當中見到更多的內容,但是現在讓我們跳過這里,介紹一下第一種模式。
讓我們把這個例子變成包含遞歸數字列表的情況。這里我們用的不是 int 數組,而是一個對象數組,其中我們有一個約定,其內部的東西是 int 值或者是其他包含 int 的數組,也可以是新的對象數組,其中有一些 int 值嵌套在當中。或許如果里面也可以包含 null 可能會讓人能更加明白,現在我們需要更新一下我們的這個 Tally 方法,讓其能夠處理這個數組。
首先讓我們直接替換為這個對象數組,好的現在我們得到了一個錯誤,因為這個 v 不再是 int 類型了;他是一個對象。我們需要知道它是否是 int 值,如果是我們就添加它。因此我們需要進行一些邏輯處理;在過去,我們會執行一個類型檢測。如果 v 是 int 類型的話,然后我們就執行轉換並處理;但是,即便我們檢測出它是 int 類型,這里我們實際上仍然不知道它是什么。我們必須再次執行檢查才能將其放入。
相反在這里,您可以將其認為是對 is 表達式的一個擴展。您現在可以聲明一個變量。當您詢問它是否 is int 的時候,如果是,那么就會取這個 int 值並將其賦到這個新的變量 i 當中。接下來變量 i 就有 v 的值了,但是類型已經確定為 int 了。現在我們就可以在這里將其添加進去,運轉良好。
is 表達式現在擴展為允許模式的存在了,這是 C# 當中引入的一個新概念。模式,而不是類型。模式可以是很多復雜的組合。現在模式還不能很復雜。基本上只能夠允許常量或者類型模式。例如,可以設定常量值 v is 7(現在這個被允許了,因為這屬於常量模式)。我們正在實現更多的模式,將它們集成到語言特性當中來,比如說表達式。
另一個我們正在集成的地方是,我們正在嘗試將其整合到 switch 語句當中。我現在可以對任何東西進行 switch,而原來 swtich 只可以對原子類型進行操作。這是很古老的特性了,不過現在它可以對任何東西進行操作了。我們可以對對象進行操作:switch on v。在我的這個 switch 語句當中,我可以列舉沒有任何常量存在的情況,現在這個屬於一種特殊的模式,不過可以對任何模式進行操作。我可以這么聲明 case int i。(我必須記得要 break,這就是為什么在這里我得到了一個波浪線)。
我這里已經用了一種模式。我擴展了 switch 語句當中了 case 塊,以便其能夠應用某種模式,並且可以「當這種模式適用時,就執行此 case 塊」。我可以對 swtich 語句進行現代化。我可以讓對象數組成為 case 的條件,這也是我所期待的另一件事。讓我們將其聲明為 a,我可以將條件放到 case 里面。我可以設定「我只需要長度大於 0 的對象 a,因為 a.Length 大於 0(否則就不必執行其他操作了)」。在這種情況下,我可以設定 var t = Tally,然后加入嵌套數組,並將結果添加到 r;r = r。您知道后面的用法:r.s + t.sum、r.c + t.c。然后 break 退出。這是對既有模式特征的一種泛化,也就是 C# 當中模式匹配所擁有的程度。
在未來,我們希望能夠加入更多的模式。我們需要更智能的模式。您應該需要能夠使用遞歸模式。我們讓您能夠指定一個給定的類型,讓其能夠被解構。例如,您可以在 Point 類型進行指定,這樣它就可以被解構,就像我們之前對元組進行解構,分解為不同的變量里面。當類型被設定為可解構的之后,我們就將其與模式匹配結合在一起,並允許您能夠:同時檢查 o 是否是 Point 類型,如果是的話就對其進行解構,並且可以應用遞歸模式:如果 o 是一個 Point,那么這個點的第一個部分 x is 5,然后將第二個部分放到變量 y 當中。您可以得到更智能的模式。您也可以使用它來構造不可讀的代碼,但是一般而言,如果能夠更深入模式,那么您就會發現模式是非常有用的。
我們應該需要在新的地方當中添加模式。switch 語句是 20 世紀 60 年代的產物了。或許我們可以新增一個 switch 語句的表達式版本。或許是一個匹配表達式,而這是函數式語言當中所稱呼的,它具有更新的語法,基於表達式,然后 case 語句中也可以添加表達式,這樣可以讓代碼更為簡潔。但是現在我們已經有了模式的概念,我們可以添加新的模式,然后向新的地方添加新的模式。這就是我們下一個版本的 C# 所需要關注的一件事,我們已經在努力實現它們,因為 C# 版本 7 已經差不多完成了。(我們還沒有發布,也不要問我什么時候發布)。
(此部分關於C#新版本模式的概念理解起來比較模糊,還是待以后版本發布后實際使用一下體現會更貼切。)
未來展望:可空的引用類型
1 string? n; // 可空的引用類型
2 string s; // 不可空的引用類型
3 n = null; // 允許;它是可空的
4 s = null; // 警告!不應該為空
5 s = n; // 警告!類型不同
6 WriteLine(s.Length); // 允許;是不可空的
7 WriteLine(n.Length); // 警告!它可能為空
8 if (n != null) { WriteLine(n.Length); } // 允許;您進行了類型檢查
9 WriteLine(n!.Length); // 如果存在的話當然可以
新的語言當中,有一個特性正在成為主流,那就是類型系統能夠進行區分的能力,也就是判斷類型是否可空。
變量有時候可能會為 null,因為它是值域的一部分;但是有些時候我並不希望空值出現,那么為什么我需要隨時對引用錯誤進行處理呢?Swift 也有這項功能。我們能否讓 C# 也實現這些功能呢,即便我們現在已經推出了 7 個版本,而可空性完全是一個基於運行時的玩意兒呢?
我們認為可以:我們已經在 C# 當中為可空值類型留下了尾隨問號標志。如果我們允許您將它應用於引用類型,或許這就是您陳述某個類型為空的方式。另一方面,如果您不這么聲明的話,就說明您期望那里的東西不可能為空。
我們將幫助您進行維護,這意味着我可以將 null 分配給 n,但是不能夠分配給 s,並且如果沒有任何限定條件的話我也無法將 n 賦值給 s,因為 n 的值很可能是 null。我保護變量以防止它持有不該持有的值。另一方面,當我想要使用這個引用的時候,我可以無需任何限定就執行 s.Length,因為我們知道它可能不會為 null。我們無法讓像 C# 之類的語言做一個保證,保證這里一定有值。
n.Length 會警告您它的值可能是 null,您可能會得到 null 引用異常。解決的方法是,這些新語言當中存有一種新的空值檢查特性。它們有一種新的模式匹配方法(或者某種可以用來檢測 null 的東西)。我們不打算改變您檢查 null 的方式。在 C# 當中,已經有 7 種檢查空值的方式存在了。相反,我們想讓編譯器對其進行跟着,來檢查某個類型是否為空。
如果您有一個 if 語句,來判斷 n 不為空的時候才進入到其中,那么我們就知道這個范圍已經經過了類型檢查了。事實上它的值不可能為 null,我們將會假設您使用點語法訪問內部成員是沒有任何問題的。那么有沒有別的辦法處理它呢?當然有,然而您現在仍然還需要使用這種方式。您必須要隨時使用這種方法才能消除所有的空值引用異常。
此外還有一個「強制」操作符,您可以給某個可空值上面加一個前置感嘆號 (!),這就意味着您強制讓其不能為空。您知道在這里,它的值永遠不可能為空,只要你能足夠勇敢、足夠肯定,那么您就可以使用點語法,也就沒有警告的產生。我們已經在開發這個功能,我們希望能夠在下一代 C# 當中得到這個功能。
希望這個功能是非常有用的。關於這個特性的一件趣事是:它不僅需要深入到語言內部進行調整,還需要確保我們所有的框架都應用上這個特性,這樣您才能夠確保自己的代碼應用上了正確的可空值。這是一個非常具有挑戰性的功能,我覺得這是非常值得的。
(個人見解:微軟在下一個C#版本中增加此定義, 目的還是為了代碼的安全性。現在的引用類型在定義時並沒有如此分開定義聲明,以后在定義如string s這種定義時,可潛移默化的表示是不可以為null的,這樣一方面可以不用進行if(s==null)這樣的判斷;另一方面也同時保證了在忘記進行此類判斷時,程序也不會“拋出未將對象引用到定義”等此類的異常。反之,若想定義可以為null的引用類型,則可以以string? s的形式示人,算是微軟在語法方面更加規范化了。)
備注:本人基於對原文的理解,增加了個人備注(紫色斜體括號部分),若有錯誤,請讀者提出意見和看法,願和大家一起進步!
原文鏈接 by Mads Torgersen on Dec 27 2016
