There's a ton of classes [1] in the .NET framework. As much as I'd like to think all the engineers behind .NET are brilliant, sometimes, you'll learn how they've implemented something, and you think to yourself, WTF?
What are some of the things that have made you pause and ask yourself, "WTF?," while using the .NET library?
Here's an example:
Why does StreamReader inherit from TextReader, and not the other way around, and BinaryReader derive from neither? After all, a StreamReader could be reading any type of data, while a TextReader should only read text, you'd think, right?
How about some methods that should obviously (in your mind), be virtual, but can't be overridden?
Many of these WTFs were described in the " Framework design guidelines [1]" book. Brad Abrams (one of the book authors and PM of .NET FW) confessed that there were some problems in .NET design. .NET is HUGE, a lot of people were involved in it's development. In my opinion it's quality and consistency is still superb taking into account it's complexity and diversity.
There was a code analysis of .NET FW made by NDepend tool. And if I recall correctly metrics were not bad.
I really amazed with this solid software. I can forgive them some design glitches taking into account huge number of wonderful features in .NET.
[1] http://rads.stackoverflow.com/amzn/click/0321246756I know that there are reasons for this, but honestly I was a bit surprised when I figured out how Math.Round worked.
1.5 rounds to 2.
2.5 rounds to...2!?!?
By default Math.Round will round to the nearest EVEN number. I know that there is a lot of history and reasoning behind this, but it runs contrary to everything I've been taught about arithmetic.
For a great discussion on the poor naming of core .Net classes you mention in the question I would recommend Microsoft's Framework Design Guidelines [1] book. There is a lot of information in that book written by members of the Microsoft .Net team. They discuss how poorly they named items like the *Reader classes, and what they learned from their mistakes. I was impressed at how candid the comments were from various developers inside Microsoft.
Besides being a great postmortem on .Net 1.0, the book discusses naming conventions, design guidelines, etc, for ongoing development of .Net. This is one of my favorite tech books.
There is also a second edition [2] coming out November 3, 2008.
[1] http://rads.stackoverflow.com/amzn/click/0321246756I have no doubt that if .Net designers were to design it again, they would make some (or many) adjustments. the question is do you want to wait for the ideal framework to come to being before you start doing something or you want to start producing some apps today? at the end of the day, it's the programmer, not the tool that makes the difference.
my 2 cents Ali
Not that this is any excuse for the framework, but if you've ever worked with MFC, you'd feel that the .net framework was a masterpiece.
Another huge WTF is .Net Framework versioning scheme. I never understand why they renamed WinFX to .Net 3.0. Calling latest version 3.5 completes the confusion for most people.
Imho 3.0 should be 2.5 and 3.5 should be 3.0 (or 2.10 if someone want to respect version of clr). Or they can flollow Ubuntu versioning scheme [1].
[1] http://en.wikipedia.org/wiki/Software_versioning#DateToo much of the documentation is perfunctory and not helpful, especially examples. No help on what the property means, or why one would want to change the value. This sort of thing:
Widget.UserValue This property is the User Value of the Widget.
Example:
Widget.UserValue = true ;
Don't forget about these things:
Websites. Why did they even bother? They are much slower to compile. They compile down to a "random" number of dlls named in the worst possible way (random names). You need to publish sites in order to deploy them (why?). Pages and classes don't have namespaces by default... and all this when there was a much, much better option, web applications. Fortunately they re-introduced web apps later on, otherwise I would have dropped .Net for any serious work.
Object Datasource. Object maybe, but not strongly typed. Everything is a string internally. Sometimes it stops working altogether. Just avoid.
Various things in membership. Password can be stored in clear. Or encrypted. Or hashed. And this preference can be changed on a live site. So you end up having half of your passwords hashed and the other half encrypted... in the same database column, IIRC.
Again on membership: you have users and profiles. Of course you can't create a user and a profile at the same time (like User u = new User(...); u.Profile = new Profile(...); u.Save();). You have to have 2 database calls. Without a transaction (OK you can use a distributed transaction, but...)
There is no interface that represents the basic arithmetic operations, on the other hand there is an IComparable interface for the (not so dissimilar) "Compare" operator. To make things worse, number classes and value types do not share a common ancestor.
Exceptions! FxCop and MSDN guidelines do not agree on how to properly write an exception (derive from Exception or ApplicationException)?
The WebService class. Why is it there at all? All ASMX functionalities are provided in a "wired-in" not configurable way via Reflection. What if I want to extend these? Not possible!
ConfigurationSection and ConfigurationElement. ConfigurationSection derives from ConfigurationElement, and can contain a ConfigurationElement. Therefore, one must assume that a ConfigurationSection can contain another ConfigurationSection. Which would be great: let's say you need to configure a class that uses another class. Each has a configuration section to specify the values. You should be able to make one ConfigurationSection contain the other accordingly. But, lo and behold! DotNet will allow you to do this at compile time but throw an error at runtime. WTF?
I always thought the XmlWriter was a pretty insane design for an abstract base class. The following are just the abstract methods you have to provide - there are plenty of other virtual and non-virtual variations.
One of the things that bothers is that there are so many different ways callers can do the same thing. When you think you have a correct implementation, and you go to use it with an XmlSerializer, you always learn something new about caller expectations you're not satisfying.
But the biggest WTF has to be WriteRaw. I mean the XmlWriter is supposed to be the abstraction of the formatting of the infoset being written, but that method allows the caller to format any fragment into xml text and pass it in. What's a non-xml-text writer supposed to do? Parse the xml text fragment in context to the best of it's ability and call whichever methods should have been used in the first place?
public class CustomWriter : XmlWriter
{
public override void WriteStartDocument() { }
public override void WriteStartDocument(bool standalone) { }
public override void WriteEndDocument() { }
public override void WriteDocType(string name, string pubid, string sysid, string subset) { }
public override void WriteStartElement(string prefix, string localName, string ns) { }
public override void WriteEndElement() { }
public override void WriteFullEndElement() { }
public override void WriteStartAttribute(string prefix, string localName, string ns) { }
public override void WriteEndAttribute() { }
public override void WriteCData(string text) { }
public override void WriteComment(string text) { }
public override void WriteProcessingInstruction(string name, string text) { }
public override void WriteEntityRef(string name) { }
public override void WriteCharEntity(char ch) { }
public override void WriteWhitespace(string ws) { }
public override void WriteString(string text) { }
public override void WriteSurrogateCharEntity(char lowChar, char highChar) { }
public override void WriteChars(char[] buffer, int index, int count) { }
public override void WriteRaw(char[] buffer, int index, int count) { }
public override void WriteRaw(string data) { }
public override void WriteBase64(byte[] buffer, int index, int count) { }
public override void Close() { }
public override void Flush() { }
public override string LookupPrefix(string ns) { throw new System.NotImplementedException(); }
public override WriteState WriteState { get { throw new System.NotImplementedException(); } }
}
I don't have enough reputation yet to answer volothamp directly, but the .ForEach() thing bothered me at first too. However, I realized that a .ForEach() on an IEnumerable would be incredibly redundant. It's kind of the reason foreach works in the first place. Adding an IEnumerable.ForEach() would just be a second way of doing a foreach loop, but this time with perhaps a performance hit, and with no significant savings in text or clarity.
Really worth anybody's time to implement, test and document if the only thing it accomplishes is helping people shoot themselves in the foot?
There is a handy Pair class for storing 2 related objects, but it's buried in System.Web.UI [1]
[1] http://msdn.microsoft.com/en-us/library/system.web.ui.pair.aspxSystem.Data.SqlCient.SqlException
does not have a public constructor,
so it makes simulating database
failures when mocking difficult.System.Data.Linq.DataContext
does
not implement an interface, so it
makes mocking LINQ to SQL far more
difficult.The DateTime class is causing me major headaches. The fact that for half the year DateTime.Now == DateTime.UtcNow (in the UK anyway...) means that our code gets peppered with code where the two formats are jumbled up, which means when summer rolls around we get problems. I'd like them to be separated into two classes, UtcDateTime and LocalDateTime which would have a TimeZone property.
From the .Not, the .Net hall of SHAME [1]:
[1] http://www.veridicus.com/tummy/programming/dotnetshame.aspProblem:
System.Collections sucks. There has been an attempt design decent collection classes but the "engineer" gave up too soon. There is IList and IDictionary but there is no IQueue or ISet and too many classes in the .NET framework rely on concrete implementations of IList and IDictionary (such as ArrayList and Hashtable) rather than just use the abstract concepts of just IList and IDictionary. Any first year computer science student knows the importance of abstracting abstract data types (list, dictionary, stack) from their implementation (arraylist, hashtable, arraystack). One wonders where the guy who designed System.Collections "earned" his computer science degree...
Solution:
Refer to first year text books (or Java2 documentation) and write decent collection classes.
Welcome to the world of iterative, cross-team design.
The .Net team isn't just one team. It's a CLR team plus a compiler team plus a framework team plus an IDE team. Each has their own set of budgets, deadlines, requirements, technical restrictions... the works.
Whenever you see a WTF, it'd not a "durrrrrr", but a balance of time and resources to get a product out the door. Some challenges are easy in some areas and hard in others. There is a TON of legacy stuff laying around from 1.0 and 1.1 when they didn't have access to generics. It would have been nice to have them in the first go-around, but it wasn't in the cards. That said, it's beyond outstanding that the 2.0 runtime supports as much extensibility as it does. There's been nothing but patches to the runtime, and the base framework at large, since, oh, mid-2004. They prioritized getting things out like anonymous delegates, generics, etc., which led to such innovations as Linq, functional language support and dynamic runtimes.
The language and framework designers are constantly measuring user requirements up against resource considerations to get you the best bang for the buck. You may see something that looks like a WTF, and sometimes it truly is a questionable decision, but most of the time it's a simple matter of, "this is what we could afford to get out to you when you needed it." I, for one, could not have waited until late-2008 to get this framework.
here's a few that leap to mind:
Not including a reference to System.Configuration in WinForm/Console apps by default always made me say, "WTF..."
CodeDom
.
Why is almost every class in CodeDom
sealed?
CodeMemberProperty
should support separate visibility for the getter and the setter, but it doesn't. I also can't work around the design because (naturally) all the relevant classes are sealed.
The ternary operator is not supported in all .NET languages, so I have to generate the 3 expressions and then concatenate them together in a CodeSnippetExpression
to be able to generate them. Why not support the operator, and then let the CodeDom
Some combinations of member attributes aren't distinct, and as such can't be generated, such as sealed override
.
Argh, CodeDom.
Another one for the lame collections. Why the hell was't there a Set<T>
class from the start?
Why does ReadOnlyCollection<T>
inherit off IList<T>
? It's meant to be a collection, not a list!
ArraySegment
[1] doesn't implement any enumerator. Which means you can't use foreach
on one.
BitArray
[2] is non-generic (even in .NET 4), and the eumerator only returns an object
, not a bool
. Which means that when you're enumerating through one, every single bit value will be boxed and then unboxed.
Why does Enum.ToObject()
not return an Enum
instead of an object
? And why does it not have a TryParse
method?
None of the object GetSomething(Type t, ...)
methods scattered around have had T GetSomething<T>(...)
versions added, which would remove such nastiness as
MyEnum e = (MyEnum)Enum.Parse(typeof(MyEnum), value)
and replace it with
MyEnum e = Enum.Parse<T>(value);
especially for the service componentmodel stuff...
I could go on...
[1] http://msdn.microsoft.com/en-us/library/1hsbd92d.aspxNo EventArgs<> in 2.0+, I have to write my own each time.
It's not that its hard to write, but... wtf? Especially since they do have EventHandler<>.
EventArgs
class should work?! - Konrad Rudolph
My WTF is why is there no IsNumeric() function on the string class. VB had it, so why doesn't it exist in C#? I must say though, that with extension methods, I've created an extension to the string class called IsNumeric, so that does kind of solve the problem, but still, WTF?
One my most puzzling WTF's is why the JavaScriptSerializer (in System.Web.Script.Serialization - http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx) has been made obsolete between the release of AJAX 1.0 and .NET 3.5.
It's the best way to serialize third-party object and a beautifully quick way to serialize any object over DataContractJsonSerializer
The Microsoft team's love of XML. Don't get me wrong, I can see the uses of it for "enterprise" type applications, but for a lot of uses, it's just too much and its binary file support isn't a substitute for something human readable.
The BaseNumberConverter handles hex incorrectly because someone mistyped some parentheses (means it will accept hex numbers for doubles).
I second DateTime. It would be solved in 3.5 with DateTimeOffset, but DateTimeOffset not generated in the XSD.exe, not are you able to use in XmlSerialization attribute.
I toss in that in .Net 2.0, Nullable was added, and DbNull... If you say it's an improvement on java, then why again did we not use objects.
Generally, this is why it's important to do usability studies of your APIs/Object Models.
.NET classes' usability has improved since 1.1, mainly because of deliberate efforts to make them so.
Once upon a time, the fact that System.Uri
was a MarshallByRefObject
was a huge WTF, but
it's been fixed now
[1].
a) Lack of visual inheritance with generics in Windows Forms. This makes typeful view - classes a pain in the youknowwhat.
Must have been some intern developing this feature.
b) Necessity to pay a fantasy price to be able to use Code Contracts correctly. I mean... seriously?
I vote Collections too. And not only the old one, but the new ones too.
Why IEnumerable hasn't got a "Foreach" Extension method? I know that I can write all by myself, but I want to know WHY.
The biggest WTF to me is nothing in the framework itself, but rather a term they used a while ago.
Being able to call unmanaged code from managed code had the acronym IJW.... or in longer form, "It Just Works".... WTF!?!?! That makes me confident ;). But alas, all the work i ever did like that, did work, and quite well; but still the term was scary.
Something slightly less WTF is the amount of highly useful classes & methods that are marked sealed/private/internal.