Tuesday 19 June 2007

Cons of C#

C#

  • Problem:
    No anonymous inner classes. Anonymous inner classes are the OO equivalent of lambda abstractions and can be very powerful.
    C# features type-safe function pointers called delegates but has NO concept of anonymous inner delegates. This makes Java's event handling with anonymous inner classes much more convenient than C#'s named delegates.

    Solution:
    Add support for inner and anonymous inner classes (J# supports them! Jesus!) as well as anonymous methods.

    The syntax for anonymous methods could look like this:

    public MyForm()
    {
    this.Click += new EventHandler(object sender, EventArgs eventArgs)
    {
    Close();
    };
    }

    The code without anonymous methods would look like this:


    public MyForm()
    {
    this.Click += new EventHandler(MyClickHandler);
    }

    private void MyClickHandler(object sender, EventArgs eventArgs)
    {
    Close();
    }

    It may not look impressive but trust me, many event handlers are only one line long and writing a member function for each handler isn't pretty. Making event handler methods part of the class often makes no sense because the event handling code often has no context outside of the method that adds the handler. Classes are often less cohesive when they contain event handlers as top level methods. Anonymous methods increase encapsulation and localises the method to the area where it is used.

    [Update: 18 November 2002: Microsoft have announced that a future version of C# will include support for anonymous methods.]

    It will probably be a cold day in hell before Microsoft adds support for anonymous inner classes. The reason is simple, Java has them and "newbie" Java developers love to complain about how confusing they are. Microsoft will not add the feature in an attempt to gain respect from the developers who are confused by the language feature in Java.
    Anonymous inner classes (like anonymous methods) increases encapsulation and cohesion and would make a good addition to C#.

  • Problem:
    The syntax for using structs and classes look the same but, semantically, they can mean very different things.

    e.g. The following code will behave differently depending on whether or not Point is a class or struct.

    Point point1 = new Point(10, 10);
    Point point2 = point1;

    point2.X = 20;

    Console.WriteLine(point1.X);

    If Point is a struct, changing point2.X won't have an effect on point1.X. This IMO is the most fundamental flaw in C#.

    Solution:

    One solution would be to require the use of the struct keyword when referring to value types. Intrinsic value types (int, short, long, etc) can ignore this requirement.


    public static void Foo(struct Point p)
    {
    // I can see that p is a struct so I know I have a copy and not a reference.

    p.x = 20;
    }

    public static void Main()
    {
    // I know p is stack allocated because I'm using 'new struct' instead of 'new'.

    struct Point p = new struct Point(10, 10);

    // I can see that p is a struct so I know I'm passing Foo() a copy of p.

    Foo(p);
    }

    Using the struct keyword also conveniently eliminates the confusion that C++ programmers have over the use of the new keyword to construct value types [In C++ new is used to allocate objects on the heap -- never the stack]. This solution is so obvious (especially when you consider C#'s C++ ancestry), elegant and unobtrusive that it's amazing that Microsoft missed or dismissed it.

System.IO

  • The Environment class contains the static property CurrentDirectory. Setting this property changes the current directory of the process. This is a very poor design decision. Changing the current directory for the entire process has major consequences for multi-threaded and multi-appdoman applications. Ignoring the issue of how sensible (or not) it is to allow the current directory to change, there is another problem: the use of a property setter to change global state. Microsoft's own documentation states that a set method should be used in place of a property setter if setting the property will do something major.

    Sun, being thoughtful engineers, have not added the ability to change the current directory in Java. This means that in the future, applications can all run under one VM without the random chaotic state changes that .NET applications will have to endure.

    Solution:

    Make the CurrentDirectory property a read only property. Add a new method called SetCurrentDirectory. Document the method as potentially dangerous in multi-threaded applications.
  • FileSystemInfo.Refresh throws an ArgumentException if the underlying file doesn't exist. This makes no sense because FileInfo.Refresh takes no arguments.

    Solution:

    FileSystemInfo.Refresh should adapt ArgumentExceptions into FileNotFoundExceptions before throwing them.

System.Windows.Forms

  • Problem:
    The standard controls such as the TreeView, ListBox and Combo box don't separate the UI from the data. The controls store the data they display themselves rather than requesting the data from a model as required. This way of doing things encourages developers to use the UI controls as a way to store their data (something no self-respecting OO programmer would even begin to consider).

    Solution 1:

    Use the Model-View-Controller architecture to separate the concerns of data display and data storage. This has the benefit of eliminating the need to store data in two locations (the UI and the storage classes) and releases the developer from having to manually populate and update the UI control.

    Solution 2:

    Use DataBinding to seperate the UI from the data. This method won't work with TreeViews.



    Example of why M-V-C is superior and Windows Forms sucks:

    Say we need to use a ListBox control to display the contents of an ArrayList. The problem we're immediately faced with is how to communicate to the ListBox the contents of the ArrayList. The Microsoft way would be to manually walk through the ArrayList and for every item in the ArrayList add it to the ListBox. This means that the ListBox, internally, contains a a copy of every item in our ArrayList. The ListBox contains data and this breaks one of the first rules of UI design. Containing data means that the ListBox is slower and takes twice as much memory. If our ArrayList contained 10000 items, the ListBox would also need to use memory to store those 10000 items. If we also wanted to display the items in the ArrayList in a ComboBox and ListView at the same time it would use three times as much memory. Additionally, there is a huge problem with data synchronization. If you remove an item from the ArrayList you have to remember to update the ListBox (this includes having to write the code to find the item in the list box and update it).

    The OO solution is simple and elegant. So simple in fact that it would make Windows Forms much easier to implement (for Microsoft) and use (for developers). ListBoxes no longer need to store and maintain an internal list of items they need to display. You just need to define the following interface:


    public interface IListModel
    {
    ListModelEventHandler event Added(ListModelEventArgs e);
    ListModelEventHandler event Removed(ListModelEventArgs e);
    ListModelEventHandler event Changed(ListModelEventArgs e);

    int Count
    {
    get;
    }

    object GetAt(int index);
    }

    The ListBox control would no longer store the data it displays. The ListBox would simply be passed an instance of IListModel. Every time the ListBox needs to render an item in the list, it would consult the IListModel instance and ask it for the item to draw.

    This means that the ListBox never stores any data (potentially saving a lot of memory). It also means you don't have to write complex algorithms to keep your data and the UI control (the ListBox) in sync. You simply have to fire an event from the model every time you think your data may have changed. This is much simpler than having to search the ListBox and modify the appropriate items. The other cool thing about using MVC is that your data doesn't even have to exist in memory at all. You can write a model that returns calculated values. For example, you can write a model that returns "0" as the item at index 0 and "1" as the item at index "1" etc. This allows you to make a ListBox that displays and looks like it contains 2^32 numbers without every needing to store all those numbers. If you tried to do that using Windows Forms your program would most likely lock up and run out of memory.

    Example of using MVC based ListBox, ListView and ComboBox to display all integers:


    class IntegerModel
    : AbstractListModel
    {
    public override int Count
    {
    return int.Max;
    }

    public override GetAt(int index)
    {
    return index;
    }
    }

    IListModel model = new IntegerModel();

    ListBox listbox = new ListBox(model);
    ListView listview = new ListView(model);
    ComboBox combobox = new ComboBox(model);


    Example of using Microsoft's ListBox, ListView and ComboBox to display all integers:


    ListBox listbox = new ListBox();

    for (int i = 0; i < int.Max; i++)
    {
    listbox.Items.Add(i);
    }

    ListView listview = new ListView();

    for (int i = 0; i < int.Max; i++)
    {
    listview.Items.Add(i);
    }

    ComboBox combobox = new ComboBox();

    for (int i = 0; i < int.Max; i++)
    {
    combobox.Items.Add(i);
    }

    The code using the MVC/OO listbox is clearer, declarative and allows us to easily and dynamically change the values displayed. We only have to declare that we want to display all the integers once (not three times). Doing it the Microsoft way would result in repetitive code, lots of memory use, hard disk thrashing and eventually an OutOfMemoryException and even if it did work, it would be a pain to maintain.

  • Problem:
    The Panel control is intended to be used as a container for other controls yet it derives from ScrollableControl rather than ContainerControl (ContainerControls are ScrollableControls but ScrollableControls aren't ContainerControls). Who does Microsoft hire to design this crap?

    Control -> ScrollableControl -> ContainerControl
    |
    -> Panel

    The design makes it look like Panel isn't a container control when it reality that's the Panel control's primary purpose -- being a general purpose container for other controls.

    Solution:

    Make Panel derive from ContainerControl.

    Control -> ScrollableControl -> ContainerControl -> Panel
  • Problem:
    The AnchorStyles and DockingStyle enumerations are inconsistently named. The enumerations are so closely related you have to wonder how the hell anyone could make a mistake like this.
    (Yes I do realise that AnchorStyles is a [Flags] enumeration)

    Solution:
    Rename AnchorStyles to AnchorStyle.
  • Problem:
    Layout management support is poor. Every control has a DockStyle and AnchorStyle property. This is bad cause it means that each control *knows* about where it will sit. This should be left up to a different class (the LayoutManager).

    Solution:

    Use the strategy design pattern. Every ContainerControl should have a LayoutManager property which set/gets the class responsible for laying out child controls.
    A default layout manager that supports the classic Anchor-Dock style can be provided to support VB programmers.
    This solution replaces enumerations (which are fixed) with an extensible way of adding support for new layout algorithms.
  • Problem:
    Most controls only have the default parameter-less constructor. For example, Button doesn't have constructors that takes the button text. This was probably overlooked cause the form designer doesn't need it.
    Some of us like to program GUIs manually with code rather than by dragging and dropping though.

    Solution:
    Add useful constructors to the standard controls.
  • Problem:
    There is no easy way for a control to capture the keyevents from its child controls (and their child controls etc). In Java/Swing, this is a simple flag you set when you register the action listener.
    Forms have the KeyPreview property which lets you do this for top level windows but not controls. KeyPreview is also pretty lame compared to what Swing lets you do.

    Solution:
    Add support for this by adding more advanced key event registration.
    (BTW You can hack around this using IMessageFilter).
  • Problem:
    ImageLists are used everywhere but they shouldn't be. Although image lists can improve performance (if you're still using a 386), they are much harder to work with than simply setting an "Image" property for each component. Good design should come first and optimisation can come later. There is no reason to drag the old Windows "ImageList" technology into the .NET era.
  • Solution:
    Don't use ImageLists. Everything that has an Image should have an Image property. Performance can be regained by internally caching Images.
  • Problem:
    Control borders aren't implemented using the strategy design pattern. This means it is up to the control writer to add support for borders themselves. This leads to more work for control writers and less features and flexibility for control users.

    Solution:
  • Use the strategy design pattern instead using &*$%@# hardwired enumerations.

System.Collections

  • Problem:
    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.

  • Problem:
    CollectionBase is evil. It exposes its implementation to deriving classes through the
    InnerList : ArrayList property. The only time the names of ANY concrete collection class (ArrayList, LinkedList, Queue etc) should be used is on the right hand side of the new operator.

    Solution 1:
    Make CollectionBase take an IList in its constructor. InnerList should be exposed as an IList (not ArrayList) and should be assigned the value of the IList given in the constructor. This means CollectionBase can internally use any kind of IList for storage. [Microsoft probably overlooked this because they only supply ONE list implementation -
    ArrayList. Lame].

    Solution 2:
    Don't use
    CollectionBase. Make a generic ListWrapper class that decorates/wraps ANY IList implementing object. Then write a ListWithEvents object that extends ListWrapper. The ListWithEvents object will fire events for every action that IList supports (Add/Remove etc). Then use ListWithEvents where-ever you would normally use your CollectionBase derived class. This eliminates the need to create a new type just to intercept actions that occur on the List. You can use event handling instead. You can listen to IList actions of ANY list implementation (ArrayList, LinkedList etc).

  • Problem:
    IList doesn't have methods such as Sort or Search but ArrayList does. The operations Sort and Search are generic list operations and should not be made specific to a given implementation (such as ArrayList). If you have an IList implementation that isn't an ArrayList then there would be no way to perform a sort or Sort on it. Microsoft's solution is one of the most inelegant I've ever seen. To sort a LinkedList you would have to call ArrayList.Adapter to adapt the LinkedList into an ArrayList. Then you can call the Sort or BinarySearch method on the adapter. This is absolutely horrifying design. Both LinkedList and ArrayList are *lists* which by definition can be searched and sorted using a generic algorithm. There should be no need to adapt a LinkedList into an ArrayList to sort the list.

    Here's a simple illustration of the problem:


    abstract class Animal
    {
    public abstract void Eat();
    }

    class Cat
    {
    public override void Walk() {...}
    public override void Purr() {...}
    }

    class
    Dog
    {
    public override void Bark() {...}
    }


    Cat cat = new Cat();
    cat.Walk();

    Dog dog = new Dog();
    dog.Walk(); // Can't do this!


    All animals can eat but only cats can walk. Later on in the design phase we realise that all animals can walk. What do we do? The natural solution would be to add Walk() to the Animal class.


    abstract class Animal
    {
    public abstract void Eat();
    public abstract void Walk();
    }

    class Cat
    {
    public override void Walk() {...}
    public override void Purr() {...}
    }

    class
    Dog
    {
    public override void Walk() {...}
    public override void Bark() {...}
    }

    Animal cat = new Cat();
    cat.Walk();

    Animal dog = new Dog();
    dog.Walk(); // Yah!

    Based on their work with the .NET collection classes, Microsoft's solution would have been to provide a method that would allow you to adapt any animal into a cat. You would essentially have to turn a dog into a cat in order to make it walk.


    abstract class Animal
    {
    public abstract void Eat();
    }

    class Cat
    {
    public override void Walk() {...}
    public override void Purr() {...}

    public static Cat Adapter(Animal a)
    {
    // Turn animals into a cats here.
    }

    }

    class
    Dog
    {
    public override void Bark() {...}
    }

    Cat catdog;
    Dog
    dog = new Dog();

    catdog = Cat.Adapter(dog); // Disturbing
    catdog.Walk();

    Shocking isn't it?

    Solution 1:
    Add Sort and SortedSearch to the IList interface and provide a default implementations for IList implementers. Sort would sort the list and SortedSearch would do an "optimized" search based on the assumption that the list is already sorted.

    This solution would allow lists to provide customized sorting and searching algorithms. Doing a BinarySearch on a LinkedList makes no sense since lookups have O(N) performance therefore LinkedLists could implement SortedSearch using a linear search algorithm where best performance would be O(1), worst performance would be O(N), average performance would be O(N/2).

    ArrayList.Sort -> Uses Quicksort O(N log N)
    LinkedList.Sort -> Uses Mergesort
    O(N log N))
    ArrayList.SortedSearch -> Uses binary search O(lg N)
    LinkedList.SortedSearch -> Uses linear search O(N)

    Solution 2:
    Make a new class called
    ListAlgorithms which contains static methods for performing algorithms such as SortedSearch and Sort on lists.

    This solution would allow application developers to choose the algorithms they want to use but this means that they need to know the concrete implementation of the list so Solution 1 would be better.

  • Problem:
    All arrays in .NET implement System.Collections.IList. The indexer (IList.Item) is documented to throw ArgumentOutOfRangeExceptions if the index given is out of range. The indexer on arrays throws IndexOutOfRangeExceptions instead of ArgumentOutOfRangeExceptions therefore breaking the contract with IList. This is dangerous because code working against ILists may randomly break when given array instances. This is a good example of why C# should have checked exceptions (aka throws clauses) which prevents this kind of abstraction destroying, contract breaking error.

    Solution:
    Make IList.Item implementers throw IndexOutOfRangeExceptions instead of ArgumentOutOfRangeExceptions (or vice versa).

System.Drawing

Like everything else in .NET, the drawing namespace has a distinct lack of the application of even basic OO principles.

  • Saving image objects (System.Drawing.Image) isn't performed utilizing the strategy design pattern. The Image.Save method should take a strategy class that will automatically save the Image any any desired format. For instance, there could be a JpegSaveStrategy that would know how to take pixels produced by the Image (or some class like ImagePixelProducer) and and convert them to a JPEG stream.

    What Microsoft did was make the lame ass Image.Save method take an ImageFormat. The ImageFormat class is sealed, fixed and neutered and is pretty much just an enum. It is the Image class and not the ImageFormat class that does the saving. This means it is impossible to support saving images to formats that aren't supported by Microsoft. It also means that Microsoft can't add support for new ImageFormats without having to modify both the Image and the ImageFormat classes. The Image, ImageFormat and Graphics classes are very tightly coupled (which is very bad).

Java's Drawing2D and imaging APIs are outstandingly designed. Support for new Image formats (etc) can easily be added without having to modify a single class in the core API. This is .NOT the case with .NET.

Microsoft's insistence on not making their classes thin, hacked wrappers around their native APIs is going to kill them. This will become starkly apparent as incompatibilities between different versions of .NET appear. Come on Microsoft, GDI+ was designed for C++ NOT C#. WAKE UP.

It is clear from looking at the System.Drawing and System.Windows.Forms namespaces that .NET is NOT and was never intended to be a cross platform platform; it is is a cross platform framework. C# will be like C in that it will supply some basic features and leave the rest of the services (UI, Drawing etc) up to the native operating system (which will vary from OS to OS). Java on the other hand is very much a meta operating system/platform. Java supplies the same APIs on all platforms.

No comments: