Ruby-Like Expressiveness in C# 3.0

0 comments suggest edit

UPDATE: Looks like Ian Cooper had posted pretty much the same code in the comments to Scott’s blog post. I hadn’t noticed it. He didn’t have a chance to compile it, so consider this post a validation of your example Ian! :)

Scott Hanselman recently wrote a post about how Ruby has tits or is the tits or something like that. I agree with much of it. Ruby is in many respects a nice language to use if you think in Ruby.

One of the comparisons of the syntactic sugar Scott showed was this:

Java:

new Date(new Date().getTime() - 20 * 60 * 1000);

Ruby:

20.minutes.ago

That is indeed nice. But I was on the phone with Rob Conery talking about this when it occurred to me that we’ll be able to do this with C# 3.0 extension methods. That link there is a blog post by Scott Guthrie talking about this feature.

Not having any time to install Orcas and try it out, I asked Rob Conery to be my code monkey and try this out. So we fired up GoToMeeting and started pair programming. Here is what we came up with:

public static class Extenders
{
  public static DateTime Ago(this TimeSpan val)
  {
    return DateTime.Now.Subtract(val);
  }

  public static TimeSpan Minutes(this int val)
  {
    return new TimeSpan(0, val, 0);
  }
}

Now we can write a simple console program to test this out.

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine(20.Minutes().Ago());
    Console.ReadLine();
  }
}

And it worked!

So that’s very close to the Ruby syntax and not too shabby. It would be even cleaner if we could create extension properties, but our first attempt didn’t seem to work and we ran out of time (Rob actually thinks eating lunch is important).

Found out from ScottGu that Extension Properties aren’t part of the language yet, but are being considered as a possibility in the future.

So now add this to the comparison:

C# 3.0

20.Minutes().Ago();

Just one of the many cool new language features coming soon.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

