Friday, March 2, 2007

Symbols in C# 3.0

One of the languages that gets a lot of buzz nowadays is Ruby. Ruby is a dynamic, object-oriented language with a flexible syntax. The language allows you to change an object's structure at run-time by adding new methods. You can also create entirely new code by building a code string and executing it with the eval function.

i = 30
eval("i = 20")


This function alone, which is shared by Python and Javascript, is enough to enable all sorts of advanced metaprogramming. However Ruby has something that the aforementioned languages don't have: symbols. Symbols are just strings that the interpreter checks to ensure they are valid identifier names. This just means that Ruby will throw an error if you attempt to use a symbol that contains spaces, starts with a digit, or is otherwise invalid as an identifier. This is the only method Ruby provides to manipulate code as data. In the words of jazz singer Peggy Lee: "Is that all there is?" Yup.

Despite the simplicity of this feature symbols are still incrementally more useful than strings when combined with metaprogramming techniques like code generation. Typically a method is created which takes an array of symbols and generates code for each of them using eval. The advantage of using a symbol instead of a string is that your generated code is more likely to be syntactically correct.

In the following Ruby example two functions attr_reader and attr_writer are passed an array of symbols and then create accessor and mutator methods for the name, author, and duration fields in the Song class. This is equivalent to creating three properties in .NET.

class Song
attr_reader :name, :artist, :duration
attr_writer :nmae, :artist, :duration
end


Notice the ":" before each argument. That is how you create a symbol in Ruby. The observant reader will notice that in the second function I've made a typo writing ":nmae" instead of ":name". This was intentional because I wanted to point out an important fact that might not be immediately obvious. Specifically that symbols don't necessarily refer to an identifier that exists at compile-time. In fact, there is no compile-time in Ruby. It is an interpreted language. As a result the code above will not trigger an error. Remember: symbols are just strings.

When working in a statically-typed language like C# I find having to put code in a string very annoying because my IDE is powerless to point out typos like the one above. Also, my IDE cannot provide me with intellisense or rename an identifier globally for me. However certain important interfaces in the .NET framework require me to put code into strings. Consider this Customer class that implements the System.ComponentModel.INotifyPropertyChanged interface, enabling it to be data-bound to WinForms controls:

public class Customer : INotifyPropertyChanged {

private string name;
public string Name {


get { return name; }

set {
name = value;
OnPropertyChanged(this, new PropertyChangedEventArgs("Name")); // notice we have to put our property symbol in a string
}

}
protected void OnPropertyChanged(object source, PropertyChangedEventArgs args)

{

if (this.PropertyChanged != null)
this.PropertyChanged(source, args);

}

public event PropertyChangedEventHandler PropertyChanged;

}



In this case it would be really nice to have a symbol-like construct in C#, preferably one checked for correctness by the compiler. Thankfully in C# 3.0 writing a function that will return the string representation of a symbol is trivial. You can replace the applicable line above with:

OnPropertyChanged(this, new PropertyChangedEventArgs (this.GetPropertySymbol(o => o.Name)));

This is just beautiful. Intellisense even kicks in as we type the expression:



Even if we somehow manage to make a typo with intellisense helping us, the program will not even compile. Also we can now do a global rename of the property using refactoring tools.

How does it work? GetPropertySymbol is an extension method that takes an expression tree as its argument. I've blogged about expression trees extensively here and here so I'll assume you know all about them. You do read my blog religously don't you ;-)

Did you notice that we didn't have to specify any type information and yet intellisense still managed to display the list of Customer members? This works because GetPropertySymbol declares that the first argument to the lambda function it is passed is the same type that the extension method is applied to. As a result C# can figure out the type with type inference!! Without further ado, here is the single line function definition:



All I'm doing is grabbing the member expression inside the lambda expression body and returning the name! This function comes in handy when doing data-binding as well:

this.DataBindings.Add (
new Binding(
this.GetPropertySymbol(o => o.Text),
this,
this.GetPropertySymbol(o => o.GameTitle)
)
);

Incidentally I hope no one gets the impression that I'm putting Ruby down. In fact, I really like Ruby. However the most interesting thing to me about the language is not it's oft-touted metaprogramming capabilities but the way it encourages you to write code in continuation-passing style (CPS). This approach can make writing certain types of client-server applications like web applications much more simple conceptually. I advise you to check the language out. Learning programming languages which approach problems in radically different ways than the ones you are used to will definitely make you a better programmer.

28 comments:

Judah Gabriel Himango said...

Very, very cool. I hate having to put code in between strings; refactors break it. We have to do this often in unit tests as well as data layer programming...if symbols could replace this, it would be absolutely brilliant.

I've submitted this article to dotnetkicks.com

Jafar Husain said...

Can you give me an example of how you would use symbols in unit tests? I'm curious because I never found the need.

Thanks

Darius Damalakas said...

Correct me if i understood wrong, but:
is there a way to write
student => LastContract => DateCrated?

I.e. to get the property name via property path?
Student.LastContract.DateCreated?

Jafar Husain said...

The current version doesn't work for nested member expressions. It's trivial to modify it to do so though. Just keep following up the MemberExpressions up the expression tree and appending the names to a string. Then you would be able to do:

this.GetPropertySymbol( o => o.Student.LastContact.DateCreated )
//returns "Student.LastContact.DateCreated"

If you have any questions or problems doing it yourself I'll write the code and post it.

Jafar Husain said...

Oh the heck with it. I just wrote the code and posted it :-). Check it out here.

