10 ms·
> zero warts C# is a nice language, but oh man, is that asking for it! My pet example of a wart is the set of requirements on the iterated collection in forea
by millstone 13y ago
> zero warts
C# is a nice language, but oh man, is that asking for it!
My pet example of a wart is the set of requirements on the iterated collection in foreach loops. Most C# developers probably think that the collection has to implement IEnumerable, but it is not so! I pasted the requirements from the spec at http://pastebin.com/EfsAmz6f http://pastebin.com/EfsAmz6f
This is also a case where the dynamic type does something different than the static type. If you iterate over a statically typed non-IEnumerable collection, it will work fine (assuming it implements the required methods). But if you then change the type to dynamic, I think you get a runtime error.
So there's a wart for ya!
- danbruc 13y agoI was aware that something remotely similar is done when rewriting LINQ query expressions but I was not aware of this one. My first thought was that they added it to support dynamic and objects not implementing IEnumerable but as you mentioned exactly this seems to be the case where it would not work. I had a look at the C# 1.0 specification and it's already in there but a lot less verbose. Any idea why they did this? The only thing this seems to achieve is that you can implement IEnumerable and IEnumerator without explicitly stating it. Are there any (common) classes to which this applies?
- noblethrasher 13y agoOne big benefit of using structural (rather than nominal) typing in the foreach construct is that value types don't need to be boxed.
- danbruc 13y agoI am not sure about this - why would requiring an interface require boxing?
- noblethrasher 13y agoInterfaces are reference types and so if the GetEnumerator method on a struct is called through an interface then it must be boxed.
- danbruc 13y agoI see your point but I am not yet convinced. At least if the interface method is not explicitly implemented, I can call the same method not through the interface. But I don't know the specs well enough to judge if this would be a viable optimization avoiding boxing or if this could cause different behavior, for example if you start handing out the this pointer. Thinking a bit more about it, it probably does. Maybe I will have a closer look at this out of curiosity.
- noblethrasher 13y agoYes, if the method is not explicitly implemented then you can call it without going through the interface. But at that point it's just a regular method that might also happen to implement part of an interface that you the programmer are not using (at least at that point). Likewise in the case of foreach, the compiler is looking for a particular method and does not need to care whether or not that method happens to also implement part of an interface, so there is not point in making the programmer go through the trouble of marking the type as such.
- MichaelGG 13y agoCollection initializers also do not need to be collections or anything. They just need to be init'ing a type that has IEnumerable and an Add function: class Heh : IEnumerable { public void Add(object x) { Console.WriteLine(x); } public IEnumerator GetEnumerator() { return null ; } } static void Main() { new Heh { "a", 1, DateTime.UtcNow }; } } Prints out: a, 1, 6/25/2013 4:51:38 AM, It might have been the right engineering approach all things considered. But having these special things that only the language can decide is annoying. The fewer the language primitives, the better.
- danbruc 13y agoIt actually makes some sense - this is not a language primitive but syntactic sugar for a common task baked into the language. It's the same for using, foreach, events and link query expressions. In all cases you have to map the language construct to methods and properties. Now you can argue whether it is better to just map to the methods Add<T>(T item), GetEnumerator<T>() and Dispose() or if you require an interface like IInitializable<T>, IEnumerable<T> and IDisposable. On one hand requiring an interface just adds some overhead, on the other hand it makes the intention much more explicit. They came up with different solutions for different cases - using requires the interface, foreach supports both ways and there is no interface for collection initializers. I don't understand why foreach supports both ways - if I implement GetEnumerator() adding IEnumerable to the interface list seems no unreasonable requirement just like in the IDisposable case. I can understand that there is no interface for collection initializers because this feature was added in C# 3.0 and would have required adding an new interface to all existing collection classes, or changing interfaces or starting with an inconsistent implementation. Would be interesting to hear about the details of the decisions.
- MichaelGG 13y agoI'm arguing it's better to build the duck typing features that foreach and initializers use into the language itself. Every hard-coded keyword and to some extent, feature, is a strike against the language. foreach and stuff like that should be part of the stdlib.