33 responses

  1. Avatar for Rob Conery
    Rob Conery May 24th, 2007

    Hey how come you didn't show the FizzBuzz approach we wrote up using Extension Methods! Dude that was the best part!
    I take that back - correcting your mistakes was the best part...!

  2. Avatar for crimar
    crimar May 24th, 2007

    Big deal, so we can turn a good object oriented language where it is clear where all the code is located into a difficult to understands scripting language syntax. Whats the value in that. And if something is wrong with the code, how much running aroundtrying to find where the modified code is.
    I seem to remember one of the qualities of good object oriented design was encapsulating the functionality applicable to an object.
    The code doesn't seem to make it clear, but can this extension be used anywhere? Like "Hello World!".Minutes.Ago()? If so, how can I ever tell where it is valid, what errors might be thrown, how to handle it?

  3. Avatar for Ryan
    Ryan May 24th, 2007

    A *very* similar example was posted in the comments by Ian Cooper on Hanselman's post.

  4. Avatar for Haacked
    Haacked May 24th, 2007

    @Ryan - Doh! I hadn't seen Ian's comment.
    Ian said:
    > not compiled so treat as expression of intent instead of working code
    Hey Ian! We compiled it. It works! :)
    @crimar - running around? How about right click and "Go To Definition"? I mean, you get the same problem with good object oriented design. For example:

    ISomething something = Factory.Create("GiveItToMe");
    something.DoSomething();

    Now given the above code sample, how do you find out what DoSomething does? If you go to definition, you get to:

    public interface ISomething
    {
    void DoSomething();
    }

    You can only tell what is happening at runtime. With an extension method, you can tell at design time.
    This particular extension method cannot be applied to a string. you can tell by the type of the *this* argument.
    So the Minutes() method only applies to instances of type Int32 (or int) and the Ago() method only applies to instances of type TimeSpan.

  5. Avatar for Kevin Dente
    Kevin Dente May 24th, 2007

    >It would be even cleaner if we could create
    >extension properties,
    From what I've read, there's no support for extension properties, only methods, in Orcas. Don't know if that's final final, but knowing Microsoft, it probably is at this point.

  6. Avatar for Haacked
    Haacked May 24th, 2007

    @Kevin - Thanks Kevin! I was posting my 2nd update to this post as you were typing that comment. ;)

  7. Avatar for Alex
    Alex May 24th, 2007

    Phil,
    I wrote about this exact thing a while back, after being inspired by Derek Slager's Channelling Ruby Post
    Extensions methods are cool, extension properties would be even better, but the best thing would be to have generalized interfaces
    Cool stuff.
    Alex
    BTW I only just noticed your live comments preview thingie... now that is cool too!

  8. Avatar for Don M
    Don M May 24th, 2007

    I must be an old curmudgeon. I really don't think this is all that bad:
    DateTime.Now.Subtract(TimeSpan.FromMinutes(20));

  9. Avatar for Haacked
    Haacked May 24th, 2007

    @Don - well it's not *that* bad, but wouldn't you say that:
    20.Minutes.Ago is more readable.
    For someone with a lot of experience with C#, it might be hard to be impartial. You probably read DateTime.Now.Subtract(TimeSpan.FromMinutes(20)); like it's the Sunday funnies. It's easy to decipher.
    But for someone new to the language, something like this:
    20.Minutes().Ago()
    ... is quicker to comprehend the intent of the author.

  10. Avatar for Cam Soper
    Cam Soper May 24th, 2007
    ...Ruby has tits or is the tits or something like that.


    Suddenly, Ruby has my complete and total attention. ;)

  11. Avatar for Scott
    Scott May 24th, 2007

    "20.Minutes().Ago()"
    THAT is still ugly. why does C# require parens on methods with no arguments?
    " where it is clear where all the code is located "
    That boat sailed with the introduction of partial classes in 2.0. Now if you want to, you can put every method that returns an int or a DateTime into a .cs file named "ReturnsDatetime.cs".
    "DateTime.Now.Subtract(TimeSpan.FromMinutes(20));"
    Yeah, that's great if Yoda you are. ;)

  12. Avatar for Haacked
    Haacked May 24th, 2007

    > Yeah, that's great if Yoda you are. ;)
    ROTFL!
    > why does C# require parens on methods with no arguments?
    Not my fault. I'll make it prettier just for you as soon as we get them Extension Properties.

  13. Avatar for Bil Simser
    Bil Simser May 24th, 2007

    This is exactly why *I* believe C# extensions are going to be the tits, and much bigger and better ones than Ruby is (or will be). As much as I really want to learn another language and another syntax, and as much as Ruby is more of a naturally expressive language it's cryptic when I look at peoples code. I wonder what is Ruby and what is code. Okay, so extensions is going to do similar things here but I personally feel it's going to be great to say link in a "natural extension" assembly that gives me this type of syntax (assuming I wanted it in the first place) and give me the compile time syntax checking I want.

  14. Avatar for StefanVE
    StefanVE May 24th, 2007

    It can be done. But if the goal is enhancing readability, this can also be achieved with a simple property or function in our current version.
    I am not experienced enough in ruby yet to understand this , but is the point the ruby guys are trying to make, that this kind of syntax is more natural, more out of the box in ruby?
    While you have todo a little bit of work with extenders (or if you want to stick to the analogy, fumbling with the bra to show the tits) in C#.

  15. Avatar for Joe Brinkman
    Joe Brinkman May 24th, 2007

    @Stefan - You have to do the same work in Ruby. Extensions are not free. Someone has to write them. In Ruby, the community has had time to create some "standard" libraries, and I suspect that the same will happen in the .Net space.
    Also, re-read the original problem. This cannot be solved in the existing framework. 20 is an integer and there is no way in C# 2.0 to add any methods or properties to the integer class. In C# 3.0, you can add extension methods to ANY class, whether you have the source code or not. So if I need an additional method that works on strings FileInfo objects, I am free to add it without creating a new subclass.

  16. Avatar for J. Irvine
    J. Irvine May 24th, 2007

    20.minutes.Ago might seems more readable than DateTime.Now.Subtract(TimeSpan.FromMinutes(20)); at first glance, but only if you have a good working knowledge of English. What happens when the developer maintaining your code two years from now only speaks, say, German? The "hard to read" code is part of the framework, so the German guy will have learned how to use it. The "easy to read" code just become a major impediment to his understanding of what your code it trying to do.

  17. Avatar for The Other Steve
    The Other Steve May 24th, 2007

    Ok, someone explain to me what is wrong with wrapping "DateTime.Now.Subtract(TimeSpan.FromMinutes(20));" into a function which simply reads...
    MinutesAgo(20);
    You complain about how hard some line is to read, then toss together some special functions in Ruby to make it easier, but you don't do the same thing for the Java or C# functions. Huh?
    If I wanted a language that read like a english sentence I would have learned fracking COBOL.
    And back in my day we used to write Assembler, and any of these is simpler than trying to add the date using that! And we did it while walking up hill all the way!

  18. Avatar for Scott
    Scott May 24th, 2007

    "In C# 3.0, you can add extension methods to ANY class"
    Even value types?
    Does this mean strings are mutable in C# 3.0?

  19. Avatar for Haacked
    Haacked May 24th, 2007

    @Scott - No, strings are not mutable. Yes, ANY class because extensions can automatically box and unbox.
    @The Other Steve - Convenience. When you type 20. and intellisense tells you "Minutes()" is one of the options. Cool. Also, the composition is cool. What if you want a timespan of 20 minutes. 20.Minutes()
    @J.Irvine - Yet DateTime.Now.Subtract(TimeSpan.FromMinutes(20)); is somehow more comprehendible to Germans? Please explain.
    Scott Hanselman actually addresses this point in his post. There's no such thing as a universal programming language that will work for all languages.
    All the keywords of the most widely used languages are in english and flow left to right. Ruby was invented by a Japanese gentleman who realized that and did not deviate from that convention.

  20. Avatar for Cam Soper
    Cam Soper May 25th, 2007

    Please excuse my earlier comment. I was having a juvenile moment. :)
    @Haacked - I think what J.Irvine was implying is that DateTime.Now.Subtract(TimeSpan.FromMinutes(20)); follows a common convention across languages, a hierarchical OOP syntax that makes sense regardless of your native tongue, while 20.minutes.ago seems to pander to the English speaker, the English syntax; e.g., the adjective "ago" applying to "minutes." I'm not saying I agree, but I think that's what he was saying.
    I've got to be honest, while I'm 100% with Scott H. on "sharpening the saw," I'm still not sold on Ruby, even with all the Watir scripts I've written. I don't know if I'm hardwired for things like DateTime.Now.Subtract(TimeSpan.FromMinutes(20)), but that OOP hierarchy of properties and methods speaks clearly to me, enough so that I really almost derive some kind of satisfaction out of writing it.
    Plus, it's very obvious to me what I'm looking at: DateTime (structure/value type) .Now (static property) .Subtract (method) (TimeSpan (structure/value type) .FromMinutes (static method) (20 (integer)). I look at 20.minutes.ago, and I have no clue what I'm looking at. I realize that's the essence of the appeal, but I'm not so sure I'm comfortable with it.

  21. Avatar for Miguel de Icaza
    Miguel de Icaza May 25th, 2007

    I like this very much.
    Over the years there have been other small things that could have been done to C# to make it for more "scripty".
    It might be a good idea to setup a public MIT/X11 licensed library (that anyone can use/copy/paste, no strings attached) that tracks some of these extension methods.
    For example, for the longest time I have wanted to be able to write:
    string x = "hello" [1,2]
    Miguel.

  22. Avatar for Rob Conery
    Rob Conery May 25th, 2007

    @The Other Steve:
    Check out our latest checking for SubSonic. Working on this post with Phil inspired me to create a "Sugary" set of of helper methods in SubSonic (which Phil said we should call "SubSonic.Sugar".
    In there are methods like (Dates.MinutesAgo(20), Dates.HoursFromNow(10), Numbers.IsNumeric("12"), Strings.Strip("This long string","long"),Strings.RemoveLast("long string with comma,",1) etc.
    I know it's sorta silly, but hey, if using these saves some time and makes your intent a little clearer - hurrah!
    The idea here, especially with Ruby, is the concept of intent. YES, we all know C# but in general you can't read C# the same way you can with Ruby:


    5.times do {print 'I love shrimp'}


    or


    exit unless "restaurant".include? "aura"


    I stole these examples from Why's Poignant Guide to Ruby - something I've read for fun over 10 times. Do yourself a favor and read it - it takes maybe 2-3 hours and is one of a kind.
    http://poignantguide.net/ru...
    Finally - "back in your day"? That sort of paints a picture I'm not sure you want :).

  23. Avatar for The Other Steve
    The Other Steve May 25th, 2007

    If Ruby is the future, I can be glad that i spent my early formative years working with UCSD Pascal and MS-BASIC.

  24. Avatar for The Other Steve
    The Other Steve May 25th, 2007

    Rob - Yeah, I'm ornery. I'll grab the latest from Subsonic. I have a project I started working on in my spare time, and I intend to use it.
    Anyway, why not this?

    PERFORM 5 TIMES
    DISPLAY "I love shrimp"
    END-PERFORM.

    Easy to read, no?

  25. Avatar for Scott
    Scott May 25th, 2007

    "No, strings are not mutable."
    Interesting. Something to take into account when writing extension methods that take strings.
    If you use an extension method on a string, it HAS to return a new string. What happens to the reference to the old string? When does it fall out of scope?
    I think the answer is, "it depends on the code and what references to it are still being held"
    Why's guide rocks. But compare
    5.times do {print 'I love shrimp'}
    with
    print.5.times("I love shrimp")
    Which really reads more like english?

  26. Avatar for count0
    count0 May 25th, 2007

    Great. But...if Ruby-Like syntax is so cool, why we don't use Ruby directly?

  27. Avatar for Haacked
    Haacked May 25th, 2007

    @Scott, but you have the same issue with proper methods of the string class.
    For example:

    string s = "upper";
    s.ToUpper();
    Console.WriteLine(s);

    Will print "upper" because strings are immutable. The proper thing to do is this:

    string s = "upper";
    s = s.ToUpper();
    Console.WriteLine(s);

    Extension methods are no different here and introduce nothing new with regards to strings. What happens to the old string? It gets garbage collected unless it happens to be interned, but that's another story. ;)
    @count0 - Well some of us do! :) And with IronRuby on the horizon, it'll be easy to use it in a future .NET project.

  28. Avatar for you've been HAACKED
    you've been HAACKED May 28th, 2007

    The Only Universal Language In Software Is English

  29. Avatar for Community Blogs
    Community Blogs May 28th, 2007

    In a recent post , I compared the expressiveness of the Ruby style of writing code to the current C#

  30. Avatar for Kenji HIRANABE
    Kenji HIRANABE May 29th, 2007

    Matz(The designer of Ruby), Kakutani, and I talked in Japanese about the history of Ruby, Java and other OO languages in Japanese.
    The video (with super-inposed English telop) is here. This is the first release of six separate videos, and the vol.2 will include discussion of Ruby's flexibility inherited from Lisp.
    http://jude-users.com/en/mo...

  31. Avatar for Rob Conery
    Rob Conery May 30th, 2007

    SubSonic Sugar

  32. Avatar for Ian Cooper
    Ian Cooper June 1st, 2007

    To be honest I seem to recall Don Box similarly showinf how C# 3.0 had much Ruby goodness, so I can't really claim to have suddenly thought about it, only to have been thinking about if for a while.
    The jury is a little out on 'fluent interfaces', for this is what is really at issue here, and how maintainable they are.
    However I hope to be open-minded as to the possibilities; I get the impression some C# developers are pretty closed to usage of some C# 3.0 language features such as var, lambda expressions etc.

  33. Avatar for billie
    billie July 13th, 2007

    These really break the OO clarity that c# had.