.NET 或将引入类型类和扩展

  • Jonathan Allen
  • 谢丽

2017 年 4 月 17 日

话题:.NETC#语言 & 开发

类型类是另外一项正被考虑引入.NET 未来版本的特性。在提案“外观和扩展(Shapes and Extensions)”中,该特性被称为外观,它们将大幅提升.NET 泛型的能力。Mads Torgersen 这样描述类型类:

接口抽象的是作为类型实例的对象和值的“外观(shape)”。从根本上讲,类型类背后的思想是抽象类型本身的外观。而且,当通过类型声明引入需要的类型实现一个接口时,其他人可以在单独的代码中实现类型类。

类型类解决了一个长期存在的接口问题:它们无法处理静态函数或操作符重载。这导致了一些问题,比如,在数学库中,对于不同的数值数据类型,需要反复声明相同的函数。

Mads 总结道:

一般来说,外观的声明和接口声明非常像,但它:

几乎可以定义任意类型的成员(包括静态成员)

可以通过扩展实现

可以在特定的地方像类型一样使用

最后一个限制很重要:外观不是类型。外观的主要目的是作为泛型的一种约束,限定类型参数,保证它们有正确的外观,并允许泛型声明体使用那个外观。

与外观的思想紧密相关的是一种经过改进的扩展语法。扩展结构几乎可以为类型类提供任何东西,而不只是方法扩展。考虑下面这个最简单的例子:

public shape SNumber<T>
{
    static T operator +(T t1, T t2);
    static T operator -(T t1, T t2);
    static T Zero { get; }
}

Int32 类型已经提供了大部分内容,但它缺少 zero 属性。扩展可以修复这个问题:

public extension IntGroup of int : SNumber<int>
{
    public static int Zero => 0;
}

然后,你可以像下面这样使用它:

public static AddAll<T>(T[] ts) where T : SNumber<T> // shape 用作约束 
{
    var result = T.Zero; // 使用 shape 的 Zero 属性 
    foreach (var t in ts) { result += t; } // 使用 shape 的 + 操作符 
    return result;
}

实现

这实现起来需要一些接口和结构方面的技巧。

  • Shapes 被翻译成了接口,每个成员(甚至是静态成员)都转换成了接口中的实例成员;
  • 扩展被翻译成了结构,每个成员(甚至是静态成员)转换成了结构中的实例成员;
  • 如果扩展实现了一个或多个弯管,则底层的结构实现了那些外观的底层接口。

通常,上述结构被称为“见证结构(witness struct)”。它的存在可以证明一个类遵循外观的规则。或者换句话说,该类在类型类中。

编译器会将上述 AddAll 方法翻译成如下代码:

public static T AddAll<T, Impl>(T[] ts) where Impl : struct, SNumber<T>
{
    var impl = new Impl();
    var result = impl.Zero;
    foreach (var t in ts) { result = impl.op_Addition(result, t); }
    return result;
}

然后,上述见证结构就可以用于向 AddAll 方法提供必要的功能。结构可以直接在类型上调用方法或者根据需要使用扩展结构。

在类和接口中实现外观

使用和我们扩展基类及实现接口一样的语法,类可以显式实现一个外观。然后,编译器会提供相应的见证结构。

也可以将接口标记为满足外观的要求。下面是一个例子:

public extension Comparable<T> of IComparable<T> : SComparable<T> ;

由于 IComparable 和理论上的类型类之间存在一对一关系,所以我们不需要为扩展结构提供扩展体。

泛型类型

事实证明,泛型类型有他们自己的问题。和泛型方法一样,向泛型类添加外观或者类型类作为类型约束需要额外提供一个类型参数。在泛型类上,由于类型参数的数量是其名称的一部分,所以这会导致它和其它名称相同的泛型类型发生冲突。

扩展外观

扩展结构不仅可以用于实现外观,还可以扩展它们。因此,你可以向现有的外观中添加新方法、静态方法及操作符。正如扩展方法一样,语法是一样的,就像它们在底层类型上直接定义了一样。

评论

总的来说,人们对于该特性的反应不错。不过,也有一些修改请求。例如,外观目前必须显式实现。有些开发人员希望,如果特定的类或接口不需要额外扩展方法时,就由编译器隐式实现。Mads 列举了这样做的一些问题:

那可能会导致,为了见证以相同的方式应用到同一类型的同一个外观而生成许多结构类型,有生成的类型过度扩散的风险。如果编译器比较聪明,每个程序集只生成一个,或许可以缓解这种情况,但我们从匿名类型了解到,这种重复数据删除技术非常困难,而且很容易出错。

如果我们允许泛型类型拥有外观约束的类型参数,那么同一个东西拥有多个见证结构会导致实例化的泛型类型具有不同的类型标识,无法互换。

人们还担心外观和扩展绑定得太紧。他们认为,那将来可能会引起混淆。

对此,Mads 答复说:

合并:在我的提案里,“扩展”实际上合并了多个问题:

[……]

我觉得,对于上述服务于所有这些目的的语言机制,有太多内容需要讨论——但归根结底,它们的关系非常密切。如果有一个提案可以将它们清晰地分开,那将是非常有意义的。那也许会更加简单有效。

查看英文原文:.NET Futures: Type Classes and Extensions

.NETC#语言 & 开发