為什麼推薦你用 C# 構建大型後端應用?- Part 1

語言: CN / TW / HK

前言

今天下英雄,惟使君與操耳。--曹操《三國演義》

對於在 IT 圈摸爬滾打多年的程式設計師來說,如果要問國內最主流的後端程式語言,我相信大部分會說 Java。這並不意外,因為 Java 存在了 30 多年,有著龐大的使用者規模和生態體系,在軟體工程領域似乎有著絕對霸主地位。但是,古人云:“得民心者得天下。” 使用者最多的程式語言不一定是最受開發者們喜歡的。根據 StackOverflow 2021 年在 82,914 名開發者中做的關於程式語言滿意度調查,喜歡 Java 的佔比只有 47%,已經排到 20 名開外了,僅高於 PHP、C、COBOL。另一方面,我們從調查結果可以看到,有著 “山寨版 Java” 之稱的 C#,反而在開發者心目中滿意度達到了 62%,比 Java 高 15%。雖然 C# 的滿意度跟 Rust、TypeScript 差距還比較大,但可以看出 C# 作為 Java 的替代程式語言,在開發效率、部署便捷性、文件完善度等方面已經逐漸佔據優勢。筆者因為工作的原因,在平時開發中使用 C# 和 Java 開發了不少專案,因此對它們之間的相同點、不同點以及優勢、劣勢有一定了解。筆者認為,C# 相對於 Java 來說更受開發者歡迎是有一定道理的,因為它的開發體驗很好。

20211119-language-satisfaction

限於篇幅原因,整個 C# 的原理及實戰介紹(即為何推薦用 C# 構建大型後端應用),將被拆分為一系列文章,該系列將從語法特性、開發模式、生態體系、部署構建等維度深度分析 C# 這門 “年輕” 的程式語言,並以跨平臺框架 .NET Core 為例介紹如何用 C# 構建大型後端應用。

本篇文章是 C# 系列文章的第一篇,主要在語法特性方面介紹 C# 的一些現代語法特性,以及它們是如何提高開發效率的。

C# 簡介

C# 是 2000 年由微軟釋出的新型程式語言。它在誕生之初就因為與 Java 極高的相似度而被貼上 “山寨 Java” 的標籤。C# 跟 Java 類似,是面向物件程式設計(OOP)程式語言,包含類、方法、介面、單一繼承等 OOP 元素,而且是 Windows .NET 網路框架的基礎語言。C# 語言的釋出與 SUN 公司對微軟的一個訴訟有關,是為了取代造成糾紛的 Java 變種語言 Visual J++。

從釋出 1.0 版本到現在,C# 大大小小經歷了多次迭代,至今隨 .NET 5 一起釋出了 C# 9.0 版本。從最早的面向物件支援,到後來更為豐富的功能,例如非同步程式設計、跨平臺支援等。C# 在變得越來越強大的同時,也遵循著 “簡單、現代、通用” 的設計標準,在如今越來越複雜的後端開發以及架構要求中顯得如魚得水,既可以開發 Windows 桌面端應用,還可以用於更加具有擴充套件需求的分散式系統和微服務架構等。

C# 語法特性

可能很多 C# 的特性都類似於 Java,例如型別系統、面向物件程式設計、依賴注入、異常處理等。這一小節將介紹更多跟 Java 不一樣的特性,而這些特性很大程度上提升了 C# 的開發體驗,讓開發者更加青睞於 C#。

空值操作

在使用 C# 開發專案的過程中,我發現 C# 中對空值的判斷和操作非常簡單,不會像 Java 中這麼繁瑣。下面會舉幾個例子來說明 C# 的語法優越性。

例子 1

如果要從 JSON 物件中獲取較深層的元素,一般來說需要做空值判斷,這樣不可避免的會導致很多諸如 if (value == null) { ... } 這樣的判斷語句,顯得異常囉嗦。而在 C# 中,這個空值判斷被簡化為了一個問號 ?。例如,你可以這麼來寫獲取深層 JSON 元素的程式碼。

// access index C in member B of A A?.B?[C];

用這樣的方式,你避免了大量的 if 語句,如果其中一個成員不存在,將自動將整個獲取值的表示式返回為 null,這大大簡化了空值帶來的冗餘程式碼。

例子 2

不少時候,你可能會希望給一個表示式設定預設值,如果該表示式為空值 null,就返回該預設值。例如下面這個例子。

// set default value of text if input is null var text = input ?? '-';

用兩個問號的操作符 ??,你將可以在 C# 輕鬆的設定預設值。否則,例子 1 一樣,你可能將不得不加入不必要的 if (value == null) 的判斷語句。?? 這樣的操作符也是 C# 中節省程式碼的方式。

C# 還有不少其他空值操作的語法,例如 null 包容運算子、可空型別,但是上面舉的兩個例子是 C# 中利用空值操作符的最常見例子,對平時的 C# 專案開發來說非常有用。

如果你對本技術部落格另一篇介紹 TypeScript 的技術文章 《為什麼說 TypeScript 是開發大型前端專案的必備語言》 有印象的話,你應該會記得 TypeScript 也有這樣的空值操作語法。是的,TS 是借鑑 C# 過來的,因為 TypeScript 的建立者正是 C# 之父 Anders Hejlsberg!

隱式型別本地變數

為什麼很多 Java 開發者會抱怨說寫 Java 就像在寫又臭又長的八股文,就是因為在 Java 中每宣告一個新變數你都不得不去思考它的型別,從而需要花大量的腦容量來推演和記憶臨時變數的型別。這對於開發效率來說真的不是好事情。

