My foray into C#
Listen to this article
Well after a relaxing 10 days riding my motorbike around Tasmania I return to Melbourne, chilled (literally), relaxed and raring to go. I think it's about time to convert Simian to .net as had been suggested by many users. So here is a little blog of my experiences.
To start with, I've never written a line of C#. EVER. So I'm totally ignorant about all language constructs (except that having read the CLR book it looks like Java with uppercase method names) including, importantly, the libraries.
I'm also a linux weenie so I'm using mono under Gentoo. The combination is pretty good. Mono comes with mcs the compiler, monodoc, a purpose built browser for the framework libraries and mono for running your assemblies. At least I think that's the new fandangled name for executables. Anyone?
I had first investigated writing an automated tool. The process seemed mechanical enough to automate yet difficult enough to end up doing some curly stuff. I also looked at some off the shelf converters but in the end decided doing it myself by hand was probably a good way to learn the ins and outs of the C# language and at the same time indulge my years of Java bias :-D.
My approach is simple: Open my project in IntelliJ (my editor of choice) and one by one, copy the .java files to corresponding .cs files, run mcs and hack away until it compiles cleanly. I have to also admit that I'm not going to turn my code into C# style stuff during this conversion. All my method names, variable names, class names, interfaces are remaining untouched. I'm not adding an I to my interfaces nor am I giving all my method names an uppercase first letter. Stuff that LOL.
So here we go...
The first thing is the package declaration. This becomes a namespace. Only, some crack smoking monkey decided that it should be a proper block which means I have to have open and closing curly braces around my entire source file. Not so bad but that means everything gets indented one more level. YUK! So I choose to just leave the indenting ASIS. Not so bad. Still seems unecessarily verbose to me.
Next. String becomes string. What's with that? Method names get uppercase but String (a class) gets a lowercase? I got over it. Find+Replace is my friend.
Once again, Find+Replace for boolean. It's a bool in C#. I'm guessing a hang-over from C++. Again, I can live with this.
What's the problem with declaring a class as final? Ahhh. A quick search of the 'net reveals that it's sealed. Ok. Another keyword to remember. Not too bad I guess. On we go...
Crap. How do I declare a constant? In java it's static final. In C# it's, quite logically I guess, const. Bulk Find+Replace for that. Done
Ok. Now I want an immutable (readonly) instance field. In Java I just mark something as final. In C# it turns out, I have to use readonly. Egads! Another keyword to remember.
It's interesting. When I first started using Java I thought it odd that a few keywords, such as final, seemed to be used for slightly different meaning but it soon became apparent that really the meaning was the same: It's final; Unchangeable; Immutable; Not modifiable. C# seems to want me to use a different keyword for every little thing. This is beginning to irritate me but it's still not difficult to do.
Starting to lose enthusiasm for this. I see all this syntactic sugar entering the Java language, obviously driven by C# and I wonder if my already less than perfect Java will become a quagmire of lexical sludge.
On I go...
Syntax Error. Hmmm... Maybe it's not called throws in C#? Quick IM to Mike Melia. C# has no checked exceptions. I knew this. But what I didn't know is that you can't even declare that a mehod throws an exception. Any exception. Hmmm. Oh well. Believe it or not, it's my custom Assert class (starting simple) and it's only IllegalStateException anyway so strictly speaking it doesn't have to be declared. I'll just delete it.
Ok what's the problem now? class IllegalStateException not found. What's an equivelant in C# I wonder. Trudging through the documentation I give up. This doco is really hard to follow. JavaDoc seems much easier to read. Maybe it's just a matter of what I'm used to. I'll just throw ApplicationException instead.
Still giving me grief. I prefix the class name with System.. That does the trick. Hmmm. That's a bit stupid. Having to import the System namespace? Go figure. I choose to add a using for it. I don't like putting package/namespace names into my code.
I discover that C# doesn't allow importing classes. Only namespaces. This is ok by me. I always figure that once you have a dependency on a class in a package, you really have a dependency on all classes in the package in a way. What I don't like is that now I don't have any idea, just by looking at the top of the class, what other classes it depends on.
Woohoo. A clean compile. It's complaining that there is no entry point but I can live with that. I haven't specified a main anywhere. At least we got a compile.
It's now a little after 12.30am. A bit of success has brought back my enthusiasm. I'll keep going.
Time for an interface. Bleh. Can't have public keyword on interfaces. Yeah Yeah I know. They're all public anyway. But as in Java, it annoys me that a missing visibility modifier means one thing for a class and another for an interface. At least in Java I can safely put the public there and it doesn't complain. Find+Replace. Done.
Now time to do one of the classes that implements the interface.
Ok. This is ridiculous. If I have an abstract class that implements an interface but doesn't implement all the methods, I still have to define the methods in the abstract class as abstract. I've defined the class as abstract. Can't the compiler work this out? Sheesh. Talk about needless typing.
Uh. Now I discover I have to explicitly say virtual on all my methods to allow them to be overriden. I have no problem with saying override when I actually do override something, that's kinda cool, but I'm guessing the virtual bit is a hang-over from C++ and/or it makes it easier to optimize the output of the compiler because I the developer have to tell the compiler that there is no need to create jump-vector-tables for the method. Whatever the reason, combined with the previous stuff to do with interfaces, it's really starting to drive me nuts.
Ok. It's now 2:45am and I'm honking along. Thankfully the compiler catches all the bits I forget to change. Sometimes the messages aren't particularly helpful but that may just be the mono compiler. Nothing to do with C# per-se.
I've done all the simple classes I could find. All the ones with few dependencies. Now it's time for some of the more meatier ones. I think I'll keep going until 3.30am and then call it a night.
I have a bunch of decorators (I love decorators) but boy is it a pain in the butt to implement in C#. It's like alphabet soup after I've added all the necessary keywords: virtual, override, sealed, etc. Do Microsoft developers get paid by keystrokes? Anyway I'm getting there. It really is becoming quite mechanical now.
C# uses C++ style class extension. So instead of saying extends you use a colon :. you also call the super-class constructor using the C++ style colon after the constructor name instead of on the first line of the constructor. That's not too bad. Pretty much the same thing anyway. Oh and just to be different, super becomes base.
I've just spent the last 20 minutes learning about the collections classes. I'm beginning to think the .Net libraries were an R&D project that made it into the wild a little too early. 57 (I exaggerate a little hehe) different collection classes and none seem to do what I want.
Aha! There it is. StringCollection. Basically a Set implementation for strings. Ok now to iterate over them...
Grrrr. No Iterators. IEnumerator? Gimme a break. Instead of hasNext() followed by next() they use MoveNext() which returns true if it was successfull and a Current property that is null if there isn't a current value. Ok. but I can change my for loops into foreach which is kinda cool and surely makes up for it. As I mentioned before, that's one of the features I'm looking forward to in J2SDK 1.5.
I've just converted some code that parses numbers and some that performs file I/O. Do you think that I could work out what exceptions might be thrown? I'm going head-first into the unchecked exceptions debate here and state outright that it is plain broken and wrong that the only way I can find out what exceptions can be thrown is to pray and hope that they were documented. My god! Not only that, but the I/O exceptions don't seem to extend any sensible base class. So now instead of hoping I've caught all the necessary exceptions, I'm forced to catch Exception.
I'm very glad I'm converting from fully tested, well designed (if I do say so myself, which I do hehehe) Java code because I truly believe at this stage that C# and the .Net libraries are woeful.
I admit, I've now around 3 1/2 hours of C# experience which clearly makes me an expert, NOT, but I struggle to see how you could take Java, and make it worse and less mature. They managed it though.
Ok, well now I'm screwed. IEnumerator doesn't allow you to remove from a collection whilst iterating. In fact it expressly says this isn't allowed. I've written a bunch of custom collection classes (for performance reasons) that I was hoping I could just ignore for now but looks like I'm going to have to convert them as well just to get the behaviour I need.
It's now 3.45am. Time for bed. My brain hurts. Back into it tomorrow me thinks. I've done around 45% of the code base in a couple of hours. Not bad. It's pretty easy. I wonder what the Microsoft conversion tool is like :-)
It's not too bad so far. A few quirky things here and there. It surely looks like they've tried to make all their existing developers happy by keeping lots of language constructs the same as those in Microsoft flavours of C/C++, VB, etc. I can understand that. In fact I think in some ways it's remarkable that they think that way about their developers. But still it's a bit of a heinz 57 varieties in places.
Comments
Great story Simon. Going through some exercise to learn a more about C# has been on my to-do list for a while now. The experience seems less scary now.
Posted by: Marty Andrews | January 19, 2004 02:16 AM
"I think I'll keep going until 3.30am and then call it a night."
Let's think about this for a moment...
Just a couple more hours and you could call it a morning... :)
Posted by: Jason Yip | January 19, 2004 04:50 AM
Simon, at my previous employer, we developed a java->c# translator. MS translator took over 5 hours to translate our code; it does not know how to read *.jar files so you need to translate all of the source all the time. We decided to write our own libraries that wrapped the standard libs from each platform to get to 100% automatic translation. One interesting thing that we found is that 100% of java syntax is not translatable.
Try this: Class A has foo(int); class B extends A and has foo(int) and foo(double), so foo(int) is overriden and foo(double) is just overloaded. Call instance of B with an int param (b.foo(10)). See how it works differently in java and C#.
Another surprise is:
Intgeter i = new Integer(10);
assertSame(i,i); //true in java, but not c# if using Int32 value object
Posted by: David Vydra | January 19, 2004 10:27 AM
Speak for yourself, Marty... after reading this, I'm even more concerned about dabbling in C#.
The learning curve with modern languages is all about the libraries. If their collections library, and the old COM standard interfaces, are any example, the libraries sound like they're a bit of a convoluted mess.
Oh, and Simon? Get some more sleep. :)
Posted by: Robert Watkins | January 19, 2004 01:37 PM
Funny read, but so totally biased. I could write the same kind of stuff about all the things that bug me in Java as a C# developer and bitch for pages. The choices of the other side always looks weird because they are not our own habits. It doesn't mean they're wrong.
A few things, though:
- a namespace is not a package, and using is not the same as import: using just enables you to use syntactic shortcuts, and you still have to reference the library. That's why "importing" a class does not make sense in C#.
- You can still use String, with capital S. String and string are strict synonyms. Most people choose to use string when declaring, and String when calling a static method.
- C# may have a lot from Java, but the syntax comes more from C++. Hence the keyword differences. For most of these, you could say that Java departs from C++, not that C# departs from Java. But not always, granted.
- About the class library and Javadoc, I have to say that's precisely what I find the most dreadful in Java. So that must really be subjective and a matter of habits. Convoluted mess? I think not! Not what I'm used to would be closer to the truth.
- Structs are different from objects because they are value types, like an int or a bool. It means boxing/unboxing, passing by value, etc.
- Explicitly declare virtual IMHO helps prevent some of the nastiest bugs you can create in OOP. Anyway, if you want to override a method that has not been marked virtual, you can still shadow it using the "new" keyword instead of overrides.
- Let me troll a little: you may not like the way .NET handles regular expressions, but at least it HAS a RegEx library. All right, Java now has one too.
- Saying that the libraries must have been written by first year comp science students is just plain trolling
- Try to do your perf measurements with the MS runtime instead of Mono
It seems like everyone is satisfied with what he works with. That's a good thing, isn't it? No need to despise the other side... It's still the Server Side.
Posted by: Bertrand Le Roy | January 20, 2004 04:22 PM
One last thing about perf: if you take a C# application and translate it into J#, you'll probably find the C# app much faster. It does not mean that one language is faster than the other. It just means that the languages are different enough for the programming styles to have an impact on performance. An application written specifically for a platform will always be faster than one that's been translated from another (especially if this translation was made in just a few hours).
Posted by: Bertrand Le Roy | January 20, 2004 04:27 PM
Hey Bertrand, thanks for the feedback. I didn't actually post to the server side. That was someone else. I just blogged it as it happened :-)
Regarding performance, knowing the code base, I disagree that in my case the language has that much of an impact. Rather I'd say the mono runtime under linux accounts for the difference. But I already stated that :-)
I totally agree with you on "what one is used to".
Have a look at my latest post and I think you'll feel a little more at ease :-)
-- Cheers, Simon
Posted by: Simon Harris | January 20, 2004 05:27 PM
Thanks for the answer. The joke on the server side referred to their new .NET site, theserverside.net.
I know they just pointed to your blog, where of course, no one would deny your right to say anything you want. What's great about blogs is that one can disagree and say so in the comments. Cheers!
Posted by: Bertrand Le Roy | January 20, 2004 06:14 PM
Yeah I love comments. It makes blogging much more fun. It's always great to hear the other side.
Thanks.
Posted by: Simon Harris | January 20, 2004 06:23 PM
The closest C# equivalent to IllegalStateException (that I have found) is InvalidOperationException -- from the docs:
"InvalidOperationException is used in cases when the failure to invoke a method is caused by reasons other than invalid arguments."
Posted by: Derek Slager | October 14, 2004 03:52 PM