前言

如今 C# 虽然发展到了 8.0 版本,引入了诸多的函数式特性,但其实在 C# 未来的规划当中,还有很多足以大规模影响现有 C# 代码结构和组成的特性,本文中将会对就重要的特性进行介绍,并用代码示例展示这些特性。

以下特性将会在 C# 9.0、10.0 或者更高版本提供。

Records

Records 是一种全新的简化的 C# classstruct 的形式。

现在当我们需要声明一个类型用来保存数据,并且支持数据的解构的话,需要像如下一样写出大量的样板代码:

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 之后,上面的样板代码只需简化成一句话:

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 所代表的第二个位置将成为 Y

还有一种声明方式是基于名称的:

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 的方向渐行渐远,期待这门语言变得越来越好。

从未来看 C#的更多相关文章

  1. 精神状态: Confused

    阿里和网易都已开放简历投递入口,本以为招聘季9月才开始的我,着实被震惊到了. 我还没准备好呢,远没有准备好. 这次日志,主要是想写三点.实习经历.接下来的计划.最后,自已在未来应该维持的心态. 关于实 ...

  2. AI人工智能天机芯芯片

    AI人工智能天机芯芯片 描述 2019年刊出的<自然>封面文章,展示了清华大学类脑计算研究中心团队研发的新型人工智能芯片"天机芯(Tianjic)".这是世界首款异构融 ...

  3. 对于不平凡的我来说,从小我就在想为啥别人就什么都能拥有,而看看自己却什么都没有,对于原来的我就会抱怨爸妈怎么没有别人父母都能给自己想要的,可我从未想过父母的文化只有小学,其实父母内心也有太多的辛酸,所以我不甘愿如此,从此让我在大学里面直接选择一个让我巨大的转折————IT。

    对于不平凡的我来说,从小我就在想为啥别人就什么都能拥有,而看看自己却什么都没有,对于原来的我就会抱怨爸妈怎么没有别人父母都能给自己想要的,可我从未想过父母的文化只有小学,其实父母内心也有太多的辛酸,所 ...

  4. PHP项目感悟 -- 从CI框架来看iOS的MVC

    其实这几天一直都想找时间把这个感悟整理出来,也是这一段一直思考的问题,因为这一段参加一个PHP后台项目的开发,框架使用的是CI,随着项目的进展,对于CI接触的也越多,但是由于理解的可能并不深刻,我也只 ...

  5. 以代码爱好者角度来看AMD与CMD

    随着浏览器功能越来越完善,前端已经不仅仅是切图做网站,前端在某些方面已经媲美桌面应用.越来越庞大的前端项目,越来越复杂的代码,前端开发者们对于模块化的需求空前强烈.后来node出现了,跟随node出现 ...

  6. 【web前端面试题整理03】来看一点CSS相关的吧

    前言 昨天我们整理了14到js的题,今天我们再来整理14到CSS相关的题目,昨天整理时候时间有点晚了我便有点心浮气躁,里面的一些题需要再次解答,好了看看今天有些什么吧. PS:我这里挑一点来做就好了, ...

  7. 接口测试从未如此简单 - Postman (Chrome插件)

    接口测试从未如此简单 - Postman (Chrome插件) 一个非常有力的Http Client工具用来测试Web服务的, 我这里来介绍如何用它测试restful web service 注:转载 ...

  8. 从汇编来看c语言之指针

    一.基础研究 将下面的程序编译连接,用debug加载: 首先执行第一条语句: 发现p=(unsigned char *)0x1000;在这里是把1000赋给一个偏移地址为01af.大小为两字节的内存空 ...

  9. 从汇编来看c语言之变量

    1.基础研究 对如图程序进行编译连接,再用debug加载. 我们在偏移地址1fa处查看main函数的内容: 执行到1fd处,发现n的偏移地址为01a6,段地址存储在ds寄存器里,为07c4. 再查看函 ...

随机推荐

  1. 14 微服务电商【黑马乐优商城】:day06-了解vue-router和webpack的使用

    本项目的笔记和资料的Download,请点击这一句话自行获取. day01-springboot(理论篇) :day01-springboot(实践篇) day02-springcloud(理论篇一) ...

  2. 怎么通过scanf读取一个空白前的字符

    /************************************************************************* > File Name: scanf2.c ...

  3. logstash 使用glusterfs网络存储偶发性文件解析异常的问题

    其实问题到现在为止也没有解决 因为服务是部署在k8s上,挂载的,偶发性的出现文件解析异常 bom头已经验证过了 手动重新解析这些文件完全正常,问题无法复现,文件本身并没有问题. 最后怀疑到了最不该怀疑 ...

  4. ac_查看每个用户登陆服务器所使用的时间

    ac 如果你想知道每个用户登录服务器所使用的时间,你可以使用 ac 命令.这个命令需要你安装acct 包(Debian)或 psacct 包(RHEL,Centos). 如果我们想知道所有用户登陆服务 ...

  5. 32)PHP,遍历对象的属性或者属性值

    首先是遍历属性: <?php class A{ ; ; ; function fetchAllProp(){ //遍历时,key取得属性名,value取得对应值 foreach($this as ...

  6. 吴裕雄--天生自然C语言开发:运算符

    #include <stdio.h> int main() { ; ; int c ; c = a + b; printf("Line 1 - c 的值是 %d\n", ...

  7. 进程异常行为-反弹Shell攻击,KILL多个进程

    进程异常行为-反弹Shell攻击 父进程名称:bash 进程名称:bash 进程名称:/usr/bin/bash 进程id:23,077 命令行参数:sh -c /bin/bash -i >&a ...

  8. 三角插值的 Fourier 系数推导

    给定 $k$ 个互不相同的复数 $x_0,\cdots,x_{k-1}$,以及 $k$ 个复数$y_0,\cdots,y_{k-1}$.我们知道存在唯一的复系数 $k-1$ 次多项式$$\mathca ...

  9. Create Collection Type with Class Type Constraints

    Create Collection Type with Class Type Constraints: new TypeToken<ArrayList<ClassType>>( ...

  10. [LC] 222. Count Complete Tree Nodes

    Given a complete binary tree, count the number of nodes. Note: Definition of a complete binary tree ...