Design flaws in .NET

This post was imported from blogspot.

I made this list of the flaws in .NET (CLR+BCL) for StackOverflow, but then I noticed that the question is closed. Whatever, I may as well CC to my blog. All of these flaws except #3 have irked me personally. .NET is my favorite platform, yet it is still far from ideal.
  1. There are no subsets of ICollection<T> and IList<T>; at minimum, a covariant read-only collection interface IListSource<out T> (with an enumerator, indexer and Count) would have been extremely useful.

  2. .NET does not support weak delegates. The workarounds are clumsy at best, and listener-side workarounds are impossible in partial trust (ReflectionPermission is required).

  3. Generic interface unification is forbidden even when it makes sense and causes no problems.

  4. Unlike in C++, covariant return types are not allowed in .NET

  5. It is not possible to bitwise-compare two value types for equality. In a functional "persistent" data structure, I was writing a Transform(Sequence<T>, Func<T,T>) function that needed to quickly determine whether the function returns the same value or a different value. If the function does not modify most/all of its arguments, then the output sequence can share some/all memory from the input sequence. Without the ability to bitwise compare any value type T, a much slower comparison must be used, which hurts performance tremendously.

  6. .NET doesn't seem able to support ad-hoc interfaces (like those offered in Go or Rust) in a performant manner. Such interfaces would have allowed you to cast List<T> to a hypothetical IListSource<U> (where T:U) even though the class doesn't explicitly implement that interface. There are at least three different libraries (written independently) to supply this functionality (with performance drawbacks, of course--if a perfect workaround were possible, it wouldn't be fair to call it a flaw in .NET).

  7. Other performance issues: IEnumerator requires two interface calls per iteration. Plain method pointers (IntPtr-sized open delegates) or value-typed delegates (IntPtr*2) are not possible. Fixed-size arrays (of arbitrary type T) cannot be embedded inside classes.

  8. The fact that identical delegate types are considered incompatible (no implicit conversion) has been a nuisance for me on some occasions (e.g. Predicate<T> vs Func<T,bool>). I often wish we could have structural typing for interfaces and delegates, to achieve looser coupling between components, because in .NET it is not enough for classes in independent DLLs to implement the same interface--they must also share a common reference to a third DLL that defines the interface.

  9. DBNull.Value exists even though null would have served the same purpose equally well.

  10. When creating a thread, it is impossible to cause the child thread to inherit thread-local values from the parent thread. Not only does the BCL not support this, but you can't implement it yourself unless you create all threads manually; even if there were a thread-creation event, .NET can't tell you the "parents" or "children" of a given thread.

  11. The fact that there are two different length attributes for different data types, "Length" and "Count", is a minor nuisance.

  12. Extension methods that accept mutable structures cannot modify those structures. (Yes, I've heard mutable value types are evil. You know what else is evil? Mutable reference types. Unnecessary heap allocations. Immutable structs that make you twist your code in contortions to change one property. It's tradeoffs all the way down, my friend.)

  13. I could go on and on forever about the poor design of WPF... and WCF (though quite useful for some scenarios) is also full of warts. In general, the bloatedness, unintuitiveness, and limited documentation of many of the BCL's newer sublibraries makes me reluctant to use them. A lot of the new stuff could have been far simpler, smaller, easier to use and understand, more loosely coupled, better-documented, applicable to more use cases, faster, and/or more strongly typed.

  14. I'm often been bitten by the needless coupling between property getters and setters: In a derived class or derived interface, you can't simply add a setter when the base class or base interface only has a getter; if you override a getter then you are not allowed to define a setter; and you can't define the setter as virtual but the getter as non-virtual.

If we broaden this list to include flaws of C#, it would be a lot longer. For instance, C# has no ??= operator; you must write variable = variable ?? value. Indeed, there are a few places in C# that needlessly lack symmetry. For example you can write if (x) y(); else z(); (without braces), but you can't write try y(); finally z();.