This may come as a surprise for some people, but I’ve been using C# and Objective-C both for almost exactly as long (between three and four years). What you might call my current day job is comprised of writing C# code.
Here’s the thing with .NET. .NET is great. It’s not great as the One True Platform for Windows development. On the other hand, it’s not “great for something that’s from Microsoft” either. It is great on its own merits, perhaps, and it is a shame that its merits are so tightly coupled to provide an excuse for C# to exist, because they are each shackled to eachother.
.NET is now, and has been for a while, at least perceptibly noticeably faster than Java. I’m personally way better off with C# than with Java. What I think I’m doing at this stage is establishing that I really quite like .NET and C# both, and that even if I didn’t, I’d think they were more than reasonable efforts for a technology-of-the-week company like Microsoft.
That said, there’s a big technology - and philosophy - divide between C# and Objective-C, and between .NET and Cocoa. Coding in both of them means that I’m soaking both my feet in water of different temperatures. I am in a fairly unique position to stand with one foot in a bucket of cold water and one foot in a bucket of hot water and announce how, on average, I’m feeling pretty good. To wit: When I’m in C#, I end up missing Objective-C. When I’m in Objective-C, I end up missing C#. Here, therefore, are some reflections, most prompted by a chat I had with Scott earlier today.
I love generics. This appeared gradually and surprised me at first, but it’s quite true. Generics, if you don’t know, means that you can supply types to classes when using them - like
List<string>orDictionary<string,int>- and have instances whose methods change to accept those types. I’m generally firmly in the duck typing camp, but C# and Objective-C both need compiling, and when there’s a compiling stage, you can get warnings and errors. The Objective-C code for storing, say, a string in an array, makes it impossible to know when you’re passing in non-strings without doing type checks manually beforehand, or creating tuned methods that only accept the given type in order to generate the standard compiler error.C#:
List<string> strings = new List<string>();
strings.Add(”xyzzy”); // takes only strings
strings.Add(15); // compiler error
string x = strings[0]; // guaranteed to be a string
// or non-existant (yielding an exception)
strings.Remove(0);Objective-C:
NSMutableArray *strings = [[NSArray array] mutableCopy];
[strings addObject:@”xyzzy”];
[strings addObject:[NSNumber numberWithInt:15]];
string x = [strings objectAtIndex:0];
[strings removeObjectAtIndex:0];The previous example brings to mind another wider look at the same subject - lists. In Objective-C, you get a fairly simple choice for an array: mutable or immutable (default). Being the strict C superset that it is, you can also drop down into C arrays and pointer trickery.
C#, on the other hand… Someone at Microsoft enjoys having a dozen deceptively similar - but ultimately all a little different - ways to solve this problem.
At the bottom end, you have C# arrays, which are similar to C arrays. They are fixed-size and immutable. They’re also alone in being natively initializable via literals. A low-level class,
System.Arrayhandles the juggling, and eachArrayis typed for a specific type but using a special, C array-like syntax (string[]), instead of generics syntax. This is because generics were introduced in C# 2.0 and .NET 2.0, and theArrayhas been along (with the same special treatment) since the very beginning.Slightly above C# arrays are non-generic lists. The
ArrayListis an almost perfect parallel to Cocoa’sNSMutableArray- something that stores only objects. You can store anything in anArrayList. These were the only mutable all-purpose lists in .NET 1.0 and 1.1.Above
ArrayListlie generic lists -List<T>, whereTis to be substituted to a type you pass in when you instantiate, subclass from or otherwise use the class.So far, so good. What’s wrong with the hierarchy is everything above this point. Imagine for a second that you have a control called a ListView. (Hint: Microsoft does.) Imagine further that you want a property inside to return the list of items for your pursual. Imagine also that these items are all of class ListViewItem. If you were Microsoft, you might want to use, simply,
List<ListViewItem>. Too bad you’re not Microsoft, and too bad ListView was around in .NET 1.0, way before generic lists.In these kinds of situations, there are special purpose lists (belonging to some sort of fuzzily defined grouping called Collections meaning basically “objects that have other objects in them”). In fact, there are fields, endless fields, where Collections are no longer being born, they are grown. So what do you end up with?
ListView.ListViewItemCollection- a special-purpose class if you ever had one - specifically designed to holdListViewItems. To be fair,ListViewItemCollectionhas at least one speciality - that of being able to accessListViewItems by passing theirKeyproperty as the indexer - literally likecollection["key"].Just having several hundred very special-purpose classes isn’t a valid point of criticism, though. The strange thing is that many of them drop (or choose not to implement) small parts of functionality. Some might not ‘do’ enumeration (the technology that enables
foreachloops), some might leave out Contains-like functionality to check for the existance of a value. It’s all very confusing and a very poor and uneven experience for the programmer, especially since the class’s raison d’être is often deeply hidden in the documentation, if mentioned at all.As much as I like generic lists, I despise most of the rest of the trickery.
Boxing! C# gets boxing (wrapping non-objects in objects) right -
intanddoubleandstringand friends are supposedly primitives, but are surprisingly well-mapped back and forth to real classes to the point where you really can’t tell unless you’re profiling. In Objective-C, on the other hand, the boxing process is entirely manual, like:NSNumber *integer = [NSNumber numberWithInt:8]; int x = [integer intValue];C# has constructors. Objective-C has alloc-init method pairs, designed to be called in a chain, or class convenience methods for doing both and returning an autoreleased object for you. When you call a constructor in C#, it first creates the object for you and then calls the appropriate constructor, whose objective it is to set up the object, already assigned to
this. In Objective-C, you’re responsible for returning the objectself. In both languages, you’re responsible for calling the appropriate superclass constructors.The C# way involves less manual labor and book-keeping. In Objective-C, you can return an object that has already been initialized, say, if you’re keeping a cache of objects not supposed to be created twice, instead of the object just created.
There’s no correct answer, but the different approaches will be sure to have you curse at one time or another.
The class models in C# and Objective-C are vastly different.
Objective-C has bona-fide class methods, eligible for specification in Objective-C protocols. C# has static methods, not eligible for specification in C# interfaces. Objective-C does not have class variables, but it does inherit static variables from C, which works almost the same. C#’s doodads are also called static variables. In Objective-C, no instance variable can be assigned immediately on declaration, but must be instantiated in the init instance method; in C#, both static and instance variables can be assigned immediately, and to the return value of any statement, including functions. (Since Objective-C inherits C’s static variables, you can only assign literal values to them on declaration, and not return values of methods or functions.)
C# has dynamic dispatch off by default - it’s opt-in by the use of the
virtualkeyword in the method declaration and theoverridekeyword in the ‘overriding’ method. Classes can be prevented from containingvirtualmethods by being declared with thesealedkeyword. It’s also possible to declareabstractclasses, which haveabstractmethods that have to be overridden and use dynamic dispatch. Objective-C has dynamic dispatch on by default. (Dynamic dispatch is being able to define the methodfooin classA, introducing classBthat inherits from classA, implementing methodfooin classB, and having classBcalled when you call the methodfooon classB.)Finally, C#, like Java before it, and Objective-C before it, uses a policy of one superclass, n interfaces/protocols. In Objective-C, the way of specifying an interface type is
ObjectClass<Interface>; in C#, it’s justInterface. (C# has one root class,System.Object, which everything implicitly inherits from by default; Objective-C allows defining new root classes and provides theidkeyword - in actuality the runtime’s data structure of an object - to specify ‘any object whatsoever’.)
There’s a lot of ground to cover on C# and Objective-C, and on .NET and Cocoa. The origins and fates of .NET and Cocoa are, dates taken out of the equation, remarkably similar: objective-oriented juggernauts gaining traction with smaller and newer apps, but largely failing with the cross-platform market and the big, heavy, thousand-year apps.
What best sums up my feelings about Objective-C and C# is this: Objective-C+Cocoa is smaller and better defined, and scores a higher average level on the graph. However, C#+.NET’s extremes - both good and bad - are more pronounced. At its best, it’s above Objective-C+Cocoa by a good deal; at its worst, it’s right down the chasm with no hope of improving.
I am certainly hoping that both teams are watching each other with open minds.