这是滥用泛型类型系统吗?

时间:2011-10-07 18:07:00

标签: .net generics clr language-design

假设我们有一个编译成.Net的编译器,其中所有类型(参数和本地)的所有方法都是通用的,并且在编写方法时不会指定类型,而是指定约束。

编译时,方法可能如下所示:

interface IFoo<T> {
    T Foo();
}
interface IBar<TParam, TResult> {
    TResult Bar(TParam param);
}
public TResult FooBar<TItem, TParam, TIntermediate, TResult>(A item, B param)
    where TItem: IFoo<TIntermediate>
    where TIntermediate: IBar<TParam, TResult> {
        return item.Foo().Bar(param);
}

它将具有针对此的语法,并且将推断出类型,例如

FooBar = (item [I], param [P]) [I: IFoo<X>, X: IBar<P, R>] => [R] {
    item.Foo().Bar(param)
}

大多数方法都是这样编译的程序(除了可以从使用中推断出确切类型的程序)是使用 .Net的高级通用类型系统还是滥用它?

例如,这样的事情是否会减慢CLR,因为它有太多的jitted类型,或者它是否“正常工作”,与普通代码一样有效?

4 个答案:

答案 0 :(得分:2)

组合泛型类型约束的过程存在一个主要问题:没有通用的方法来对一个对象进行类型转换,以便可以将它传递给这样的例程 - 这种情况与没有这种类型参数的例程非常不同。

如果例程需要例如一个IFoo,我想传递一些我认为是IFoo的对象,我可以将对象转换为IFoo并将其传递给例程。如果对象可能是一个IFoo,编译器将生成代码,假定该对象是一个IFoo,如果没有则失败。

如果例程接受泛型类型参数,则传入的对象必须是编译时类型,它满足所有约束。如果只有一个约束并且它是特定的基本类型或接口(例如IFoo),那很容易 - 将其强制转换为约束隐含的类型。类型转换后的对象的编译类型将满足约束,如果对象的运行时类型满足约束,则转换将成功。

然而,如果想要调用其参数被约束为实现多个约束的例程Wowzo,则会出现困难,例如,接口IFoo和基类Wuzzle,但是想要传递给该例程的对象不共享满足约束的任何公共基类型。例如,假设类Wuzzle1,Wuzzle2和Wuzzle3都实现了IFoo,并且都继承自Wuzzle,但是Wuzzle没有实现IFoo。我有一系列的Wuzzle,其中的物品是Wuzzle1,Wuzzle2和Wuzzle3的混合物。如果一个数组元素碰巧是一个Wuzzle1,我可以将它转换为Wuzzle1并将其传递给Wowzo。如果它恰好是一个Wuzzle2,我可以把它投到一个Wuzzle2并传递给Wowzo。与Wuzzle3一样。不幸的是,没有很好的“通用”方法将数组元素传递给Wowzo,而不必明确处理列表中可能存在的每种可能的类型。

答案 1 :(得分:2)

简答:总是使用通用约束是在标准多态性面前不必要使用泛型,因此算作滥用我书中的特写。我无法谈论潜在的大量通用实例的实际性能影响,但如果没有任何好处,我不会冒险。

长答案:

对于提议的新语言,您可以自由地使用语法,但理想情况下,编译器应尝试使实际编译的方法尽可能不通用。这改善了导入库的C#/ VB / etc开发人员的体验,因为我不认为通用约束在Intellisense中表现良好。这也减少了运行时必须进行的通用实例化的数量。但是,制作该算法可能会非常困难。例如,我最初认为您的示例方法可以像这样重写:

TResult FooBar<TParam, TResult>(IFoo<IBar<TParam, TResult>> item, TParam param)
{
    return item.Foo().Bar(param);
}

这很接近,但由于IFoo上没有差异以及结构可能实现IBar的可能性,因此不太相同,在这种情况下,方差仍然无济于事。

另一方面,发布泛型方法的不好时间的一个例子是:

Write = (stream [S], data [D]) [S: Stream, D: byte()] => [] {
    stream.Write(data, 0, data.Length)
}

这里的直接翻译可能会产生:

public void Write<S, D>(S stream, D data) where S : Stream, D : byte[]
{ stream.Write(data, 0, data.Length); }

由于标准多态性,因此不需要任何通用参数,而是可以发出此参数:

public void Write(Stream stream, byte[] data)
{ stream.Write(data, 0, data.Length); }

答案 2 :(得分:1)

一个大问题是方法重载。 CLR不认为这些方法不同。

public void Method<T>(T arg){} where T:IFoo
public void Method<T>(T arg){} where T:IBaz

不可否认,您可以使用modreq / modopt参数修饰符以您的语言解决此问题。但是,这意味着信息从大多数其他.NET语言中丢失给调用者,或者根本无法被其他语言调用。

另一个痛点是虚拟方法的性能下降。虚拟通用调度比正常调度慢一个数量级。

最后,确实没有解决约束组合问题的好方法,你必须在jit时间之前发出一个类型来满足类型系统,如isinstcastclass这样的指令上班。因此,如果您想支持x as IFoo,IBar,则必须创建<GeneratedInterface>_IFoo_IBaz:IFoo,IBaz的界面。然后x的实际类型必须添加对生成的接口的支持,因为.NET实际上没有鸭子类型。

你也可能会陷入神奇的痛苦x as IFoo<T>,IBaz<T>但是T这是一个不可能的组合,(例如,第一种情况下的T是结构,第二种情况下的T是类(x实际上是{{1}创建这样一种语言可能非常简洁但很难。

答案 3 :(得分:0)

如果来自这种编程语言的结果IL是可验证的,那么它不会滥用类型系统,因为它旨在运行可验证的所有内容(以及一些无法验证的内容)。

我的问题是 - 你为什么要这样做?使用多态性处理的静态类型方法参数有什么问题?