It's no secret that I love C#. I believe that in this day and age, C# is, 1) overall and 2) for the majority of development needs, the best solution. Let me clarify my thoughts about 1) and 2):
1) C# doesn't win in all areas against every language. It doesn't match Python in beauty, C++ in control and efficiency, Java in cross-platform support, etc. But all these languages heavily compromise somewhere else. C# doesn't make any extreme trade-off: it's fast, without the being the fastest; it's reasonably clean, while staying familiar to C++/Java programmers; it's compatible across a good variety of platforms, while providing superior Windows support, useful since Windows is still so prominent today.
2) I'd venture to say that for the majority of software, both programmer productivity and performance are important. There are times where productivity >> performance, where Python may be an even better choice; or times where performance >> productivity, in which case C++ may be the only solution. But in the vast majority of cases, we have very limited budgets to pay our programmers, and we still want the fastest software they can build with the limited time and funds we can pay them.
C# fits the bill perfectly: as its specification states, it aims to be "economical with regard to memory and processing requirements", while recognizing the importance of "software robustness, durability, programmer productivity", "source code portability", "internationalization", and be suitable for "the very large (...) down to the very small". I think C# has the right philosophy behind it: it's pragmatic. Anders Hejlsberg has always understood (as his work on Turbo Pascal and Delphi shows) that a useful general-purpose tool is one that doesn't epitomize one ideal over everything else.
C# is also much more than a relatively well-designed language, because there are many others. For example, Cobra is probably a better language than C#. But no one knows about it, because the only thing Cobra has to speak for itself is a compiler and a half-assed (more like quarter- or tenth-assed) Visual Studio plugin, while C# has full Visual Studio integration, that works well out of the box, is not too buggy, has good documentation, tons of support to be found on stackoverflow.com, books, etc. Today, a naked compiler is nothing; a good language is also the ecosystem around it and its online community. I wouldn't be half as productive as I am with C# if I had only a compiler and a support forum with 3 members.
That said, the fact that C# is the best we have today leaves me rather bitter, because see, C# sucks in many ways. It sucks less overall than others, but there's still a lot to be desired.
1) IDisposable. The main problem with a garbage-collected-only language like C# is deterministic resource management. Garbage collection manages memory very well, but there are tons of other resources: handles to various operating system objects (files, sockets, etc.), blocks of manually allocated memory (often needed for interop with native code), and more generally, being able to perform some cleanup operation when you're done with an object. The solution Microsoft came up with was IDisposable and the using pattern, which sucks because a) implementing IDisposable is a breaking change, b) the Dispose pattern is extremely touchy and difficult to understand, and c) the onus is still on the consumer of the class to ensure Dispose is called. Stephen Cleary wrote an excellent article on the topic.
2) Fail constructor/virtual semantics inherited from C++/Java. Why does this code compile without warnings? It doesn't even make sense! Yet, it's very easy to miss the problem without explicitely looking for it. And this code sample is approximately the smallest that reproduces the issue, so imagine how easy this is to miss in a larger code base.
I'm no Heljsberg, but I'm sure there are ways to achieve what constructors and virtual methods achieve while making this scenario impossible. Keep in mind, I'm not saying this code should behave differently; I'm saying a good language is one that doesn't compile nonsensical code (semantically, not logically, of course).
3) Poor syntax inherited from C. C has an egregious syntax for for loops and switch statements, among others, yet for some reason C# (and many others!) inherits it. It's not just the clumsiness and the amount of typing necessary, it's mainly that these constructs are error-prone.
4) No non-nullable references. You won't hear me say this often, but C++ has C# beat on this one. In C++, you can declare a reference that is statically provably not null (T& myObject), and proving something statically is always infinitely better than sort-of-but-not-really proving it through unit tests or code contracts. This concept is easy to implement and would rule out a whole class of NullReferenceExceptions, and NullReferenceExceptions represent probably 50% of all failures of .NET programs!
Anders Hejlsberg himself has admitted this as something he regrets not putting in, and today it is too late to add it to the language.
(See Anders Hejlsberg: Questions and Answers at 1:00:35)
5) Poor generics support. It's better than Java, mind you! At least the genericity is not magically erased at run-time. But when you read about generics, the first thing you want to do (assuming like me, you like writing games) is this:
Mind you, this can be side-stepped by defining interfaces with "Add()", "Multiply()" methods etc., but that's a poor alternative! Jeffrey Richter, in CLR via C#, notes the same thing: "This is a severe limitation on the CLR's generic support, and many developers (especially in the scientific, financial, and mathematical world) are very disappointed by this limitation. (...) Hopefully, this is an area Microsoft will address in a future version of the CLR and the compilers."
It goes further than this, however. Another major issues with generics is that they didn't make it for version 1.0 of the framework, and as such, much of the class libraries are designed around having no generics. Some libraries had new, generic versions when 2.0 shipped (notably System.Collections.Generic), but most did not. A lot of subtle issues crop up, a typical one being:
I think that sums up the big picture issues with C#. I have of course other pet peeves, like the fact that LINQ-to-objects is too slow to really replace for loops in general, or that floating-point performance sucks (although that's more of a CLR issue), etc, but these could be addressed relatively easily by Microsoft, if they wanted to.
To conclude, C# may be the best we have right now, but we should hope and ask for something better. If you haven't, try out Python, Scala, Cobra, F#, see how they do things differently, see how they change the way you think about programming, and how these ideas could make it into C# or some successor of C#.
Unfortunately, Microsoft is more busy catering to users of horrible languages (Javascript integration in Windows 8 is particularly vexing) than trying to push them towards greener pastures. I understand the financial motivation, but it's sad.
As a side note, the current Microsoft enthusiasm for C++ worries me. I've watched every Herb Sutter talk where he tries to explain why "performance" is suddenly so critical in 2012, but that doesn't explain why there are no signs of a new version of XNA. Or why they don't fix WPF performance problems if performance is so important (isn't WPF written in C++ anyway? That should make it fast, right ?
). I fear Microsoft is shifting its focus from a beautiful framework that could use some love to a horrid mishmash of under-specified, overly-complex features piled on top of a brittle, dangerous memory model (Eric Lippert, principal designer of C#).
1) C# doesn't win in all areas against every language. It doesn't match Python in beauty, C++ in control and efficiency, Java in cross-platform support, etc. But all these languages heavily compromise somewhere else. C# doesn't make any extreme trade-off: it's fast, without the being the fastest; it's reasonably clean, while staying familiar to C++/Java programmers; it's compatible across a good variety of platforms, while providing superior Windows support, useful since Windows is still so prominent today.
2) I'd venture to say that for the majority of software, both programmer productivity and performance are important. There are times where productivity >> performance, where Python may be an even better choice; or times where performance >> productivity, in which case C++ may be the only solution. But in the vast majority of cases, we have very limited budgets to pay our programmers, and we still want the fastest software they can build with the limited time and funds we can pay them.
C# fits the bill perfectly: as its specification states, it aims to be "economical with regard to memory and processing requirements", while recognizing the importance of "software robustness, durability, programmer productivity", "source code portability", "internationalization", and be suitable for "the very large (...) down to the very small". I think C# has the right philosophy behind it: it's pragmatic. Anders Hejlsberg has always understood (as his work on Turbo Pascal and Delphi shows) that a useful general-purpose tool is one that doesn't epitomize one ideal over everything else.
C# is also much more than a relatively well-designed language, because there are many others. For example, Cobra is probably a better language than C#. But no one knows about it, because the only thing Cobra has to speak for itself is a compiler and a half-assed (more like quarter- or tenth-assed) Visual Studio plugin, while C# has full Visual Studio integration, that works well out of the box, is not too buggy, has good documentation, tons of support to be found on stackoverflow.com, books, etc. Today, a naked compiler is nothing; a good language is also the ecosystem around it and its online community. I wouldn't be half as productive as I am with C# if I had only a compiler and a support forum with 3 members.
That said, the fact that C# is the best we have today leaves me rather bitter, because see, C# sucks in many ways. It sucks less overall than others, but there's still a lot to be desired.
1) IDisposable. The main problem with a garbage-collected-only language like C# is deterministic resource management. Garbage collection manages memory very well, but there are tons of other resources: handles to various operating system objects (files, sockets, etc.), blocks of manually allocated memory (often needed for interop with native code), and more generally, being able to perform some cleanup operation when you're done with an object. The solution Microsoft came up with was IDisposable and the using pattern, which sucks because a) implementing IDisposable is a breaking change, b) the Dispose pattern is extremely touchy and difficult to understand, and c) the onus is still on the consumer of the class to ensure Dispose is called. Stephen Cleary wrote an excellent article on the topic.
2) Fail constructor/virtual semantics inherited from C++/Java. Why does this code compile without warnings? It doesn't even make sense! Yet, it's very easy to miss the problem without explicitely looking for it. And this code sample is approximately the smallest that reproduces the issue, so imagine how easy this is to miss in a larger code base.
class Base {
public Base () { SayHello(); }
protected virtual void SayHello() {}
}
class Derived : Base {
string m_hello;
public Derived() { m_hello = "hello!"; }
protected override void SayHello() {
Console.WriteLine(m_hello.Length);
}
}
var d = new Derived(); // throws NullReferenceException ?!
I won't explain what the problem is, because the fact that many people don't immediately see what's wrong here illustrates the problem perfectly. If you really don't see it, paste the code in Visual Studio and run it line-by-line in the debugger.I'm no Heljsberg, but I'm sure there are ways to achieve what constructors and virtual methods achieve while making this scenario impossible. Keep in mind, I'm not saying this code should behave differently; I'm saying a good language is one that doesn't compile nonsensical code (semantically, not logically, of course).
3) Poor syntax inherited from C. C has an egregious syntax for for loops and switch statements, among others, yet for some reason C# (and many others!) inherits it. It's not just the clumsiness and the amount of typing necessary, it's mainly that these constructs are error-prone.
// What is the problem here?
var array = new int[20][];
for (int i = 0; i < array.Length; ++i) {
array[i] = new int[20];
for (int j = 0; i < array[i].Length; ++j) {
array[i][j] = i * array[i].Length + j;
}
}
If you're looking for it, this is easy to spot; obviously the second loop's end condition should compare j, not i. But since I'm a human and I'm not always paying attention, this kind of error sometimes slips by, and the compiler cannot help me, since it's perfectly valid code. Consider that simply changing the syntax makes this error impossible, at no cost in performance or expressiveness:// VB.NET (much better): For i = 0 To array.Length // Cobra (even better) for i in array.Length
4) No non-nullable references. You won't hear me say this often, but C++ has C# beat on this one. In C++, you can declare a reference that is statically provably not null (T& myObject), and proving something statically is always infinitely better than sort-of-but-not-really proving it through unit tests or code contracts. This concept is easy to implement and would rule out a whole class of NullReferenceExceptions, and NullReferenceExceptions represent probably 50% of all failures of .NET programs!
Anders Hejlsberg himself has admitted this as something he regrets not putting in, and today it is too late to add it to the language.
5) Poor generics support. It's better than Java, mind you! At least the genericity is not magically erased at run-time. But when you read about generics, the first thing you want to do (assuming like me, you like writing games) is this:
class Vector2<T> {
public T X { get; set; }
public T Y { get; set; }
public static Vector2<T> operator +<T>(Vector2<T> a, Vector2<T> b) {
return new Vector2 { X = a.X + b.X, Y = a.Y + b.Y };
}
}
Oops, operator + isn't defined for T. Well, constraints should solve that, right? I should be able to say:class Vector<T> where T : operator+or
class Vector<T> where T : NumericWell, no you can't. Turns out the only thing you can put in that "where" clause is "new()" (has a parameterless constructor), "SomeType" (implements or derives from SomeType), "class" or "struct" (is a reference or value type). The very common scenario where you want to make an algorithm valid for any numeric type isn't supported, because there's no way to specify: "T is a numeric type".
Mind you, this can be side-stepped by defining interfaces with "Add()", "Multiply()" methods etc., but that's a poor alternative! Jeffrey Richter, in CLR via C#, notes the same thing: "This is a severe limitation on the CLR's generic support, and many developers (especially in the scientific, financial, and mathematical world) are very disappointed by this limitation. (...) Hopefully, this is an area Microsoft will address in a future version of the CLR and the compilers."
It goes further than this, however. Another major issues with generics is that they didn't make it for version 1.0 of the framework, and as such, much of the class libraries are designed around having no generics. Some libraries had new, generic versions when 2.0 shipped (notably System.Collections.Generic), but most did not. A lot of subtle issues crop up, a typical one being:
foreach (var e in Enum.GetValues(typeof(MyEnum))) {
if (e == MyEnum.Val1) {} // doesn't compile because type of e is object
}
// What would be if .NET 1.0 had had generics:
foreach (var e in Enum.GetValues<MyEnum>()) {
if (e == MyEnum.Val1) {} // compiles !
}
I think that sums up the big picture issues with C#. I have of course other pet peeves, like the fact that LINQ-to-objects is too slow to really replace for loops in general, or that floating-point performance sucks (although that's more of a CLR issue), etc, but these could be addressed relatively easily by Microsoft, if they wanted to.
To conclude, C# may be the best we have right now, but we should hope and ask for something better. If you haven't, try out Python, Scala, Cobra, F#, see how they do things differently, see how they change the way you think about programming, and how these ideas could make it into C# or some successor of C#.
Unfortunately, Microsoft is more busy catering to users of horrible languages (Javascript integration in Windows 8 is particularly vexing) than trying to push them towards greener pastures. I understand the financial motivation, but it's sad.
As a side note, the current Microsoft enthusiasm for C++ worries me. I've watched every Herb Sutter talk where he tries to explain why "performance" is suddenly so critical in 2012, but that doesn't explain why there are no signs of a new version of XNA. Or why they don't fix WPF performance problems if performance is so important (isn't WPF written in C++ anyway? That should make it fast, right ?






About non-null references, you could try Spec# which extends C# with non-nullable references (in fact, references are non-nullable by default in Spec#), code contracts (requires/ensures/modifies/... like Cobra, it seems) and stuff. I read the spec but never tried it, though - it looks like it was made to create statically provable code, which is very nice but a PITA to write since methods, properties, even loops must say exactly what they do and what guarantees they offer.