Judah Gabriel Himango said...

In unit tests, we'll do things like this in mock objects:

// myFooMock is a mock IFoo, which has a Fobnicate method
myFooMock.Expect("Fobnicate");

If I'm understanding you right, we should be able to use symbols to get the name of the Fobnicate method. Is that accurate, Jafar?

Anonymous said...

Actually, if you back your code up just one step, you've got the basics of the "info-of" feature-wish: a way to get MemberInfo without having to jump through all the hoops.

Nice example, by the way.

John Rusk said...

Judah,

The case you mention (symbol for method can be fully implemented right now in C# 2.

See http://dotnet.agilekiwi.com/blog/2007/04/symbols-part-2.html

John Rusk said...

Oopps, that URL didn't come through. I'll try again:

Anonymous said...

Here's another idea: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1487253&SiteID=1&mode=1

which is a refinement of yours (requires hypothetical compiler change though)

Anonymous said...

@Dariusyou are getting "=>" wrong, it is c#'s lambda/anonymous function

In c#: (o => o.Name)
In python: (lambda(o): o.Name)
In javascript: (function(o){return o.Name;})
In (my broken understanding of) ruby: {|o| return o.Name}
#or probably just
{|o| o.Name}


to get what you want do: ( student => student.LastContract.DateCreated)

Your exapmle will return a function that takes an object as parameter and returns another function that takes anothey object as parameter, then returns the value of "DateCreated" in the closure

Judah Gabriel Himango said...

Jafar, I'm aware one can do it with methods right now in C# 2: simply construct a delegate around your function, then call the Name property on the delegate target. But you can't do it with properties, since delegates can't point to properties.

So, I'm looking forward to symbols like this in C# 3. :)

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...

bDuek2 Thanks to author.

Anonymous said...

Nice Article.

Anonymous said...

Thanks to author.

Anonymous said...

actually, that's brilliant. Thank you. I'm going to pass that on to a couple of people.

Anonymous said...

Magnific!

Anonymous said...

actually, that's brilliant. Thank you. I'm going to pass that on to a couple of people.

Anonymous said...

Thanks to author.

Anonymous said...

Please write anything else!

Anonymous said...

AIXweu actually, that's brilliant. Thank you. I'm going to pass that on to a couple of people.

Anonymous said...

Hello all!

Anonymous said...

Interestingly enough, the equivalent VB syntax doesn't work with intellisense:


Public Module SymbolExtensions
<Extension()> _
Public Function GetPropertySymbol(Of T, R)(ByVal obj As T, ByVal expr As Expression(Of Func(Of T, R))) As String
Return CType(CType(expr, System.Linq.Expressions.LambdaExpression).Body, System.Linq.Expressions.MemberExpression).Member.Name
End Function
End Module

Me.GetPropertySymbol(Function(o) o.Name)

But, the compiler does catch it, so that's something.

Anonymous said...

My friends and I like to buy Anarchy credits, because the Anarchy Online credits is very useful to upgrade equipment. Only your equipment becomes better, then you can win this game. In Anarchy gold, you can buy everything you want in this game. Tomorrow will be my birthday, so my friends promise to buy AO credits as gifts. I am so happy. They understand me so well, Anarchy online gold is my favorite.

I like angels gold very much because it is very useful. In fact at first sight I have fallen in love with angels online gold. So no matter how much I have spent to buy angels gold, I never regret. Because of cheap angels online gold, I meet a lot of friends.

Anonymous said...

In preparation for the purchase of a tennis racquetbefore, we must consider your financial ability to bear; On this basis, a further comparison, as far as possible, choose your tennis racket. Now a lot of cheap tennis racquet and more mixed materials, the proportion of mixed-use to control the stiffness of the tennis racquet discount and the shock-absorbing capacity, the more rigid cheap tennis racket, the swing more powerful force; but the relative resilience of the shock-absorbing capacity and discount tennis racket performance of talks on the easier it is for the wrist and elbow injury.
head junior tennis racket
wilson tennis racquet
wilson tennis racket
head tennis racket
babolat tennis racket
Womens Handbags
Cheap Purses
Designer Handbags

Anonymous said...

Burberry polo shirt the steady, solid, so many young girls also love it. Speaking of people of a ralph lauren polo, think it a sign of nobility elegant waving in the horse club.spyder jacket in the cold in your winter activities can be easily.columbia jacket it is expensive, but here you do not need to consider the price of it. the north face jacket one of my favorite money, I do not know how many in this world of its fans.
ed hardy clothing
ed hardy clothes
ed hardy shirts
ed hardy t-shirts
ed hardy sunglasses
ed hardy mens
ed hardy womens
Wholesale Handbags
Cheap Handbags

Anonymous said...

Acer Laptop Batteries
Apple Laptop Batteries
Compaq Laptop Batteries
Dell Laptop Batteries
HP Laptop Batteries
IBM Laptop Batteries
Lenovo Laptop Batteries
Samsung Laptop Batteries
Sony Laptop Batteries
Toshiba Laptop Batteries
ASUS Laptop Batteries
Gateway Laptop Batteries
LG Laptop Batteries
NEC Laptop Batteries
HITACHI Laptop Batteries
Panasonic Laptop Batteries
BenQ Laptop Batteries
Fujitsu Laptop Batteries

About Me

My photo
I'm a software developer who started programming at age 16 and never saw any reason to stop. I'm working on the Presentation Platform Controls team at Microsoft. My primary interests are functional programming, and Rich Internet Applications.