深入分析Go語言與C#的異同

2023-06-19 15:02:35

摘要:本文由葡萄城技術團隊於部落格園原創並首發。轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

前言

為了更加深入地介紹Go語言以及與C#語言的比較,本文將會從多個維度出發進行詳細的闡述。首先,將從Go語言的關鍵字方面介紹Go與C#在語言特性上的異同,並且探討兩種語言在關鍵字方面的優化和不足之處。其次,本文將通過程式碼範例、效能測試等方式,展示Go語言在關鍵字方面的優勢,從而為讀者呈現出Go語言的強大之處。除此之外,為了更好地幫助讀者理解Go語言,本文還將介紹一些優秀的Go語言工具和社群資源,供讀者進一步學習和探索。相信通過這些內容的全面介紹,讀者們會對Go語言有更全面深入的認識和了解。

文章目錄:

1.Go的前世今生

​ 1.1Go語言誕生的過程

​ 1.2逐步成型

​ 1.3正式釋出

​ 1.4 Go安裝指導

2.Go和C#的關鍵字比較

​ 2.1Go與C#都有的關鍵字

​  2.1.1.Var

​  2.1.2. Switch-case-default

​  2.1.3.If-else

​  2.1.4. For

​  2.1.5. Struct

​​ 2.2Go與C#不太一樣但是意思差不多的關鍵字

​  2.2.1. Package與namespace

​​  2.2.2. Import與using

​​  2.2.3. Type與class

​  2.2.4. Defer與finally

​ 2.3Go有而 C#沒有的關鍵字

​​  2.3.1. Fallthrough

​​  2.3.2.Func

3.文章小結
4.擴充套件連結

1.Go的前世今生

1.1Go語言誕生的過程

話說早在 2007 年 9 月的一天,Google 工程師 Rob Pike 和往常一樣啟動了一個 C++專案的構建,按照他之前的經驗,這個構建應該需要持續 1 個小時左右。這時他就和 Google公司的另外兩個同事 Ken Thompson 以及 Robert Griesemer 開始吐槽並且說出了自己想搞一個新語言的想法。當時 Google 內部主要使用 C++構建各種系統,但 C++複雜性巨大並且原生缺少對並行的支援,使得這三位大佬苦惱不已。

第一天的閒聊初有成效,他們迅速構想了一門新語言:能夠給程式設計師帶來快樂,能夠匹配未來的硬體發展趨勢以及滿足 Google 內部的大規模網路服務。並且在第二天,他們又碰頭開始認真構思這門新語言。第二天會後,Robert Griesemer 發出瞭如下的一封郵件:

可以從郵件中看到,他們對這個新語言的期望是:在 C 語言的基礎上,修改一些錯誤,刪除一些詬病的特性,增加一些缺失的功能。比如修復 Switch 語句,加入 import 語句,增加垃圾回收,支援介面等。而這封郵件,也成了 Go 的第一版設計初稿。

在這之後的幾天,Rob Pike 在一次開車回家的路上,為這門新語言想好了名字Go。在他心中,」Go」這個單詞短小,容易輸入並且可以很輕易地在其後組合其他字母,比如 Go 的工具鏈:goc 編譯器、goa 組合器、gol 聯結器等,並且這個單詞也正好符合他們對這門語言的設計初衷:簡單。

1.2逐步成型

在統一了 Go 的設計思路之後,Go 語言就正式開啟了語言的設計迭代和實現。2008 年,C語言之父,大佬肯·湯普森實現了第一版的 Go 編譯器,這個版本的 Go 編譯器還是使用C語言開發的,其主要的工作原理是將Go編譯成C,之後再把C編譯成二進位制檔案。到2008年中,Go的第一版設計就基本結束了。這時,同樣在谷歌工作的伊恩·泰勒(Ian Lance Taylor)為Go語言實現了一個gcc的前端,這也是 Go 語言的第二個編譯器。伊恩·泰勒的這一成果不僅僅是一種鼓勵,也證明了 Go 這一新語言的可行性 。有了語言的第二個實現,對Go的語言規範和標準庫的建立也是很重要的。隨後,伊恩·泰勒以團隊的第四位成員的身份正式加入 Go 語言開發團隊,後面也成為了 Go 語言設計和實現的核心人物之一。羅斯·考克斯(Russ Cox)是Go核心開發團隊的第五位成員,也是在2008年加入的。進入團隊後,羅斯·考克斯利用函數型別是「一等公民」,而且它也可以擁有自己的方法這個特性巧妙設計出了 http 包的 HandlerFunc 型別。這樣,我們通過顯式轉型就可以讓一個普通函數成為滿足 http.Handler 介面的型別了。不僅如此,羅斯·考克斯還在當時設計的基礎上提出了一些更泛化的想法,比如 io.Reader 和 io.Writer 介面,這就奠定了 Go 語言的 I/O 結構模型。後來,羅斯·考克斯成為 Go 核心技術團隊的負責人,推動 Go 語言的持續演化。到這裡,Go 語言最初的核心團隊形成,Go 語言邁上了穩定演化的道路。

