從將來看 C#

前言

現在 C# 雖然發展到了 8.0 版本,引入了諸多的函數式特性,但其實在 C# 將來的規劃當中,還有不少足以大規模影響現有 C# 代碼結構和組成的特性,本文中將會對就重要的特性進行介紹,並用代碼示例展現這些特性。express

如下特性將會在 C# 9.0、10.0 或者更高版本提供。數組

Records

Records 是一種全新的簡化的 C# classstruct 的形式。數據結構

如今當咱們須要聲明一個類型用來保存數據,而且支持數據的解構的話,須要像以下同樣寫出大量的樣板代碼:async

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) { ... }
}

十分複雜。引入 Records 以後,上面的樣板代碼只需簡化成一句話:ide

data class Point(double X, double Y);

而且 Records 支持數據的變換、解構和模式匹配:函數

var pointA = new Point(3, 5);
var pointB = pointA with { Y = 7 };
var pointC = new Point(3, 7);

// 當 Y = 5 時爲 X,不然爲 Y
var result = pointB switch
{
    (var first, 5) => first,
    (_, var second) => second
}; 

// true
Console.WriteLine(pointB == pointC);

固然,recordimmutable 的,而且是能夠合併(繼承)的,也能夠標記爲 sealed 或者 abstract佈局

sealed data class Point3D(double X, double Y, double Z) : Point(X, Y);

上面的這種 record 聲明方式是基於位置聲明的,即 Point(first, second)fisrt 所表明的第一個位置將成爲 Xsecond 所表明的第二個位置將成爲 Ythis

還有一種聲明方式是基於名稱的:spa

data class Point { double X; double Y };
var point = new Point { X = 5, Y = 6 };

Discriminated Unions

Discriminated unions 又叫作 enum class,這是一種全新的類型聲明方式,顧名思義,是類型的 「枚舉」。設計

例如,咱們須要定義形狀,形狀有矩形、三角形和圓形,之前咱們須要先編寫一個 Shape 類,而後再建立 RectangleTriangleCircle 類繼承 Shape 類,如今只須要幾行就能完成,而且支持模式匹配和解構:

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()
    };

利用此特性,咱們能夠垂手可得的實現支持模式匹配的、type sound 的可空數據結構:

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

當咱們想要表示一個對象是兩種類型其一時,將可使用聯合類型來表達:

public type SignedNumber = short | int | long | float | double | decimal;
public type ResultModel<T> = DataModel<T> | ErrorModel;

這在 Web API 中很是有用,當咱們的接口可能返回錯誤的時候,咱們再也不須要將咱們的數據用如下方式包含在一個統一的模式中:

public class ResultModel<T>
{
    public string Message { get; set; }
    public int Code { get; set; }
    public T Data { get; set; }
}

咱們將可以作到,不依賴異常等流程處理的方式作到錯誤時返回錯誤信息,請求正常處理時返回真實所需的數據:

public async ValueTask<DataModel | ErrorModel> SomeApi()
{
    if (...) return new DataModel(...);
    return new ErrorModel(...);
}

還有和類型,用來表示多個類型之和,咱們此前在設計接口時,若是須要一個類型實現了多個接口,則須要定義一個新接口去實現以前的接口:

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 是一種特殊的類型 nevernever 類型是任何類型的子類,所以不存在該類型的子類。一個 never 類型的什麼都不表示。

Union types 帶來一個問題,就是咱們有時候須要表達這個東西什麼都不是,那麼 never 將是一個很是合適的選擇:

type Foo = Bar | Baz | never;

另外,never 還有一個重要的用途:控制代碼流程,一個返回 never 的函數將結束調用者的邏輯,即這個函數不會返回:

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,這個特性作到能夠在不修改原有類型的基礎上,爲類型實現接口。

首先咱們定義一個 concept

concept Monoid<T>
{
    // 加函數
    T Append(this T x, T y);
    // 零屬性
    static T Zero { get; }
}

而後咱們能夠爲這個 concept 建立類型類的實例:

instance IntMonoid : Monoid<int>
{
    int Append(this int x, int y) => x + y;
    static int Zero => 0;
}

這樣咱們就爲 int 類型實現了 Monoid<int> 接口。

當咱們想實現一個函數用來將一個 int 數組中的全部元素求和時,只須要:

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 會根據 T 進行自動推導獲得 Monoid<int>

這樣咱們就能作到在不須要修改 int 的定義的狀況下爲其實現接口。

Higher Kinded Polymorphism

Higher kinded polymorphism,又叫作 templated template,或者 generics on generics,這是一種高階的多態。

舉個例子,好比當咱們須要表達一個類型是一個一階泛型類型,且是實現了 ICollection<> 的容器之一時,咱們能夠寫:

void Foo<T>() where T : <>, ICollection<>, new();

有了這個特性咱們能夠垂手可得的實現 monads

例如咱們想要作一個將 IEnumerable<> 中全部元素變成某種集合類型的時候,例如 ToList() 等,咱們就不須要顯式地實現每一種須要的類型的狀況(例如 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;
}

當咱們想要把一個 IEnumerable<int> x 轉換成 List<int> 時,咱們只需簡單的調用:x.To<List<>>() 便可。

Simple Programs

該特性容許編寫 C# 代碼時,無需 Main 函數,直接像寫腳本同樣直接在文件中編寫邏輯代碼,以此簡化編寫少許代碼時卻須要書寫大量樣板代碼的問題:

之前寫代碼:

namespace Foo
{
    class Bar
    {
        static async Task Main(string[] args)
        {
            await Task.Delay(1000);
            Console.WriteLine("Hello world!");
        }
    }
}

如今寫代碼:

await Task.Delay(1000);
Console.WriteLine("Hello world!");

Expression Blocks

該特性容許建立表達式塊:

Func<int, int, bool> greaterThan = (a, b) => if (a > b) a else b;

// true
greaterThan(5, 4);

所以有了以上特性,咱們能夠利用表達式實現更加複雜的東西。

後記

以上特性都是對代碼佈局和組成影響很是大的特性,而且很多特性幾年前就已經被官方實現,可是由於存在還沒有討論解決的問題,遲遲沒有發佈進產品。

除此以外,還有幾十個用於改進語言和方便用戶使用等等的小特性也在將來的規劃當中,此處不進行介紹。

將來的 C# 和今天的 C# 區別是很大的,做爲一門多範式語言,C# 正在朝遠離 Pure OOP 的方向漸行漸遠,期待這門語言變得愈來愈好。

相關文章
相關標籤/搜索