November 05, 2005

C# 3.0 Score Card

To make good on my threat, here are a few thoughts on the C# 3.0 features that I find interesting, along with a grade and a few thoughts:

Implicitly typed local variables:    A

I have been asking for this feature in Java for years.

Why do I need to write:

Account a = new Account();
Employee e = (Employee) employees.get("John")

Instead of

a = new Account();
Employee e = employees.get("John")

Can't the compiler figure this out for me?  Of course, it can.

Generics solve the second case, but they add more type inference envy than ever:

List<Map<String, Name> employess = new ArrayList<Map<String, Name>>();

Surely we can do a bit better than that, right?

Well, that's exactly what type inference will bring you.  Combine strong typing with a strict application of the DRY principle and you get the best of both worlds.

Extension methods:    C

This feature lets you add methods to a class outside the definition of that class.  The syntax is a bit peculiar:  you add this to the first parameter of your method:

public static int ToInt32(this string s) {
  return Int32.Parse(s);
}

I prefer Ruby's syntax for this:

def string.toInt32()
  ...
end

Note that this feature doesn't break encapsulation:  extension methods don't have access to private fields or methods of the class they extend. 

As for the feature itself, I have to admit it frightens me somewhat (both in Ruby and C# 3.0) because it becomes really hard to know exactly the contract that classes abide by, and also where the extra definitions are coming from.  I have to admit that over the years, I have grown quite fond of Java's rigid "one public class per file" rule and I'm afraid that extension methods might resurrect the C++ header nightmares that haunted C++ programmers for decades. 

On the other hand, this kind of extensibility opens the door for some very interesting tricks, such as the ones used by Ruby on Rails to exercise its magic (more on that in another post).

Time will tell.

Lambda Expressions:    B+

I have no doubt that functional programmers are screaming in frustration to hear the term "lambda expression" hijacked to mean "closure".  Let's put the intellectual debate aside and observe that closures are extremely useful.  C# already had a head start over Java with delegates and is now pulling further ahead with closures.  Let's hope Java will follow suit.  The C# syntax is fairly straightforward:

(int x) => x + 1

defines a lambda expression that accepts an integer parameter and returns that parameter incremented by one.  You can then pass this lambda expression around as real object.  I am quite looking forward to using this feature more.

Query Expressions:    C-

This feature is also referred to as LINQ for "Language Integrated Query".  The idea is to allow a certain subset of relational language inside C#:

customers.
  Where(c => c.City == "London").
  SelectMany(c =>
      c.Orders.
      Where(o => o.OrderDate.Year == 2005).
      Select(o => new { c.Name, o.OrderID, o.Total })
  )

If we have learned anything from Java's struggle with object-relational mappers, it's that embedding SQL in your code is usually a bad idea, so I'm having a hard time seeing the point in furthering the practice by making it a syntactic part of it.  I am predicting a very grim future for this feature.

Object Initializers:    B+

This feature helps reduce the clutter of your classes by waiving the declaration of simple constructors:

public class Point
{
  int x; int y;

  public int X { get { return x; } set { x = value; } }
  public int Y { get { return y; } set { y = value; } }
}

Point p = new Point { X = 0, Y = 1 };

In this example, Point declares two properties but does not include any constructor that accepts either of these properties  Yet it is possible to create a Point object and to initialize these properties in one statement.  Quite a relief from the verbosity of this.name = name that plagues Java.  Notice also how well this feature plays out with C# properties (another top priority on my Java wish list).

Conclusion

There are so many new features in C# 3.0 that statistically, you are guaranteed to hate at least half of them.  Interestingly, feature excess has certainly not dampened the popularity of C++, quite the contrary, so if Microsoft can guarantee that C# 3.0 will be completely backward compatible with C# 2.0, I am predicting that it will have a great future.   And a lot of detractors.

 

Posted by cedric at November 5, 2005 03:25 PM
Comments

A couple of notes:

a) Lambda expressions can be converted to Expression trees. That's one of the most powerful C# 3.0 features. Check http://www.interact-sw.co.uk/iangblog/2005/09/30/expressiontrees

