My main concern about C# is that with each release, it becomes bigger and seems to be more of a kitchen sink language. This phenomenon has been the downfall of other popular industrial languages, notably C++.
As the language grows, so does the number of interactions between features. All the little corner cases start to get in the way of writing clean, useful code.
As the language grows, it also becomes harder for new programmers to learn it effectively, by which I mean not just knowing the syntax, but also understanding the idioms and being familiar with the libraries to make good use of the language in practice. For real world projects that make use of the advanced features, it therefore becomes harder to find staff who are sufficiently clued up in the areas that are in common use on any given project.
The more programming I've learned, the more I am convinced that effective programming languages are simple. This doesn't mean they can't also be powerful, but the programming model is clear and the interaction between different features is either systematic or non-existent. Nearly every widely successful industrial language achieved its success initially when it was a simple language. Many industrial failures -- in the sense that they continue to be used more because of various kinds of momentum than because of their technical merit as programming tools -- became so when they stopped being simple.
I have to disagree. I've used C# professionally since its release. At first I totally agreed with you. It seemed like the language was growing like a weed and was becoming totally unmanageable. The release of C# 3 basically led me to say "what the hell, Microsoft?"
But one major counterpoint to all of this is the features added to C# 3 may seem like huge bloat at first, but when you realize they all exist to support LINQ, you end up seeing them as a pretty cohesive package. C# 3 was by far the biggest growth spurt the language has seen to date. Implicitly typed local variables, extension methods, lambdas, expression trees, etc, all exist solely to make LINQ possible. The fact that they are usable outside of LINQ is just an artifact. And honestly I think it's a good artifact, I feel all of these features have benefited the language. The more I use the new features (whether in the context of LINQ or not) the more I noticed my code becoming cleaner, more succinct and much easier to read.
C# 4 doesn't quite have the same major killer feature that 3 had to warrant its additions. But at the same time, most C# 4 features are meant for special cases (dynamic primarily makes COM interop much simpler, for example) and don't leak into day to day programming that much. Abusing these features is just a judgment call a programmer is going to have to make.
I do think MS treads a very fine line here. It would be easy for C# to fall over the edge and become "the kitchen sink", but as a very avid user, I honestly think they (so far) tread that line very carefully and quite well. Let's see how C# 5 turns out.
I have liked where the language has gone as well. I actually think that C# is a lot like Scala, with more mainstream uptake. It is not a small language, it is not simple, but it is a true multi-paradigm toolkit. You can do straight-out functional programming, lazy evaluation, OO, all out of the box. You can mix metaphors where appropriate, and the potential to end up shooting yourself in the foot is much reduced from that of C++. My biggest gripe has always been the vendor lock-in, there just don't exist the set of tools that ship with Java, particularly for profiling and configuring the VM.
What kitchen sink was thrown in C++? The STL? That's the main reason I first fell in love with C++. Templates? Exceptions? RTTI?
IMO the problem with C++ is its C legacy (things like macros). These things make it harder to read code and make it harder to write great tools for it (ever seen really good code completion or static analysis for C++ code?).
The kitchen sink comes from the conflict between the C ideology C++ inherited, and the requirement for OO language features. RTTI and exceptions are fine when you can rely on them, but C++ users don't always want to pay for those features, so you end up with some libraries that require them, others that forbid them, and some in between which reinvented their own exceptions and RTTI mechanisms. Trying to glue all these disparate things together where there's so little accepted as being in common is a pain.
Languages built around platforms, like Java and C#, on the other hand, can assume that java.lang.Object / System.Object is the root of the inheritance hierarchy, that exceptions are used throughout for error handling, that reflection is available throughout (modulo security concerns), etc. The fact that you can rely on the base library, and that you can communicate between modules using containers / common interfaces in the box makes the job of integrating modules far easier.
C++ included some of these features, but didn't make them mandatory. So you end up with a strange mix across the board.
The other issue is that C++'s features aren't well thought out in an orthogonal sense. One example: the idea of exception safety doesn't mesh well with C++'s copy constructors, to the degree that you can't write a generic stack type whose pop method returns the value popped, because you can't get exception safety right on the mutation to the stack if the copy constructor on the returned value threw an exception.
- Classes, object-oriented programming and run-time polymorphism
- Generic programming, templates, compile-time polymorphism and metaprogramming
- Text-based macros
- Hacks and, from C++0x, built-in language support for in-line functions
- Exceptions
- Low-level memory access
By my count, every one of those things interacts with almost every other one in some way. Often, there are deep connections, such as the RAII idiom, that are fundamental to using C++ effectively but not obvious to someone with a good general programming background but no expertise in C++ specifically.
The real problem, though, is that C++ isn't really very good at any of those things in isolation. There are much better approaches to all of them, and there are tools available that take those approaches. They just don't fit together to form a coherent whole, where features are either orthogonal or blended together cleanly. It used to be the case that C++ offered a more powerful overall tool than most other languages because of the diversity of its features, but today's languages have had the benefit of decades more experience and hindsight to draw on. The very diversity that used to be a compelling reason to use C++ has now become the most compelling reason not to, IMHO.
-Procedural programming
-Structures and enumerations
-Text based macros
-Low-level memory access
And for the items they actually added to C to make C++, none of them are all that unusual, save templates (not sure if there is any other language that does it compile time).
IMO, Scala has more parts of the sink in it than C++. The problem with C++ isn't the kitchen sink. It's that its built on a language that isn't meant to hold the things that C++ would like to add to it.
Maybe I'm being pedantic, but when I see "kitchen sink" I have a vision of a lot of features. But frankly C++ isn't all that feature rich. But its the interaction complexity that makes it problematic. This is not a problem of throwing the kitchen sink at it, but rather a design problem.
Several that I've worked on. There were aspects of the language that we avoided, like export, because they weren't implemented. But in general, we didn't have ground rules about features to use or not use. At least not in the past 15 years. Back in '93ish we would be more careful, because the features were still evolving, but by 2002-2003 I don't think we had a lot of issue using the language.
As the world of programming grows, I think it's natural that programmers become more specialized. I think the value provided by sophisticated, complex tools (the language and core API) is worth the additional complexity. I don't think you can avoid complexity by sticking with simple tools - you simply offload the complexity into the application code and end up in the same situation where in order for new programmers to understand the code, they have to have extensive knowledge of the application codebase.
It may even be that concentrating the complexity in the language allows programmers to more easily switch between projects and get up to speed on new projects since they're already familiar with the language complexity and thus have less application-specific complexity to learn.
I agree. It seems like if someone were to learn C# 3.0 and wanted to learn how to use LINQ with lambda, for example, they probably could figure out how to use it as far as syntax, but they wouldn't really know how it works (as far as .NET implementation) or why it was implemented, without having to back to knowing how things were done in C# 2.0.
Where C# differs from C++ in this case is that the standard actively seeks out such corner cases and defines them explicitly, whereas the C++ standard often leaves such corner cases in the "undefined behavior" realm. There is very little "undefined behavior" punting in C#.
I'm not sure how significant that difference will be in practice. It's easy to bash C++ for "undefined behaviour" or "implementation-defined behaviour", and to a certain extent it's fair criticism. However, a lot of the interactions are defined.
Unfortunately, they're defined in chapters of the C++ standard containing dozens of pages of dense language lawyerese. Even experts then get them wrong on a fairly frequent basis, and while the underlying idea behind some of the rules makes sense if you think it through deeply, the rules can still be counter-intuitive to a typical programmer who isn't a standards geek.
In other words, it doesn't much matter if the corner cases are defined explicitly, unless you can define them in such a way that they don't catch practising developers out. I have yet to see any large, complicated programming language succeed in doing this.
I've read most of the C# 1.0 and 3.0 specs. I've also read the R6RS Scheme spec and the Python 2 spec. Admittedly, I've only read a smattering of the C++0x spec. But the C# specs have always been very clear and concise compared to just about every other technical spec I've read.
Side note: R6RS is not the acme of good scheme specs, you might want to try e.g. R4-5RS sometime, especially as R6RS adaptation has been ... less than universal (heck, it proscribes a REPL (no kidding, and not by accident); it's really a different language than the Scheme before it, designed by different people).
As the language grows, so does the number of interactions between features. All the little corner cases start to get in the way of writing clean, useful code.
As the language grows, it also becomes harder for new programmers to learn it effectively, by which I mean not just knowing the syntax, but also understanding the idioms and being familiar with the libraries to make good use of the language in practice. For real world projects that make use of the advanced features, it therefore becomes harder to find staff who are sufficiently clued up in the areas that are in common use on any given project.
The more programming I've learned, the more I am convinced that effective programming languages are simple. This doesn't mean they can't also be powerful, but the programming model is clear and the interaction between different features is either systematic or non-existent. Nearly every widely successful industrial language achieved its success initially when it was a simple language. Many industrial failures -- in the sense that they continue to be used more because of various kinds of momentum than because of their technical merit as programming tools -- became so when they stopped being simple.