為什麼推薦你用 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 等主流框架。