1.3正式釋出

2009年10月30日,羅伯·派克在Google Techtalk上做了一次有關 Go語言的演講,這也是Go語言第一次公之於眾。十天後,也就是 2009 年 11 月 10 日,谷歌官方宣佈 Go 語言專案開源,之後這一天也被 Go 官方確定為 Go 語言的誕生日。

(Go語言吉祥物Gopher)

1.4.Go安裝指導

1.Go語言安裝包下載

Go 官網:https://golang.google.cn/

選擇對應的安裝版本即可(建議選擇.msi檔案)。

2.檢視是否安裝成功 + 環境是否設定成功

開啟命令列:win + R 開啟執行框,輸入 cmd 命令,開啟命令列視窗。

命令列輸入 go version 檢視安裝版本,顯示下方內容即為安裝成功。

2.Go和C#的關鍵字比較

Go有25個關鍵字,而C#則有119個關鍵字(其中包含77個基礎關鍵字和42個上下文關鍵字)。單從數量上來講,C#的數量是Go的5倍之多,這也是Go比C#更簡單的原因之一。

Go中的 25 個關鍵字:

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

(Go關鍵字)

2.1Go與C#都有的關鍵字

  • break
  • case
  • const
  • continue
  • default
  • else
  • for
  • goto
  • if
  • interface
  • return
  • struct
  • switch
  • var

以上14個關鍵字是Go和C#共有的,它們之中大部分的用法都是完全相同的,這裡重點說一下在Go中有特殊語法的關鍵字。

2.1.1.Var

Var在Go中用來表示定義變數,但語法和 C#不同。C#中只有一種定義變數的方法,而 Go中有兩種,它們分別是:

  • 普通方式
var i int = 1

這種方式是Go的原始變數定義方式,一般包級別的變數都是這樣定義的,並且如果定義那些編譯器可以自動推斷的型別,比如上述的例子,其後的型別可以省略。

  • 語法糖(是的,Go中也有語法糖…)
i, j := 1, "hello"

上述程式碼可簡寫為語法糖形式。事實上,Go程式碼中,90%變數都以此方式定義,因為幾乎所有函數都有多個返回值,這種定義方式可省去許多麻煩。

2.1.2.Switch-case-default

Switch-case是一個連用的方法,但是case和default這兩個關鍵字在 Go中除了可以和 switch 連用,還可以和select 語句連用。

同時Go中預設把 switch 語句的一個弊端修復了,即 switch 子句中不用再寫 break 了。

switch n := "a"; n {

case n == "a":

fmt.Println("a")

case n == "b":

fmt.Println("b")

case n == "c":

fmt.Println("c")

}

上面這段程式碼的fmt是Go中的標準輸出包,其中的Println函數等同於C#中的Console.WriteLine方法。同時這段程式碼的最終結果只會輸出a,而 在C#中,同樣的程式碼會把abc全部輸出出來,這也是Go為何比C#簡單的原因之一。

除此之外,switch 語句後面出現了一種全新的寫法:n := "a"; n,這種寫法在Go中的控制語句(if, else if, switch-case, for)中都可以使用,分號前是變數的定義,分號後是定義的判斷條件。這種語法優點類似於 C#中的普通 for 迴圈的前兩個子句。

最後,可以發現switch之後沒有跟小括號,因為在Go中,控制塊的子句後面都是不需要寫小括號的,如果寫了同樣會被 gofmt 自動格式化掉。

2.1.3. If-else

Go中的if-else和C#幾乎也是相同的,它倆最大的區別是Go中特殊語法,可以在 if-else 控制塊中直接給變數賦值並且在控制塊中使用這些值。

func isEven(n int) bool {

return n % 2 == 0

}

