為什麼推薦你用 C# 構建大型後端應用?- Part 1
前言
今天下英雄,惟使君與操耳。--曹操《三國演義》
對於在 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 來說更受開發者歡迎是有一定道理的,因為它的開發體驗很好。
限於篇幅原因,整個 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
是自動實現的屬性,只需要帶上 get
和 set
表示是可讀寫的屬性;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 等主流框架。