While working on some code for work, I came to the understanding that, in general, the C# team (at least in its public form, i.e. the public responses to questions and justifications for lack of features), suffers in two ways.
First off, they suck at the details (more to come).
Second off, they are idiots (to be explained as well).
Now, to be fair, it is late and I’m really, really goddamn annoyed, so the team likely isn’t as stupid as I’m going to make them out to be. On the other hand, for the general amount of publicly displayed ego from Anders and kind, they should be gods of what they are doing.
First off, in terms of sucking at the details, we have C# generics.
As part of the stuff I’m working on this week, I’m designing public interfaces for an algorithm that operates over lists of like elements. In the signature for these methods, what I really want to say is that some generic type parameter, T, is some kind of IList. I don’t care what kind it is, I just care that it is an IList of something.
Now, in Java, you can do this. Java generics has a whole slew of constraints, from specifying superclasses (a type must be the super class of some other well-known type) to using general wildcards. In fact, this is precisely what I want to do: the Java equivalent of “T extends IList<?>”, which says “something that is derived from IList with any generic parameter, and remember T”.
What I’m trying to avoid is a type parameter that looks like this:
public MyType<T, U>(T first, T second) : where T : IList<U>
Because it has to be called like this:
new MyType<List<string>, string>(bleh bleh)
Come on now – do I really need to type “string” twice? Can’t you figure that crap out from the first guy? Please? Pretty please?
Of course, since C# is sooooo much better than Java, they’ve solved this problem, too, right?
Wrong.
Generics constraints in C# are more limited than, well, something that’s really limited, like Anders’ ability to restrain himself from stating that he has invented language features (like, you know, lambda expressions). C# generics give you only a few things – you can say that it has to be of a certain specific type (no wildcards), you can say it is a struct/class, and you can say it has a certain constructor signature (”new()”).
Of these, the first works probably 75% of the time (this is the type of generics that is good for collections and little more), the second seems only trivially useful, and the last seems a downright sad stab at C++ templates.
Let’s be fair here – even Java’s generics pale in comparison (in expressive power) to C++’s templates, but the whole point of Java’s generics was to be a safer and more explicit version of C++’s templates. The Java team knowingly sacrificed expressiveness for safety and prettier static checking.
On the other hand, the C# team, in its “let’s just glob together language features that look cool!!!!1one” phase, saw generics/templates but missed the point. So, in my code, I either have to tell people that it is safe to cast something to another type, or require that everyone use the extra type parameter everywhere (which becomes really ugly really fast, and pretty code is happy code is fast code is good code).
The second point here is that the C# team sucks at justifying things because, well, they are idiots. If you’ve followed this blog at all, you might remember some earlier articles on checked exceptions, and how C# bowed out completely (out of dumbitude, not sound design decisions), whereas Java and C++ take the “take your pick” road.
This time, the subject is optional and named parameters. C# has named parameters on attributes only, but not on argument lists, and has no concept of optional parameters.
The general reasoning cited for this genius design decision is the following:
- You can just write overrides
- Default parameters in C++ can do slightly unexpected things in rare circumstances
- Default parameters are scary and make intellisense really really complicated
Right, this shouldn’t be too hard.
First off, writing overrides is an awful replacement for default parameters and named parameters. Let’s say you have (as I do) some type of algorithm that you want to make available to the public in some way. Let’s say, also, that this algorithm is configurable along a few different axes (in my current situation, there are 4).
Now, for me, one of these axes is the type of one of the parameters, so this isn’t a good place for overrides. For this first axis, there are 3 possibilities (so I have at least 3 methods to start with, in its current implementation).
The second axis has 5 or 6 discrete entries – this is a great place for an enum. However, the default enumeration value isn’t really obvious from the individual types, so I’d want to provide a way to call into the method and get the default behavior. That means each of the 3 existing methods has to be split into two methods – one with the second axis, and the other just assuming the default.
Finally, the third and fourth axes are continuous values representing percentages (used in two different scenarios). Because these, again, have suggested values that are non-obvious, it would be nice to have default parameters. So we should probably 2 more methods for each of the original three, bringing us up to a total of 12 methods. Not too bad, right?
Wrong. You see, one of the nice things about named/optional parameters is that you only supply the ones that you use. With what we’ve built, you can’t, for example, only supply the value for the first percentage and leave the other two blank. If we really wanted to allow that, we need to supply methods on the 3 axes (the non-type axes) such that the user can specify as much or as little as they need. For the combinatorically minded, we are looking at the following:
3 items x 1 place = 3 possibilities
3 items x 2 places (where order doesn’t matter) = 3 possibilities
3 items x 3 places (where order doesn’t matter) = 1 possibility
So we have 7 methods for each of the 3 original methods. 21 total. Now it is getting a bit ridiculous.
But wait! We still aren’t really done, because this won’t even work - two of our parameters are the same type (two percentages, likely floats). Since they are the same type, they would look equivalent in the scenario where you use the default value for one of them, so you would have to either a) create a well-typed value for each one (annoying and dumb), b) switch up the ordering of the two methods to allow the signatures to be different (annoying and dumber), or c) say fuck it and pick one to be configurable (annoying and dumb).
Named/optional parameters are fundamentally different than having overloads. Just optional parameters (a la C++) are the same thing as overloads (since they are dependent on ordering and a signature defined by type), but adding named parameters introduces the idea that the strict definition of a method does not include the ordering of its named parameters, and this very fact turns what was above an exponential growth of parameters into a growth along a different axis – simply adding more parameters.
In this world, I have 3 methods (one for each type), and each takes the 3 optional parameters. 3 methods instead of 21 methods. Seems like a good deal to me.
I’ve almost forgotten, but as for the other points: the issue with C++ optional parameters is that the compiler places the default value into the caller’s compiled code. This is generally just fine, unless the default value changes and the caller doesn’t recompile – if this happens, then the caller will use the old default value.
Big whoop-de-friggin-do. Keep the type information on the callee side – you are writing the goddamn language and virtual machine environment, so don’t make the same dumb mistake. I imagine this is the way that named/optional parameters work in dynamic languages anyways, and I also imagine that you wouldn’t pay gigantic penalties for using named parameters (in other words – if your primary performance issue is that using named parameters takes too long, then you are already probably in the clear of major performance issues).
Finally, named/optional parameters don’t make intellisense any uglier than having named/optional parameters on attributes. Unfortunately, the intellisense on named parameters in attributes is rather atrocious, but that is just further argument for the fact that the people who wrote that had their heads up their asses.
Bleh. I told myself 7 months ago that I would never write in Java again, mostly because, at an expressiveness level, Java is like drawing with crayons that are tied to pickles that are glued to your fingers. I was hoping that C# was a bit better, with delegates and events and now lambdas and type inference, although my hopes weren’t too high, since Java and C# are fundamentally the same in terms of expressiveness.
Today has finally cemented in my mind that C# is and will continue to be as big (if not bigger) a steaming pile of donkey shit as Java is.