func main() {

if n := rand.Intn(1000); isEven(n) {

fmt.Printf("%d是偶數\\n", n)

} else {

fmt.Printf("%d是奇數\\n", n)

}

}

2.1.4. For

Go中的迴圈控制語句有且只有一個 for 關鍵字。而 C#中的 while、foreach 等在Go中都是通過 for 的各種變形達成的。

  • while 語句
for true {

// ...

}
  • for 語句
for i := 0; i < n; i++ {

// ...

}

Go中普通的 for 迴圈和 C#中唯一的差別就是 i++從表示式變成了語句。也就是說,Go中沒有i = i++這樣的語法,也沒有++i這樣的語法,只有i++這種語法。

  • foreach 語句
array := [5]int{1, 2, 3, 4, 5}

for index, value := range array {

// ...

}

foreach 語句的寫法和 C#中很不相同,上述的例子是 foreach 遍歷一個int型別的陣列,其中用到了一個range關鍵字,這個關鍵字會把陣列拆分成兩個迭代子物件index 和value,for range可以遍歷陣列、切片、字串、map 及通道(channel),這個語法同樣類似於 JavaScript 的迴圈語法。例如下面的程式碼就是遍歷陣列中的值並輸出:

for key, value := range []int{1, 2, 3, 4} {

fmt.Printf("key:%d value:%d\\n", key, value)

}

程式碼輸出如下:

key:0 value:1

key:1 value:2

key:2 value:3

key:3 value:4

2.1.5 Struct

Go中的struct關鍵字和C#中的作用是相同的,即定義一個結構體。因為Go中是沒有類這個概念的,所以struct就相當於是C#中class的定義。同樣的,struct在Go中是值型別結構,因此使用的時候一定需要注意案值傳遞導致的複製問題。(需要注意的是Go中的struct只能定義欄位,不能定義函數。)

// struct的定義是配合type關鍵字一起使用的

type People struct {

name string // 定義的欄位和Go語言其他的風格相同,名字在前,型別在後

age int

}

2.2Go與C#不一樣但使用方法差不多的關鍵字

  • package
  • import
  • type
  • defer

2.2.1.Package與namespace

Go中的package和C#的namespace基本相同,就是定義組織的一個包,主要作用是對程式碼模組進行隔離。但Go和C#不同的是,C#十分靈活,即使不在一個資料夾下的程式碼都可以定義為相同的namespace。但是Go中package內的檔案都需要在相同的資料夾內才能被正確編譯,並且一個資料夾內只能出現最多一個包名。除此之外,類似於C#中的Main方法,Go中可執行程式的執行入口也是一個 main函數,但是main函數必須定義在package main下。

// Go中,同一個資料夾只能同時存在一個包名

// 包名可以和資料夾名不同,但是必須有且只有一個

package main

// main函數只能在main包下才能正確作為啟動函數執行

func main() {

//do something

}

// 同資料夾下的另一個檔案,比如hello.go

package hello //編譯器報錯

2.2.2.Import與using

Import和using的作用都是用來匯入其他模組的程式碼。但是Go比C#多了一個強制要求:沒有在程式碼模組中使用import或者是定義了但是沒有使用的變數,在編譯時會直接報錯。這樣做的目的除了使程式碼看起來更簡潔以外,最主要的原因是import語句還有另一個重要功能就是呼叫包中的init()函數。例如如下程式碼:

// hello資料夾下的demo資料夾內的 demo.go

package demo

var me string

func init() {

me = "jeffery"

}

func SayHello() {

fmt.Printf("hello, %s", me)

}

// hello資料夾下的hello.go

package main

import "hello/demo"

func main() {

demo.SayHello() // 輸出:hello, jeffery

}

上述的程式定義了一個demo檔案,當demo檔案第一次被import關鍵字載入到其他包時,會自動呼叫其init()函數,這時就會把變數me賦值為jeffery,之後呼叫SayHello()函數時,返回的就都是」hello, jeffery」了。也正是因為init函數的存在,不使用的import需要被刪除,因為如果不刪除很有可能會自動呼叫到未被使用的包內的 init 函數。

2.2.3. Type與class

  • 常規用法

把 type和class 對比其實是不太合理的。因為 C#中class關鍵字是定義一個型別和這個型別的具體實現,比如下述的程式碼在 C#中的意思是定義一個名為People的類,並且定義了這類中有一個屬性 Age。

interface IAnimal {

public void Move();

}

class People {

public int Age { get;set; }

}