而 C# 借鑑了其他非強制要求變數型別宣告的程式語言,集成了隱式型別本地變數(Implicitly typed local variables)語法。筆者認為隱式型別本地變數是 C# 中非常酷的特性,它讓開發者不用強迫自己記住需要引用或需要宣告的變數型別,從而將注意力聚焦在程式碼邏輯上。

C# 用 JavaScript 中的 var 關鍵字來宣告隱式型別本地變數,它能夠讓該變數通過隱式的型別推斷來 “自動” 宣告該變數型別。下面附上 C# 文件的官方例子來說明如何使用隱式型別本地變數。

csharp // i is compiled as an int var i = 5; ​ // s is compiled as a string var s = "Hello"; ​ // a is compiled as int[] var a = new[] { 0, 1, 2 }; ​ // expr is compiled as IEnumerable<Customer> // or perhaps IQueryable<Customer> var expr =    from c in customers    where c.City == "London"    select c; ​ // anon is compiled as an anonymous type var anon = new { Name = "Terry", Age = 34 }; ​ // list is compiled as List<int> var list = new List<int>();

當然,大量的使用 var 也可能會造成一些問題。例如,包含大量包含隱式型別本地變數的程式碼會讓不熟悉的該程式碼的 C# 開發者花不少時間來琢磨該變數的實際型別,從而影響程式碼的可讀性。不過,常用的 C# IDE 例如 Visual Studio 或 JetBrains Rider 都可以自動幫你顯示該隱式型別本地變數的實際型別。正是藉助這些強大的 IDE,筆者才放心推薦使用 C# 的 var 語法來宣告變數。

語言整合查詢 (LINQ)

除了上述兩個提高開發效率的語法外,C# 還有一個非常新穎的語法特性:語言整合查詢,簡稱為 LINQ。LINQ 的出現讓資料來源操作的表示式變得足夠簡單。筆者有理由相信 C# 的創造者一定是參考了 SQL 這樣的查詢語言來設計 LINQ 語法的。下面是一個使用 LINQ 的例子。

csharp class LINQQueryExpressions {    static void Main()   { ​        // Specify the data source.        int[] scores = new int[] { 97, 92, 81, 60 }; ​        // Define the query expression.        IEnumerable<int> scoreQuery =            from score in scores            where score > 80            select score; ​        // Execute the query.        foreach (int i in scoreQuery)       {            Console.Write(i + " ");       }   } } // Output: 97 92 81

我們著重看 scoreQuery 這個變數的表示式,其中的 from ... in ... where ... select ... 語法就是用了 LINQ。其表達的意思是遍歷 scores 這個陣列,只選取 score 大於 80 的元素,最後返回 score 行程新的迭代器 scoreQuery。這跟 SQL 的 SELECT ... FROM 語法非常相似。LINQ 對於 C# 來說只是一個表示式,目的是為了將常規的資料特別是陣列操作,變得跟寫 SQL 一樣簡單。如果不用 LINQ,你可能會需要用 foreach 遍歷陣列並做 if 判斷,然後加入更多操作來實現跟上述 LINQ 語句相同的邏輯。

在後面介紹 Entity Framework 資料庫操作框架的部分,我們還會繼續介紹 C# 中的資料操作。

屬性語法

C# 定義模型的方式相對於 Java 要簡單很多,同時還支援更高階的特性,例如 get=> 定義的計算屬性。

csharp public class Person {  // private properties  private int _gender;  private int _age;    // Auto-implemented property  public string Name { get; set; }    // Expression body definition  public string Gender => _gender == 1 ? "Male" : "Female";    // Property with backing fields  public string Age { get   {      return $"{_age} years old";   } } }

在上面的例子中,我們看到有多種定義屬性的方式。Name 是自動實現的屬性,只需要帶上 getset 表示是可讀寫的屬性;Gender 是用 => 表示簡化的 get 訪問器,用於較為簡單的計算屬性;Age 包含 get 訪問器,用於較為複雜的計算屬性。

可以看到,C# 的屬性語法同時包含了簡單和複雜的使用場景,因此更能夠提高開發效率。

總結

本篇文章主要從語法特性的角度介紹了 C# 獨有而方便的特性。特別是相對於傳統的後端程式語言 Java,C# 具有很多讓人喜愛的語法,例如空值操作、隱式型別推斷、LINQ 等。雖然 Java 在新的版本中加入了部分類似語法試圖提高開發效率,然而如今市面上的 Java 產品大多還是用經典的 Java 8 版本,因此沒有這些功能。而 C# 背靠微軟開發維護,有合理的 Roadmap 開發規劃,文件也較為齊全,因此對於很多後端開發者是非常友好的。C# 目前開發方向貫徹了它 “簡單、現代、通用” 的設計標準,綜合來看很適合做中大型專案。不過,目前國內因為歷史原因還暫時是 Java 佔據主要市場,而新興的 Go 也佔據了分散式應用領域的份額(參考本部落格之前文章 《大紅大紫的 Golang 真的是後端開發中的萬能藥嗎?》),所以 C# 在推廣方面可能還需要假以時日。不過,酒香不怕巷子深。包括筆者在內的越來越多開發者已經意識到 C# 是一門非常優秀的後端程式語言,如果條件允許,可以將其應用在實戰專案中。之後的系列文章將更進一步分析 C# 的生態,特別是 .NET Core、Entity Framework 等主流框架。