java程序员,看到C#程序员要膜拜了。惊!C# 9.0 支持脚本编程

不需要任何样板代码是脚本语言的一个显著特征,你可以直接在文件的第一行编写声明和语句,就像在函数内部一样。相反,诸如 VB,C#或者 Java 之类的非脚本语言,在类文件中就必须包含类似“main”方法的样板代码。

微软的 C#开发经理 Mads Torgersen 在 3117 提案中建议 C# 9 支持顶层语句和函数的功能。该提案允许在文件中直接编写语句和函数,而无需使用类(Class)进行包装。Torgersen 声称该提案的初衷是:

C#编译器目前支持一种可用于各种脚本开发和交互目的的语言方言,使用该方言,可以直接在顶层编写语句和非虚成员,而不需要使用成员体或类型进行包装。

虽然脚本方言很少被使用,且在某些方面上,它还没有跟上“主流”的 C #语言,但是通过 Try.NET 和其他技术的使用场景却在快速增加,因此我担心 C#未来会出现两种不兼容的脚本方言。

虽然当前版本的 C#脚本还没有被广泛使用,但 Torgersen 预测未来这个局面会被打破:

除了 Try.NET 之外,C#脚本在数据科学和机器学习的使用场景也在增加,而且使用者可以直接从实时数据交互这种模式中受益。

那么为什么不将交互 / C#脚本分开呢?因为我认为使代码能够在“实验”和“软件研发”之间来回切换是非常有价值的。

Torgersen 认为以下三种方案, 都可以实现顶层语句 / 函数的功能:

方案 1

如果采纳该方案,那么执行语言将被允许出现在命名空间声明之前。这些执行语句将被编译到一个主函数内,然后该主函数会被放到一个程序(Program)类中,该主函数可支持异步操作。

如果多个文件都在命名空间的外部声明了执行语句,那么编译器会报错,除非你希望拥有多个包含主函数的程序类。

<code>译者注: 方案

1

中,顶层语句最终会被编译成如下代码:

static

class

Program

{

static

async

Task

Main

(

string

[] args) { } }/<code>

方案 2

方案 2 是实现顶层函数,该方案允许在命名空间内或者全局定义函数,尽管公开函数也是允许的,但这些函数将被默认当作内部函数。从调用者角度来看,这些函数将直接属于该命名空间(这也是 VB 模块中函数的工作原理)。

方案 2 可能的实现思路是,生成一个局部类,将这些成员包装成静态成员。这个局部类的名称不是特定某个名字,可能是在确保不同程序集的相同命名空间中,通过某种方式生成的不重复的名称。只要顶层成员中有一个是公共的,那么这个局部类就是公共的,通过这种方式,可以让程序集知道那些成员是可以直接对外暴露的。

方案 3

虽然现有的 C#脚本方言和 C#本身是 2 种不同的语言,但方案 3 目的不是消除脚本方言,而是为了让这 2 种语言结合的更加紧密。Torgersen 说到:

如果在 C#中添加对顶层语句和函数的支持,那么我们不希望顶层语句和函数的执行和其在脚本中执行有冲突。相反,在保持语义功能一致的前提下,我们希望在必要的时候,以某种方式对它们进行必要的编译。这并不会完全消除脚本语言,因为我们仍然需要处理它们所依赖的特殊指令和“魔法命令”。但至少我们可以避免相同的语法表达不同的逻辑。

目前,Mads 建议 C#只关注方案 1,他说到:

你可以大胆的想象下,要实现一个满足所有方案功能,将会是怎样。那将需要进行大量的设计, 考虑大量的细节,因此我不建议这样做,相反,我认为我们应该关注在方案 1 的实现上。因为本质上,方案 1 其实已经基本包含了其他方案。

同时他提到,在使用方案 1 实现任何功能的时候,将来都不会给实现方案 2 和方案 3 带来困扰。

设计细则

顶层语句的第一条规则是,项目中只允许一个文件存在顶层语句。就像只能有一个“Main”函数一样,如果在一个文件中包含多个 naked 语句,那么编译器会报错。

语句的内容决定了最终编译产生的代码形式。无论语句是否使用了 await 关键字或者是否有 return 表达式(例如:return 5),编译输出的代码形式只会是以下四种:

<code>

static

void

Main

(

string

[] args)

static

int

Main

(

string

[] args)

static

Task

Main

(

string

[] args)

static

Task<

int

>

Main

(

string

[] args)/<code>

支持相同的语法,就像普通方法中使用本地函数一样。