然而Go中的type關鍵字僅僅是用來定義型別名稱的,如果想要定義其實現,必須後面再更上具體實現的關鍵字。比如上述的程式碼定義在Go中就變成了如下:

type IAnimal interface {

Move()

}

type People struct {

Age int

}

上述只是 type 的最常用用法,除此之外 type 還有兩個其他的用法:

  • 以一個基準型別定義一個新型別
type Human People

這樣的語句相當於用People型別定義了一個Human的新型別。注意,這裡是一個新型別,而不是 C#中的繼承。因此如果People內有一個Move函數,那Human物件是無法呼叫這個Move函數的,如果非要使用,則需要強制型別轉換。(Go中的強制型別轉換是型別+ (),比如上述的例子 Human(People)就可以把 People 型別強轉為 Human 型別)

  • 定義型別別名
type Human = People

如果使用了等號進行定義,那就相當於給型別 People 定義了一個別名 Human,這種情況下 People 中的程式碼 Human 也是可以正常使用的。上面兩種用法基本都不常用,這裡只做瞭解即可。

2.2.4.Defer與finally

Go中的defer和C#的finally是一樣的,在一個方法執行結束退出之前只可以幹一件事。而和 C#不太一樣的是,Go中的 defer 語句不用必須寫在最後,比如我們會經常看到這樣風格的程式碼:

var mutex sync.Mutex // 一個全域性鎖,可以類似的等價於C#中的Monitor類

func do() {

mutex.Lock()

defer mutex.Unlock()

// ...

}

上面這個例子的意思是定義一個全域性鎖,在do函數進入時,加鎖,在退出時解鎖。之後再去寫自己的業務邏輯。除此之外,defer也可以寫多個,但最終的執行順序是從下向上執行,也就是最後定義的defer先執行。

2.3Go有而 C#沒有的關鍵字

  • fallthrough
  • func

2.3.1. Fallthrough

這個關鍵字是為了相容C語言中的 fallthrough,其目的是是在 switch-case 語句中再向下跳一個case,比如下面這個例子:

switch n := "a"; n {

case n == "a":

fmt.Println("a")

fallthrough

case n == "b":

fmt.Println("b")

case n == "c":

fmt.Println("c")

}

這個例子的最終輸出結果就是:

a

b

2.3.2.Func

和其他函數(比如 JavaScript 的 function,Python 中的 def)一樣,Go中的 func就是用來定義函數的。

// 定義了一個函數名稱為getName的函數

// 其中包含一個int型別的引數id

// 以及兩個返回值,string和bool型別

func getName(id int) (string, bool) {

return "jeffery", true

}

Go中的函數以及其他一系列需要定義型別的語法中,永遠都遵循名稱在前,型別在後。此外,Go中的func同樣也可以配合type使用定義C#中的委託,比如我們可以在 C#中定義一個.Net Core 的中介軟體:

public delegate void handleFunc(HttpContext httpContext);

public delegate handleFunc middleware(handleFunc next);

這樣的程式碼可以在 Go中這樣實現:

type handleFunc func(httpContext HttpContext)

type middleware func(next handleFunc) handleFunc

3.文章小結

Go語言相較於C#在關鍵字上的優點有以下幾個:

1.更簡潔的語法:Go語言致力於簡化程式碼的編寫和理解,使得語言關鍵字的數量更少,更加簡潔明瞭。相比之下,C#擁有更多的關鍵字,從而使程式碼的可讀性稍微降低。

2.更好的並行性支援:Go語言天然支援並行程式設計,通過goroutine和channel管道,可以輕鬆實現高並行的程式。而C#對於並行程式設計需要手動處理鎖,號誌等機制來控制執行緒的並行,程式碼比較繁瑣。

3.更好的記憶體管理:Go語言使用垃圾回收機制,不需要開發者手動管理記憶體,避免了許多記憶體漏失等問題。相比之下,C#需要開發者手動進行記憶體管理,需要使用using關鍵字或者手動釋放記憶體等機制來控制記憶體,這增加了程式碼的複雜性。

4.更好的效能:由於採用了更簡潔的語法和更好的記憶體管理,Go語言編寫的程式具有更好的效能表現。與C#相比,Go語言的程式不僅執行速度更快,而且資源消耗更少,效能更出色。

4.擴充套件連結

如何使用 Blazor 框架在前端瀏覽器中匯入/匯出 Excel XLSX

萬物皆可整合系列:低程式碼對接微信小程式

C#編寫伺服器端API