b) LINQ is not only for relational data, which is probably the least interesting case, but for objects and XML, or whatever structure you think is worth querying against. On the other hand, having a simple o/r mapper in the platform is a good thing for simple cases (think ActiveRecord in RoR (I'm not comparing the implementations ;)).

Posted by: Andres Aguiar at November 5, 2005 05:27 PM

Hey, how about this in Java? :-)

Point a = new Point(){{ x = 5; j = 6; }};

public class Point {
protected int x;
protected int j;
}

Bob Lee actually used this trick for closures too...

Posted by: eu at November 5, 2005 05:28 PM

Ah, nice trick, Eugene! It doesn't look bad at all, indeed.

--
Cedric

Posted by: Cedric at November 5, 2005 05:32 PM

The class extension thing is actually really good if you look at the details of how it's implemented. It's actually better than the Ruby equivalent (except for the quirky syntax) because it is entirely safe.

The way it works is that you include extensions to a class in a lexical scope only. So you say: "In this class I want the ToInt32 to be added to the Integer class." This doesn't affect any behaviour in any other classes.

Granted, it allows for much less magic than the Ruby one but it's definitely an interesting innovation. Time will tell how useful it actually is.

Posted by: Jon Tirsen at November 5, 2005 09:47 PM

I was also going to point out you'd missed the real power of C# lambda expressions - ie the compiler can construct an AST for use at runtime from a compile time expression. However Andres got there first!

Posted by: RichB at November 6, 2005 01:43 AM

I may not get your point, but what if you have:

public interface I{
...
};
public class A implements I{
...}

and you really want to work in terms of interfaces:

I i = new A();

In other words, what if your Account() implements an interface and you want to work with that type, instead of the concrete Account() ?

Posted by: lp at November 6, 2005 03:45 AM

lp,

The answer is: you be careful. :) Note that you don't have to use the infered type - it is optional, though I must admit I think people would be lazy and do it.

One alternative is to use factory methods more; because the factory method would presumably return the interface, there would be no possiblity of inferring the concrete type.

The other is to be relaxed about it. This attitude works in the more dynamic languages such as Ruby.

Posted by: Robert at November 6, 2005 05:11 AM

i agree with lp. Your A score should be "C". All the modern IDE's are making this casting automatically anyway.

Also, Especially when classes uses interfaces, a line like this:

List list = new ArrayList()

is self documenting that list is actually a List type. this should be the preffered way.

if you give that weapon to the lazy programmer, they will use it everywherem, the readability will be less IMHO. Sure there will be less clutter on the code but does it worth?

Posted by: aaa at November 6, 2005 02:02 PM

In the case of
"Implicitly typed local variables"
i hope Java stays as is. Most explicit is best imho.

I hate this "I want to write less code" attitude. Itīs not a good basis for language design. Look at some rotten languages (PHP, VB e.g), which are sooo nice regarding LoC.

I think a language is good if it is consistent. If that means, consistently strict, so be it.

Thatīs why I love Java for NOT having operator overloading e.g.

Posted by: Patrick at November 6, 2005 02:02 PM

i do not know why my previous post did not appear.

i actually agree with "lp". There advantage of writing those declaration verbose is "self documenting".

Blah s = new Foo();

when i read that code, i understand that Foo is implementing Blah, or is a Blah, and possibly i will be able to read to code that in mind. And again probably there was a reason to use it that way.
If you use the proposed way (which will definitely used-abused by the lazy developer) this knownledge will be more dificult to acquire.

As for casting, most IDE's are creating caast operations with basic short cuts. again i do not think just for creating less lettes on the code adding this easily abused things to language makes our life better. that deserves "C" instead of "A" IMHO.

Posted by: afsina at November 6, 2005 02:16 PM

