Saturday, February 25, 2012

Design flaws in .NET

I made this list of the flaws in .NET 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 cause 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. There is no WeakReference<T> (you can easily write your own, but it will use casts internally.)

  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. 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();.

  11. 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.

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

  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.