As you may or may not know know I've been working hard on Silverlight controls for the last few months. I know this blog is covered in a thin layer of virtual dust but I want to say this to the three of you left who haven't dropped me from your RSS readers in disgust: "I'm back baby." No, really. Get ready for a flurry of posts on Silverlight 2 and our upcoming controls. You can read more about them on my boss's blog. Not everything I've been working on has been announced yet (nothing in fact) so until PDC I will have to keep it general rather than specific. However after Tuesday no amount of pleading e-mails will be able to get me to shut up about Silverlight controls. So stay tuned. To whet your appetite I'll tell you a cautionary tale about a developer who found his love of functional programming and Silverlight in direct conflict.
Once upon a time there was a developer who was hired by Microsoft to write Silverlight controls. This developer understood well the benefits of functional programming and had been dutifully using query comprehensions, anonymous types, and closures whenever possible. His code was terse and declarative. Life was good. One day the tester visited the programmer. "Anonymous types are forcing our code coverage down. The compiler generates a GetHashCode function and a ToString function for them and and our test can't cover them." The developer was initially dismissive of the tester's concerns. He saw limited value in covering compiler generated code. The true coverage numbers were much higher he reasoned. Denial. "Anonymous types also account for about 15% of our DLL size." Moments later our developer regained consciousness, dazed and confused, and began to crawl out from the ton of bricks that had fallen on him. This was another problem entirely. He hadn't given a moments thought to executable size. After all, how much code could really be generated by an itty-bitty little anonymous type? var rootNode = new { Node = node, Resources = node.GetResources() };
Turns out...a lot. A class with constructor, two properties, a structural equality overload, a ToString overload, and a GetHashCode overload. The developer groused and complained that the compiler should be able to detect and omit the unused code. Anger.
The developer bargained, telling himself that he would use anonymous types sparingly, only in cases where omitting them would detract from the clarity of the code. Finally he accepted the fact that he couldn't subject users to longer download times for the sake of his high-minded ideals. He swore to never use anonymous types again...in Silverlight.
The moral of the story is it's important to avoid casually using anonymous types in Silverlight projects. This is more difficult that it seems because sometimes you are using them without even knowing. How many anonymous types do you think are generated by the following code?
var types = from type in this.GetType().Assembly.GetTypes() let name = type.FullName orderby name ascending select new { Name = name, Type = type, SuperClass = type.BaseType };
The answer is two. Let statements generate anonymous types. Under the hood an anonymous type was created for the name and type pair in addition to the triple that is actually returned from the query.
The dilemma: How to be a good developer and write stateless, declarative code while respecting your end-users precious time?
1. DON'T use the query syntax.
Many people find query comprehensions very readable, especially when doing joins. The problem is that it's too easy to inadvertently create anonymous types.
2. DO use tuples.
A tuple is an immutable generic class that is basically identical to an anonymous type but without the named properties. You can use it again and again without bloating your assembly. Here is a triple, which is like a tuple but with three properties instead of two. internal struct Triple<T0,T1,T2> { public T0 First { get; private set; } public T1 Second { get; private set; } public T2 Third { get; private set; } public Triple(T0 first, T1 second, T2 third) : this() { First = first; Second = second; Third = third; } }
We also need one last thing, a nice helper class to create them for us. The reason we need a helper class is that constructors don't support type inference. It's certainly no fun typing...
new Triple<string,Type,Type>(name, type, type.BaseType);
..is it. I have a FunctionalProgramming static class I use (which I usually alias to FP):
public static FunctionalProgramming {
public Triple<T0,T1,T2> Triple(T0 first, T1 second, T2 third)
{
return new Triple<T0,T1,T2>(first, second, third);}
}
}
Once you've built yourself a triple and a helper class you're ready to go. Now we can rewrite the code above:
var types =
this
.GetType()
.Assembly
.GetTypes()
.Select(type => FP.Triple
Tuples are used in functional programming to return multiple values and as a way of temporarily grouping related objects. There's no reason why you can't use them in C# in lieu of anonymous types.
And so our story comes to an end. With a little adjustment the developer continued to use Linq in his Silverlight project to create terse, declarative code and lived mostly happily ever after.
13 comments:
It's funny that you care about leaneness of the code and yet you use automatic C# properties for your code.
Each of those property will introduce 2 methods + 1 property definition + 1 field.
You could have used a single readonly field which would have saved you even more data.
Nice post though.
If this cautionary tale ever gets made into a movie, I bet they'll get someone handsome - and brilliant - to play the role of the tester. :)
I do see the problem, but I disagree with suggestions.
As far as I remember, only 'let' introduces invisible anonymus type, and it is easy to avoid let if you know this. So avoiding the query syntax altogether is an overkill.
And tuples are hard to understand, even three lines down from the creating code. Because what exactly is x.First? Not easy to remember.
I would just say 'avoid projections'.
var types = this.GetType().Assembly.GetTypes();
Now you get the full Type instead of the projection, and you haven't lost even a bit of information.
If you really need to do a projection and pass the results around, it is a better idea to create a specific type with well-named auto properties.
Great to have you back!
I had a chance, recently, to use some of the ideas you suggested in the following post:
http://themechanicalbride.blogspot.com/2007/02/do-you-know-linq-fu.html
I have been doing software for a pretty long time. The "LINQ to Code" listing is possibly the most beautiful code I have ever seen.
It was like reading poetry :)
Thanks!
ashmind: You're quite correct that if you're careful with query comprehensions you can avoid generating anonymous types. This advice is really just a precaution.
Your concerns about the readability are very real. I'm personally a huge advocate of pushing as much semantic information into the code as possible. I assure you it pains me to have to relegate this information to a comment. That is the price we have to pay for keeping executable size down though.
Unfortunately avoiding projections is not so easy. They are a key part of functional programming. In the admittedly contrived example I provided there doesn't appear to be a reason to use them. However sometimes you want to process data and project the result into a tuple. It's a good idea in such cases to do as much processing as you can in the stream generation as opposed to the stream traversal (i.e. the foreach statement). The reason for this is that it exposes more computation that can be done in Parallel. Think technologies like PLinq.
I never trusted those anonymous types.
hmm, should i sign this as anonymous?
Post a Comment