Lambda Expressions "Let's hope Java will follow suit."

In what way? As core language feature? Do you see any signs in this direction? Mustang will probably miss it. Dolphin maybe?

Regards,
Horia

Posted by: Horia at November 7, 2005 12:53 AM

I also hope that the Java way of implicitly typed local variables stays the same. C# way might be less typing, but it is also less self-documenting. Writing self-documenting code is a lot more important!

List list = new ArrayList()

is a lot more telling than

list = new ArrayList();

Especially when you're using types you're not aware of...

Posted by: Stas Kubasek at November 7, 2005 06:28 AM

Have to admit, that as Cedric points out, using Generics really exacerbates the pain of not having implicit casting.

WRT coding to interfaces, I agree there's some value there, although this kind of structure doesn't bother me too much:

public List<Customer> getCustomers()
{
list = new ArrayList<Customer>();
// ... do stuff ...
return list;
}

Yes, the variable inside the method isn't list-typed, but it's just as easy to change the implementation as it was before and the type isn't exposed.

I really do get sick of typing Map<Customer,Long> map = new HashMap<Customer,Long>(); and the like.

Posted by: Geoffrey Wiseman at November 7, 2005 08:02 AM

Geoffrey, usually you only need to write

Map map = new HashMap();

So it is not a big deal.

Posted by: afsina at November 7, 2005 01:27 PM

sorry, the comment eat the symbols i think :) i mean you only need to add those generics sybols to the first part of the declaration..

Posted by: afsina at November 7, 2005 01:29 PM

Afsina, you can type Map map = new HashMap(); or Map map = new HashMap(), but your IDE will probably complain and nag you until you fix it (optionally with a SupressWarnings annotation)

Posted by: Dan at November 7, 2005 05:15 PM

If someone from Sun is reading this... Please don't add these things to the language this is just ridiculous!
All of these ideas make the language more complicated which is why most of us escaped from C++ in the first place! It might not be immediately obvious why but all these generic proponents are prettey pissed right now when they have to write something like Map> map; which doesn't solve anything. The same is true for all of these.

Posted by: aaaaa at November 8, 2005 03:18 AM

Regarding the 'class extension' mechanism: it is a very nice way to package things like Visitor design patterns for example. You can really make one package with the nodes that you want your visitor to work with, and one with the actua visitor class *and the visit methods for the visitor*. You're also right that it opens up other problems of course. See the language Skala for a module mechanism that supports this, or Classbox/J for a Java extension that accomplishes a more advanced form of class extensions that eliminate some of the problems. Send me a mail for more info if you want.

PS: Note that Ruby got this from Smalltalk, where class extensions have been used for about 30 years now :-)

Posted by: Roel Wuyts at November 8, 2005 05:01 AM

About 'implicitly typed local variables':
If you got to correctly write for example a short method, where local variables will have a small set of statements for its scope, than the problem of not having the _documentation_ about the type of your local variable will not be a real pain.

You just need to look at the var instantiation statement, without using pageup of pagedown keys ;)

I think this is very handy for simple helper objects like primitive wrappers, simpler classes like awt.Point or StringTokenizers/Builders, etc.

And *may* be a real pain for more sophisticated objects that could make use of polymorphism, like Business Objects or generic containers like Collections or Maps. But Again, with your talking about very short scoped local vars, a refactoring will solve any problem easily.

Posted by: Bruno Patini Furtado at November 10, 2005 04:37 AM

If you like some of the stuff in C# 3.0 and 2.0, you can use them now in an even easier syntax:
http://boo.codehaus.org/
Also http://www.nemerle.org/

And of course there is groovy on the jvm: http://groovy.codehaus.org/

I would love a language with type inference and closures and optional dynamic typing that could compile to either .net/mono or the jvm. Right now boo has a compiler framework that might be the easiest to move in that direction although it is still far off.

Posted by: Doug at November 19, 2005 07:37 AM
Post a comment






Remember personal info?