为什么 C# 不做“简单”的事情?泛型的类型推断?
只是好奇:当然,我们都知道泛型类型推断的一般情况是不可判定的。因此,C# 根本不会执行任何类型的子类型:如果 Foo
但是......如果您无法解决一般问题,为什么不将解决方案限制在简单的情况下呢?例如,在我上面的列表中,很明显 Foo
那么,如果他们只是说,哎呀,我们会尽力而为的话,是否还会有其他深层的恐惧从深渊中爬出来呢?或者这只是微软语言人员的某种宗教纯洁性?
更新:
这是一个非常古老的线程。如今,C# 有了 var,它解决了我所抱怨的一半问题,然后使用匿名委托的 Linq 风格,有一个很好的表示法,不需要输入两次相同的内容。因此,我所反对的各个方面都已通过最近对 C# 的更改得到了解决(或者也许我只是花了一段时间来了解我发布该线程时刚刚引入的内容......)我使用这些新的现在 Isis2 系统中提供了可靠的云计算 (isis2.codeplex.com) 功能,我认为该库因此具有非常干净的外观和感觉。看看并让我知道你的想法)。 ——肯·伯曼(2014 年 7 月)
Just curious: sure, we all know that the general case of type inference for generics is undecidable. And so C# won't do any kind of sub-typing at all: if Foo<T> is generic, Foo<int> isn't a subtype of Foo<T>, or Foo<Object> or of anything else you might cook up. And sure, we all hack around this with ugly interface or abstract class definitions.
But... if you can't beat the general problem, why not just limit the solution to cases that are easy. For example, in my list above, it is OBVIOUS that Foo<int> is a subtype of Foo<T> and it would be trivial to check. Same for checking against Foo<Object>.
So is there some other deep horror that would creep forth from the abyss if they were to just say, aw shucks, we'll do what we can? Or is this just some sort of religious purity on the part of the language guys at Microsoft?
Update:
This is a very old thread. These days, C# has var, which solves half of what I complained about, and then using the Linq style of anonymous delegates, has a great notation for not needing to type in the same stuff twice. So every aspect of what I was objecting to has been resolved by more recent changes to C# (or perhaps it simply took me a while to learn about things that were just being introduced around when I posted the thread...) I use these new features now in the Isis2 system for reliable cloud computing (isis2.codeplex.com) and I think the library has a very clean look and feel as a result. Check it out and let me know what you think). -- Ken Birman (July 2014)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
他们已经解决了许多“简单”情况:C# 4.0 支持 接口和委托中泛型类型参数的协变和逆变。但不幸的是没有上课。
解决这个限制相当容易:
They already have solved it for many of the "easy" cases: C# 4.0 supports covariance and contravariance for generic type parameters in interfaces and delegates. But not classes unfortunately.
It's fairly easy to workaround this limitation:
对你来说可能如此,但对我来说不是。
对我来说,这给类型系统带来的巨大漏洞是无法接受的。如果您想像这样将类型安全抛到九霄云外,我宁愿使用实际上是为这些东西而设计的动态类型语言。
事实上,数组是协变的,尽管众所周知这会破坏类型安全,但这已经够糟糕的了,现在你想破坏它的一切吗?
这涉及到类型系统的核心内容。类型系统所做的就是拒绝程序。由于赖斯定理,那些被拒绝的程序包括完全类型良好、类型安全的程序。
这是一个巨大的成本。剥夺了表达能力,阻止我编写有用的程序。为了证明这一成本的合理性,类型系统最好能大量获得回报。它基本上有两种方法来做到这一点:在类型级别恢复它在程序级别和类型安全方面所夺走的表达能力。
前者已被淘汰,只是因为 C# 的类型系统不够强大,无法让我表达任何有趣的东西。只剩下后者了,而且由于
null
、协变数组、不受限制的副作用、不安全
等等,它已经处于相当不稳定的境地。通过使泛型类型自动协变,您或多或少完全消除了剩下的最后一点类型安全性。只有极少数情况下
S <: T ⇒ G
成立。 <: G实际上是类型安全的。 (IEnumerable
就是一个这样的例子。)并且可能有同样多的情况,其中只有S <: T ⇒ G。 <: G
是类型安全的。 (IObservable
、IComparer
、IComparable
、IEqualityComparer
。)一般来说,都不是G
<: G也不是G<: G
是类型安全的。To you maybe, but not to me.
To me, the huge hole this rips into the type system is simply not acceptable. If you want to throw type-safety out the window like that, I'd much rather use a dynamically typed language that was actually designed for this stuff.
The fact that arrays are covariant, even though this is known to break type-safety, is bad enough, now you want to break it for everything?
This goes to the very heart of what a type system is about. All a type system does is reject programs. And because of Rice's Theorem, those rejected program include perfectly well-typed, type-safe programs.
That is a huge cost. Taking away expressivity, preventing me from writing useful programs. In order to justify that cost, the type system better pay be back big time. It has basically two ways of doing that: giving back expressivity at the type-level it took away at the program-level and type-safety.
The former is out, simply because C#'s type system isn't powerful enough to let me express anything even remotely interesting. This leaves only the latter, and it is already on pretty shaky ground because of
null
, covariant arrays, unrestricted side-effects,unsafe
and so on. By making generic types automatically covariant, you more or less completely take away the last shred of type-safety that is left.There are only very few cases where
S <: T ⇒ G<S> <: G<T>
is actually type-safe. (IEnumerable
is one such example.) And there are probably equally many cases where onlyS <: T ⇒ G<T> <: G<S>
is type-safe. (IObservable
,IComparer
,IComparable
,IEqualityComparer
.) Generally, neitherG<S> <: G<T>
norG<T> <: G<S>
are type-safe.关键是你不可能对所有情况都这样做,所以你不要对任何情况都这样做。问题是你的界限在哪里。如果您不这样做,那么每个使用 C# 的人都知道它不会这样做。如果你只是偶尔这样做,事情就会变得复杂。它可能会成为一个猜测代码将如何运行的游戏。对于程序员来说,所有简单和不简单的边缘情况都会变得复杂,并可能导致代码错误。
这是一个绝对会造成严重破坏的场景。假设您可以推断场景 A 中的 boo 是 bar。其他人来更改了部分基本类型,这不再成立。通过使其始终适用或从不适用,您永远不会遇到这种情况。在复杂的环境中,追踪这样的问题绝对是一场噩梦,尤其是当您考虑到在编译期间可能无法捕获的问题(反射、DLR 等)时。预先编写代码来手动处理转换比假设它会在您的场景中工作要容易得多,而实际上有时它可能不会(不包括升级到新版本)。
C# 4.0 确实解决了其中的一些问题,因为它们允许推断他们认为对程序员来说“安全”的内容。
The point is that you can't do it for all cases, so you don't do it for any. Where do you draw the line is the problem. If you don't do it for any than everyone who uses C# knows that it doesn't do that. If you do it part of the time, that is when it gets complicated. It can become a guessing game as to how your code will behave. It's all the edge cases on what is easy and what is not that become complex to the programmers and can cause errors in code.
Here is a scenario that would absolutely cause havoc. Let's say that you can infer boo is bar in scenario A. Someone else comes and changes part of the base type and this no longer holds true. By making it either always apply, or never apply, you don't run into this situation, ever. In a complex environment, tracking down a problem like this can be an absolute nightmare, especially when you factor in this may not be catchable during the compile time (reflection, the DLR etc.). It's so much easier to write code to manually handle the conversion up front, than assume that it will work in your scenario when the possibility that sometime down the line it just won't (not counting upgrading to a new version).
C# 4.0 does fix some of this as they allowed inference on what they felt is "safe" for the programmers.
现实生活中一个很好的例子就是英语,这种语言因特殊情况而变得混乱且变得比必要的复杂。在编程语言中,诸如“i 在 e 之前,除了 c 之后”之类的内容使该语言更难使用,因此用处也较小。
A really good real life example of a language that is muddied and made more complex than necessary by special cases is English. In a programming language things like "i before e except after c" make the language harder to use and as such, less useful.
由于泛型类的实例具有不同的方法签名,我认为考虑从基类继承特定类没有任何意义。
如果您建议 Base 和 Base 比它们都具有具有相同签名(来自基类)的“方法”,并且可以通过使用对基类的引用来调用它。
您能否解释一下,如果通过指向基类来指向
Base
或Base
对象,“Method”在您的系统中会有什么明显的返回值?Since instances of generic classes have different method signatures I don't belive it would make any sence to consider specific classes to be inherited from base classes.
If you suggest that Base and Base than both of them have "Method" with the same signature (coming from base class) and it can be called by using reference to base class.
Can you please explain what obvious return value "Method" will have in your system if one points to
Base<int>
orBase<string>
object by pointing to base class?因此,我为我的实际问题找到了一个优雅的解决方法(尽管我确实发现 C# 过于受限)。
正如您所了解到的,我的主要目标是编写可以将回调注册到您稍后(例如 2011 年)编写的方法的代码,并且该代码是使用今天不存在的泛型类型参数在外部定义的,因此在我编写库时(今天)我无法使用它们。
例如,我需要创建一个明年定义的对象列表,并迭代列表的元素,回调每个对象的“Aggregate”方法。我感到沮丧的是,虽然您当然可以注册您的类或类中的对象,但 C# 类型检查不允许我调用方法,因为我不知道类型(我可以通过 GetType() 获取类型,但可以不要将它们用作表达式中的类型)。因此,我开始使用反射来手动编译所需的行为,这是丑陋的并且可能违反类型安全。
答案是......匿名类。由于匿名类就像一个闭包,因此我可以在“register”例程中定义回调(它可以访问类型:您可以在注册对象时将它们传入)。我将创建一个小的匿名回调方法,右内联,并保存一个委托给它。回调方法可以调用您的聚合器,因为 C# 认为它“知道”参数类型。然而,我的列表可以在我编译我的库时(即明天)列出具有已知类型签名的方法。
非常干净,C# 不会让我发疯。尽管如此,C# 确实没有足够努力地推断子类型关系(对不起 Eric:“什么应该是子类型关系”)。泛型在 C 中不是宏。但是 C# 太接近于将它们视为宏了!
这回答了我自己的(真正的)问题。
So, I found an elegant work-around for my real problem (although I do find that C# is overly constraining).
As you've gathered, my main goal is to write code that can register a callback to a method that you'll write later (in 2011, for example) and that was defined externally with generic type parameters that don't exist today, hence aren't available to me at the time I wrote my library (today).
For example I need to make a list of objects you'll define next year, and iterate over the elements of the list, calling back to each object's "Aggregate<KeyType, ValueType>" method. My frustration was that while you can certainly register your class, or an object in your class, C# type checking wasn't letting me call the methods because I don't know the types (I can get the types via GetType() but can't use those as types in expressions). So I was starting to use reflection to hand-compile the desired behavior, which is ugly and can violate type safety.
And the answer is... anonymous classes. Since an anonymous class is like a closure, I can define my callbacks inside the "register" routine (which has access to the types: you can pass them in when you register your objects). I'll create a little anonymous callback method, right inline, and save a delegate to it. The callback method can call your aggregator since C# considers it to "know" the parameter types. And yet my list can list methods with known type signatures at the time I compile my library, namely tomorrow.
Very clean and C# doesn't end up driving me insane. Still, C# really doesn't try hard enough to infer subtype relationships (sorry Eric: "what should be subtype relationship"). Generics aren't macros in C. Yet C# is much too close to treating them as if they were!
And that answers my own (real) question.