2021-10-04

What is wrong with C#

This is part of a series of posts in which I am documenting what is wrong with certain popular programming languages that I am (more or less) familiar with.  The aim of these posts is to support a future post in which I will be describing what the ideal programming language would look like for me.  

I will be amending and revising these texts over time.

(Useful pre-reading: About these papers)

What is wrong with C#:

  • The garbage collector.
  • Curly braces.
  • Member initializers execute in a static context. 
    • This is far worse than Java's deficiency of member initializers not having access to constructor parameters; this renders the language almost unusable.
  • Delegates are superfluous and problematic.
    • They could have been implemented as single-method interfaces, as in Java, thus keeping the language simpler.
    • The ability to have an anonymous method implementing a delegate is far less useful than the ability to have an anonymous class implementing an interface.
  • The built-in collection model is lame:
    • Arrays implement the `IList` interface, which might initially seem like a great idea, until you realize that `IList` has `Add()`, `Insert()` and `Remove()` methods, which of course cannot be honored by an array.
    • The `IReadOnlyList` interface was added as an afterthought, and it is not a super-interface of `IList`. 
    • The `IReadOnlyList` interface does not have an `IndexOf()` method. This poses problems that cannot be solved by adding an extension method, because the object implementing `IReadOnlyList` may have its own `IEqualityComparator`, which the extension method will be blissfully unaware of.
    • Enumerators still have to implement the legacy, non-generic `GetEnumerator()` method.
    • Fluent style (Linq) is limited to working almost exclusively with `IEnumerable<>`.
  • Built-in events are problematic. They are unsuitable as a language feature and should have been left for runtime libraries to implement.
    • If one event handler throws, the rest will not be invoked.
    • If one event handler causes another event handler to be removed, the removed event handler will still be invoked.
    • An event is a special kind of thing which cannot be passed to a function, not even by reference. (As all properties are.) As a result, you have to always hand-code the addition of a handler to an event, and to also separately hand-code the corresponding removal. It is absolutely impossible to pass an event to a function, along with a handler and a boolean flag specifying whether to add or remove the handler. This makes it impossible to gather all symmetric initialization and cleanup operations in one place, which in turn leads to buggy software.
  • Extension methods
    • They are a hack.
    • They are one of the most calamitously misused features of the language.
    • Java has shown how to do this right with default interface methods. 
    • Allegedly, default interface methods will also come to C#, but it is not like extension methods will ever be removed from the language to undo the harm they have caused.
  • Parameters declared with `ref` or `out` 
    • Prior to the introduction of tuples, `ref` and `out` parameters could sometimes come in handy, but only in very rare cases. 
    • At the same time, they are one of the most misused features of the language.
    • Now that tuples are part of the language syntax, parameters by reference are nothing but a liability.
  • Names of variables poison enclosing scopes.
    • Iif you declare a local variable inside the curly braces of a `for` loop, you are not allowed to declare a variable with the same name after the closing brace of the `for` loop. Duh?
  • No function-local classes.
    • You can have function-local functions, which is awesome, but you cannot have function-local classes. Duh?
  • The language runtime is shared with other languages like Visual Basic, and some decisions have been made there in favor of Visual Basic.
    • For example, when you dereference a null pointer you do not get a "Null Pointer Exception", you get an "Object Reference Is Not Set To An Instance Of An Object" exception.  (Presumably because the words "Null" and "Pointer" would cause epileptic seizures to Visual Basic programmers.)
  • No compiler-enforced function purity.
    • You cannot somehow declare that a method must be pure and have the compiler enforce that the method, and any overrides of that method, are pure.
  • No compiler-enforced immutability.
    • You cannot somehow declare and advertise that a class is immutable and have the compiler enforce that the class, and its descendants, are immutable.
  • Certain things about the runtime are completely bonkers. For example:
    • The notion of a "current directory", which is a piece of mutable global state that is shared across all threads, and even across all AppDomains within a process.  (So much for AppDomain isolation!)
    • The behavior of the `ThreadAbortException`.
    • What happens to the process exit code if the dotNet process exits due to an unhandled exception. (See https://stackoverflow.com/q/60729865/773113)
    • Collecting a stack trace is a ridiculously slow operation.
    • Throwing an exception is a stupendously slow operation.
    • In order to send a file to the recycle bin, everyone suggests that you should include a module called "Microsoft.VisualBasic". Duh?
      • Adding injury to insult, if you try this, you will discover that it is broken, it just does not work.
  • No namespace-private visibility
    • C# has always provided assembly-private visibility, which is much more useful than Java's package-private visibility, however at some point Java somewhat fixed that by introducing modules, and the ability to specify which packages are exposed by a module. Now C# lags behind Java in that it does not support namespace-private visibility, which means that everything is visible to everything within an assembly, which can easily lead to chaos if the assembly is large. There is a way to somewhat mitigate this by using partial classes as if they were namespaces, but it is hacky.
  • Interface method implementations are not tied to the interfaces they implement.
    • When you declare a class to implement a certain interface, and then you add a method to that class which implements a certain method of that interface, there is absolutely nothing in the declaration of that method to indicate or even hint that it is implementing a method of that interface. Consequently, if you remove the interface from the list of interfaces implemented by the class, the compiler cannot warn you that methods within this class that were implementing methods of that interface are not meaningful anymore. You can try to overcome this problem with explicit interface method implementation, but it is optional and therefore its use cannot be enforced, plus if you use it then you are stuck with other problems, see below.
  • Explicit interface method implementations are not directly accessible.
    • If you declare a method within a class that explicitly implements a method of an interface implemented by the class, then you cannot invoke that method from within that class, unless you first cast `this` to that interface. What the actual fuck?
  • The `switch` statement requires a `break` at the end.
    • If you omit the `break` at the end, the compiler complains that `Control cannot fall out of switch from final case label`.  Duh?
Feedback is more than welcome: you'd be doing me a favor. However, be aware that blogger sometimes eats comments, so be sure to save your text before submitting it. If blogger eats your comment, please e-mail it to me.

No comments:

Post a Comment