前言
如今 C# 雖然發(fā)展到了 8.0 版本,引入了諸多的函數(shù)式特性,但其實(shí)在 C# 未來的規(guī)劃當(dāng)中,還有很多足以大規(guī)模影響現(xiàn)有 C#
代碼結(jié)構(gòu)和組成的特性,本文中將會對就重要的特性進(jìn)行介紹,并用代碼示例展示這些特性。
以下特性將會在 C# 9.0、10.0 或者更高版本提供。
Records
Records 是一種全新的簡化的 C# class 和 struct 的形式。
現(xiàn)在當(dāng)我們需要聲明一個類型用來保存數(shù)據(jù),并且支持?jǐn)?shù)據(jù)的解構(gòu)的話,需要像如下一樣寫出大量的樣板代碼:
class Point : IEquatable<Point> { public readonly double X; public readonly
double Y; public Point(double X, double Y) { this.X = X; this.Y = Y; } public
static bool operator==(Point left, Point right) { ... } public bool
Equals(Point other) { ... } public override bool Equals(object other) { ... }
public override int GetHashCode() { ... } public void Deconstruct(out double x,
out double y) { ... } }
十分復(fù)雜。引入 Records 之后,上面的樣板代碼只需簡化成一句話:
data class Point(double X, double Y);
并且 Records 支持?jǐn)?shù)據(jù)的變換、解構(gòu)和模式匹配:
var pointA = new Point(3, 5); var pointB = pointA with { Y = 7 }; var pointC =
new Point(3, 7); // 當(dāng) Y = 5 時為 X,否則為 Y var result = pointB switch { (var first,
5) => first, (_, var second) => second }; // true Console.WriteLine(pointB ==
pointC);
當(dāng)然,record 是 immutable 的,并且是可以合并(繼承)的,也可以標(biāo)記為 sealed 或者 abstract:
sealed data class Point3D(double X, double Y, double Z) : Point(X, Y);
上面的這種 record 聲明方式是基于位置聲明的,即 Point(first, second),fisrt 所代表的第一個位置將成為 X,second
所代表的第二個位置將成為Y。
還有一種聲明方式是基于名稱的:
data class Point { double X; double Y }; var point = new Point { X = 5, Y = 6
};
Discriminated Unions
Discriminated unions 又叫做 enum class,這是一種全新的類型聲明方式,顧名思義,是類型的 “枚舉”。
例如,我們需要定義形狀,形狀有矩形、三角形和圓形,以前我們需要先編寫一個 Shape 類,然后再創(chuàng)建 Rectangle、Triangle 和 Circle
類繼承Shape 類,現(xiàn)在只需要幾行就能完成,并且支持模式匹配和解構(gòu):
enum class Shape { Retangle(double Width, double Height); Triangle(double
Bottom, double Height); Circle(double Radius); Nothing; }
然后我們就可以使用啦:
var circle = new Circle(5); var rec = new Rectangle(3, 4); if (rec is
Retangle(_, 4)) { Console.WriteLine("這不是我想要的矩形"); } var height =
GetHeight(rec); double GetHeight(Shape shape) => shape switch { Retangle(_,
height) => height, Triangle(_, height) => height, _ => throw new
NotSupportedException() };
利用此特性,我們可以輕而易舉的實(shí)現(xiàn)支持模式匹配的、type sound 的可空數(shù)據(jù)結(jié)構(gòu):
enum class Option<T> { Some(T value); None; } var x = Some(5); //
Option<never> var y = None; void Foo(Option<T> value) { var bar = value switch
{ Some(var x) => x, None => throw new NullReferenceException() }; }
Union and Intersection Types
當(dāng)我們想要表示一個對象是兩種類型其一時,將可以使用聯(lián)合類型來表達(dá):
public type SignedNumber = short | int | long | float | double | decimal;
public type ResultModel<T> = DataModel<T> | ErrorModel;
這在 Web API 中非常有用,當(dāng)我們的接口可能返回錯誤的時候,我們不再需要將我們的數(shù)據(jù)用以下方式包含在一個統(tǒng)一的模式中:
public class ResultModel<T> { public string Message { get; set; } public int
Code { get; set; } public T Data { get; set; } }
我們將能夠做到,不依賴異常等流程處理的方式做到錯誤時返回錯誤信息,請求正常處理時返回真實(shí)所需的數(shù)據(jù):
public async ValueTask<DataModel | ErrorModel> SomeApi() { if (...) return new
DataModel(...); return new ErrorModel(...); }
還有和類型,用來表示多個類型之和,我們此前在設(shè)計(jì)接口時,如果需要一個類型實(shí)現(xiàn)了多個接口,則需要定義一個新接口去實(shí)現(xiàn)之前的接口:
interface IA { ... } interface IB { ... } interface IAB : IA, IB { } void
Foo(IAB obj) { ... }
有了和類型之后,樣板代碼 IAB 將不再需要:
void Foo(IA & IB obj) { ... }
或者我們也可以這樣聲明新的類型:
type IAB = IA & IB;
Bottom Type
Bottom type 是一種特殊的類型 never,never 類型是任何類型的子類,因此不存在該類型的子類。一個 never 類型的什么都不表示。
Union types 帶來一個問題,就是我們有時候需要表達(dá)這個東西什么都不是,那么 never 將是一個非常合適的選擇:
type Foo = Bar | Baz | never;
另外,never 還有一個重要的用途:控制代碼流程,一個返回 never 的函數(shù)將結(jié)束調(diào)用者的邏輯,即這個函數(shù)不會返回:
void | never Foo(int x) { if (x > 5) return; return never; } void Main() {
Foo(6); Console.WriteLine(1); Foo(4); Console.WriteLine(2); }
上述代碼將只會輸出 1。
Concepts
Concepts 又叫做 type classes、traits,這個特性做到可以在不修改原有類型的基礎(chǔ)上,為類型實(shí)現(xiàn)接口。
首先我們定義一個 concept:
concept Monoid<T> { // 加函數(shù) T Append(this T x, T y); // 零屬性 static T Zero {
get; } }
然后我們可以為這個 concept 創(chuàng)建類型類的實(shí)例:
instance IntMonoid : Monoid<int> { int Append(this int x, int y) => x + y;
static int Zero => 0; }
這樣我們就為 int 類型實(shí)現(xiàn)了 Monoid<int> 接口。
當(dāng)我們想實(shí)現(xiàn)一個函數(shù)用來將一個 int 數(shù)組中的所有元素求和時,只需要:
public T Sum<T, inferred M>(T[] array) where M : Monoid<T> { T acc = M.Zero;
foreach (var i in array) acc = acc.Append(i); return acc; }
注意到,類型 M 會根據(jù) T 進(jìn)行自動推導(dǎo)得到 Monoid<int>。
這樣我們就能做到在不需要修改 int 的定義的情況下為其實(shí)現(xiàn)接口。
Higher Kinded Polymorphism
Higher kinded polymorphism,又叫做 templated template,或者 generics on
generics,這是一種高階的多態(tài)。
舉個例子,比如當(dāng)我們需要表達(dá)一個類型是一個一階泛型類型,且是實(shí)現(xiàn)了 ICollection<> 的容器之一時,我們可以寫:
void Foo<T>() where T : <>, ICollection<>, new();
有了這個特性我們可以輕而易舉的實(shí)現(xiàn) monads。
例如我們想要做一個將 IEnumerable<> 中所有元素變成某種集合類型的時候,例如 ToList()
等,我們就不需要顯式地實(shí)現(xiàn)每一種需要的類型的情況(例如List<>):List<T> ToList(this IEnumerable<T> src)了。
我們只需要這么寫:
T<X> To<T, X>(this IEnumerable<X> xs) where T : <>, ICollection<>, new() { var
result = new T<X>(); foreach (var x in xs) result.Add(x); return result; }
當(dāng)我們想要把一個 IEnumerable<int> x 轉(zhuǎn)換成 List<int> 時,我們只需簡單的調(diào)用:x.To<List<>>() 即可。
Simple Programs
該特性允許編寫 C# 代碼時,無需 Main 函數(shù),直接像寫腳本一樣直接在文件中編寫邏輯代碼,以此簡化編寫少量代碼時卻需要書寫大量樣板代碼的問題:
以前寫代碼:
namespace Foo { class Bar { static async Task Main(string[] args) { await
Task.Delay(1000); Console.WriteLine("Hello world!"); } } }
現(xiàn)在寫代碼:
await Task.Delay(1000); Console.WriteLine("Hello world!");
Expression Blocks
該特性允許創(chuàng)建表達(dá)式塊:
Func<int, int, bool> greaterThan = (a, b) => if (a > b) a else b; // true
greaterThan(5, 4);
因此有了以上特性,我們可以利用表達(dá)式實(shí)現(xiàn)更加復(fù)雜的東西。
后記
以上特性都是對代碼布局和組成影響非常大的特性,并且不少特性幾年前就已經(jīng)被官方實(shí)現(xiàn),但是因?yàn)榇嬖谏形从懻摻鉀Q的問題,遲遲沒有發(fā)布進(jìn)產(chǎn)品。
除此之外,還有幾十個用于改進(jìn)語言和方便用戶使用等等的小特性也在未來的規(guī)劃當(dāng)中,此處不進(jìn)行介紹。
未來的 C# 和今天的 C# 區(qū)別是很大的,作為一門多范式語言,C# 正在朝遠(yuǎn)離 Pure OOP 的方向漸行漸遠(yuǎn),期待這門語言變得越來越好。
熱門工具 換一換