为什么C#不对generics做“简单”类推理?

只是好奇:当然,我们都知道generics类型推断的一般情况是不可判定的。 所以C#根本不做任何类型的子类型:如果Foo 是generics的, Foo 不是Foo 的子类型,或Foo 或其他任何你可能的类型做饭。 当然,我们都用丑陋的界面或抽象的类定义来解决这个问题。

但是……如果你不能解决一般问题,为什么不把解决方案限制在容易的情况下。 例如,在上面的列表中,显然Foo Foo 的子类型,并且检查它是微不足道的。 对于Foo 进行检查也是如此。

如果他们只是说,那么还有其他一些深深的恐怖会从深渊蔓延出来,我们会尽我们所能吗? 或者这只是微软语言人员的某种宗教纯洁?


更新:

这是一个非常古老的线程。 这些天,C#有var,它解决了我抱怨的一半,然后使用Linq风格的匿名代表,有一个很好的表示法,不需要输入相同的东西两次。 因此,我反对的所有方面都已经通过最近对C#的更改得到了解决(或者我只是花了一些时间来了解我刚发布post时刚刚介绍的内容……)我使用这些新内容现在在Isis2系统中可靠的云计算function(isis2.codeplex.com),我认为该库具有非常干净的外观和感觉。 看看它,让我知道你的想法)。 – Ken Birman(2014年7月)

他们已经为许多“简单”案例解决了这个问题:C#4.0支持接口和委托中generics类型参数的协方差和逆变 。 但不幸的是没有课程。

解决此限制很容易:

 List foos = bars.Select(bar => (Foo)bar).ToList(); 

很可惜FooFoo的子类型

也许对你而言,但对我而言。

对我来说,这种冲入类型系统的巨大漏洞是不可接受的。 如果你想将类型安全性抛出窗口,我宁愿使用实际上为这些东西设计的动态类型语言。

数组是协变的这一事实,即使已知这会破坏类型安全性,这已经足够糟糕了,现在你想打破它的一切吗?

这是类型系统的核心所在。 所有类型系统都做的是拒绝程序 。 由于赖斯的定理,那些被拒绝的程序包括完美的类型安全的程序。

这是一个巨大的成本。 消除表现力,阻止我编写有用的程序。 为了certificate这个成本是合理的,类型系统更好的回报是回报。 它基本上有两种方法:在程序级别和类型安全性的类型级别上回馈表达性。

前者已经出局了,仅仅是因为C#的类型系统不够强大,不能让我表达任何有趣的东西。 这只留下了后者,由于null ,协变arrays,不受限制的副作用, unsafe等等,它已经处于相当不稳定的基础上。 通过使泛​​型类型自动协变,您或多或少地完全消除了剩下的最后一种类型安全性。

只有极少数情况下S <: T ⇒ G <: G实际上是类型安全的。 ( IEnumerable就是这样一个例子。)并且可能同样多的情况只有S <: T ⇒ G <: G是类型安全的。 ( IObservableIComparerIComparableIEqualityComparer 。)通常, G <: GG <: G都不是类型安全的。

关键是你不能在所有情况下都这样做,所以你不要为任何情况做这件事。 你在哪里划线就是问题所在。 如果你不这样做,那么使用C#的每个人都知道它不会这样做。 如果你在部分时间做,那就是它变得复杂。 它可以成为一个关于代码行为方式的猜谜游戏。 对于程序员而言,简单易事和不会变得复杂的所有边缘情况都会导致代码错误。

这是一个绝对会造成严重破坏的场景。 假设您可以在方案A中推断boo是bar。其他人来了并且更改了基本类型的一部分,这不再适用。 通过使其始终适用或永不适用,您不会遇到这种情况。 在复杂的环境中,追踪这样的问题可能是绝对的噩梦,尤其是当您考虑到这一点时,可能无法在编译时捕获(reflection,DLR等)。 编写代码以便手动处理前面的转换要容易得多,而不是假设它可以在您的场景中工作时,有时可能不会(不计算升级到新版本)。

C#4.0确实解决了一些问题,因为他们允许推断他们认为程序员“安全”的东西。

一个非常好的现实生活中,一种混淆并且比特殊情况所需要的更复杂的语言的例子是英语。 在编程语言中,诸如“除了c之后的e之前”这样的东西使语言更难使用,因此不太有用。

由于generics类的实例具有不同的方法签名,我不相信它会使任何意图考虑从基类inheritance的特定类。

 class Base { public T Method() { return default(T);} } 

如果你建议Base和Base比它们两个都有“Method”具有相同的签名(来自基类),并且可以通过使用对基类的引用来调用它。 如果通过指向基类指向BaseBase对象,你能解释一下你的系统中有什么明显的返回值“Method”吗?

所以,我找到了一个优雅的解决方案来解决我的真正问题(虽然我发现C#过度约束)。

正如您所收集的那样,我的主要目标是编写可以将回调注册到您稍后将要编写的方法(例如,2011年)并且使用当前不存在的generics类型参数在外部定义的代码,因此,在我写我的图书馆(今天)时,我无法使用。

例如,我需要创建明年定义的对象列表,并迭代列表的元素,回调每个对象的“Aggregate ”方法。 我很沮丧的是,虽然你可以注册你的课程,或者你class上的一个对象,但是C#类型检查不让我调用这些方法,因为我不知道类型(我可以通过GetType()获取类型但是可以不要将它们用作表达式中的类型。 所以我开始使用reflection手工编译所需的行为,这很丑陋并且可能违反类型安全性。

答案是……匿名课程。 由于匿名类就像一个闭包,我可以在“register”例程中定义我的回调(它可以访问类型:你可以在注册对象时传递它们)。 我将创建一个小的匿名回调方法,右键内联,并保存委托给它。 回调方法可以调用聚合器,因为C#认为它“知道”参数类型。 然而,我的列表可以在编译我的库时列出具有已知类型签名的方法,即明天。

非常干净,C#并没有最终让我疯狂。 尽管如此,C#确实没有努力推断子类型关系(对不起Eric:“什么应该是子类型关系”)。 generics不是C中的宏。但是C#太接近了它们就好像它们一样对待它们!

这回答了我自己的(真实)问题。