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

时间:2010-12-21 20:29:18

标签: c# generics types covariance inference

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

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

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


更新

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

6 个答案:

答案 0 :(得分:7)

他们已经解决了许多“简单”案例:C#4.0支持covariance and contravariance接口和委托中的泛型类型参数。但不幸的是,不是课程。

解决此限制相当容易:

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

答案 1 :(得分:5)

  

显然Foo<int>Foo<T>

的子类型

对你而言,但对我而言。

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

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

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

这是巨大的成本。带走表现力,阻止我编写有用的程序。为了证明这个成本是合理的,类型系统更好地支付大时间。它基本上有两种方法:在程序级别和类型安全性的类型级别上回馈表达性。

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

只有极少数的情况S <: T ⇒ G<S> <: G<T>实际上是类型安全的。 (IEnumerable就是这样一个例子。)并且可能只有S <: T ⇒ G<T> <: G<S>只有IObservable类型安全的情况。 (IComparerIComparableIEqualityComparerG<S> <: G<T>。)通常, G<T> <: G<S>和{{1}}都不是类型 - 安全

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

由于泛型类的实例具有不同的方法签名,我不相信它会使任何意义都考虑从基类继承特定的类。

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

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

答案 5 :(得分:0)

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

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

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

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

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

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