Lambda Abuse: The MvcContrib Hash
December 2, 2009 – 21:44
There’s recently been a lot of discussion on this post on StackOverflow about the use of lambda expressions to build dictionaries as part of the MvcContrib grid.
The Grid API allows you to define HTML attributes for a table by chaining a call to the ‘Attributes’ method as part of a grid definition:
<%= Html.Grid(Model.People).Columns(column => { column.For(x => x.Id); column.For(x => x.Name); column.For(x => x.DateOfBirth); }) .Attributes(style => "width:100%", cellpadding => 0) %>
This makes use of the MvcContrib.Hash class to construct an IDictionary from lambda expressions. So the following Hash definiton…:
var hash = new Hash(foo => "bar", baz => "blah");
…is the equivalent of:
var hash = new Dictionary<string, object> { { "foo", "bar" }, { "baz", "blah" } };
Internally, this is achieved by using a technique I first saw on Alex Henderson’s blog. By taking an array of delegates, it is possible to capture the compile-time variable names used by the lambda expressions and use them as keys for the dictionary.
The responses to this approach have been quite polarized. There has been lots of positive feedback on twitter…
“@JeremySkinner I like it, no problem for me, infact I’ll apply this technique where ever I can from now one forward
” (from Mark Nijhof)
“@JeremySkinner the lambda abuse is awesome, very ruby symbols like, and pushing the language envelope.” (from Adam Tybor)
…and also in the blogosphere (eg here and here) as well as on the StackOverflow post.
There has also been a lot of negative feedback:
“I hardly ever came across this kind of usage. I think it’s inappropriate”
“I find that odd not so much because of the name, but because the lambda is unnecessary; it could use an anonymous-type and be more flexible”
“This is horrible on more than one level. And no, this is nothing like Ruby its an abuse of C# and .Net. ”
“It’s counter-intuitive, there is no way of just looking at the code to figure out what it does.”
Interestingly, some of the most negative comments were from Eric Lippert on the C# compiler team:
“This is horrid in so many ways.”
“I just asked Anders (and the rest of the design team) what they thought. Let’s just say the results would not be printable in a family-friendly newspaper”
I thought Eric’s response was possibly a little harsh, but wanted to try and address and explain some of the issues here:
Issue #1: This is not interop friendly!
One of the issues raised was that this is not interop friendly. If, for example, you wanted to build a grid using F# (or possibly another CLR language) then this approach would not work as it is dependent on the C# compiler. This is a good point and is very true.
The thing I want to point out here is that the syntax is completely optional. This particular overload for the ‘Attributes’ method on the grid is an extension method for convenience when using C#. The main implementation for this method takes an IDictionary. If you don’t like the abusive-lambda approach (or you can’t use it from another language) then you can use the ‘regular’ overload instead:
.Attributes(new Dictionary<string, object> { { "style", "width:100%" }, { "cellpadding", 0 } })
The abusive-lambdas are merely a preference – I happen to like it as it saves me some keystrokes. If you don’t like it, don’t use it.
Issue #2: This is unintuitive and not intellisense-friendly
From a readability perspective, I do not agree that this is unintuitive. If you saw “Attributes(style => “width:100%”, cellpadding => 0)” in a grid definition then I think it’s pretty obvious what’s going on. It even looks like HTML attributes (kinda).
From an Intellisense perspective, I completely agree. If you see the following method signature, it is not immediately obvious what’s going on:
public static IGridWithOptions<T> Attributes<T>(this IGridWithOptions<T> grid, params Func<object, object>[] hash)
But hold on a second…let’s compare this to the approach that the ASP.NET MVC Framework uses for all its built-in HTML helpers:
public static MvcHtmlString TextBox(this HtmlHelper htmlHelper, string name, object value, object htmlAttributes)
Notice that “object htmlAttributes” at the end? That allows you to specify an anonymous type containing HTML attributes:
<%= Html.TextBox("foo", "bar", new{ style = "width:100%", @class="foo" })
This is just as opaque as using abusive lambdas. In fact, I’d aruge that it’s *more* opaque because the htmlAttributes parameter is defined as type object. This means you could accidentally pass *anything* in this parameter (eg a string) and the compiler would not complain. I have done this on many occasions and I find it very frustrating. At least with a parameter of type Func<object, object>[] you can’t accidentally pass in a string. (Much like the MvcContrib approach, ASP.NET MVC’s helpers also have overloads that take an IDictionary if you prefer).
Issue #3: You can achieve the same result with an anonymous type:
I don’t believe that the Hash is really any worse than the anonymous type. In fact, in some ways I think the use of an anonymous type is worse (see my point above regarding opacity)
Issue #4: Performance implications!
There seems to be some misunderstanding around the use of the lambda syntax. On the one hand you can use a lambda to define an expression tree which can then be parsed (this is how LINQ to SQL works – the expression tree is then converted to SQL). These expressions can then be compiled to a delegate and invoked which is an expensive operation. However, lambdas can also be used for anonymous methods. This is essentially syntactic sugar over a standard .NET delegate and is an improvement over C# 2’s anonymous delegate syntax.
The MvcContrib uses the latter approach to do its work. This way, there is no overhead for compiling the expressions. In fact, there is very little overhead versus using the standard Dictionary.Add method. Alex Henderson talks more about the different approaches for doing this on his blog, but these were the results of his benchmark for 10000 calls:
- Using Dictionary.Add: 10.0144ms
- Using lambdas with expression compilation: 9713.968ms (240.3456ms with constants)
- Using lambdas as delegates: 30.0432ms
Issue #5: Why not use method chaining?
One suggestion was to chain multiple calls to ‘Attribute’:
.Attribute("style", "width:100%").Attribute("cellpadding", "0")
This definitely works, but the whole reason why I decided to use the Hash was because I want to mimimize the amount of typing (my fingers tire easily) and making multiple calls to ‘Attribute’ is rather verbose.
And finally…
In the end, this is really just down to personal preference:
- I do not like C#’s dictionary initializers – I think they’re too verbose
- I do not like the use of anonymous types as dictionaries as they require you to take parameters of type “object”
The Abusive Lambda Pattern (I think that’s what I’ll call this) is just something I happen to like, but if you don’t like it then you certainly don’t have to use it.
Perhaps Eric and the C# team would like to give us a more succinct collection initializer for Dictionaries in C#5?
7 Responses
My problem with Lamdas used in this way is that it relies on whitespace and formatting to make it readable.
This whitespace and formatting is generally locked in the head of the author.
It can also quickly become unreadable in any example that is not simple and where nesting is involved.
I don’t think the problem has anything to do with whitespace and formatting. If a developer doesn’t care at least about how things are formatted, then nothing is going to be readable.
Also nesting is bad and hard to read anyways and should be limited.
[...] Lambda Abuse: The MvcContrib Hash – Jeremy Skinner follows up on the recent discussions on the use of lambda expressions in ASP.NET MVC Contrib for building dictionary like structures, explaining the technique used, and answering some of the criticisms of that method [...]
Any language that requires “abuses” to improve it’s readability deserves to be abused.
I don’t think this is that much more of an abuse than reflecting on anonymous types is for defining attributes. That said, I don’t like it because it’s inconsistent with the established way of working in MVC.
If, for example, you already have some code that constructs the anonymous types you need to pass into the Html helper methods you can’t use them in this Contrib code. Bad.
Also, you’re defining a new lambda for every attribute value you use versus being able to reuse existing anonymous types for different values if you do it the other way. What happens when the Attributes call is inside a loop which changes the attribute values – I’d guess a new lambda definition uses up a lot more memory than a new instance of an anonymous type.
I guess I am not surprised that language purists would use an emotionally loaded term like “absuse” to describe this. “You want to do /what/ with my language!?”
IMO, this technique is better than others suggested:
1. Anonymous type parameter. This is pure horror in terms of discoverability and compile time safety. I would go to all lengths to avoid write a method with a parameter of type object. The extra language cruft is another strike against it in terms of readability.
2. Provide only the “regular” overload. Discoverability is no better than the lambda syntax. As far as readaiblity, there is none. My eyes burn trying to understand what this beauty is trying to tell me. But hey, thanks for providing it so that I’m covered the next time I write HTML views in F#.
3. Method chaining. Discoverability is certainly better, but readability is worse. I refer you to Bob Martin’s discussion of the shortcomings of dyadic methods in his book Clean Code.
4. Provide a rich intention revealing API (e.g., .Attributes.Style.Width.Percent = 100;). This would surely be cool; however, does the work justify the benefit? We would have to embody much of the HTML spec in our framework. The Attributes method is really an extension point anyway. That is, you would use infrequently when you need a high degree of control.
In the interest of full disclosure I should mention that I used this same technique for specifying attributes as well as styles in MvcContrib.FluentHtml. I suppose it’s just an accident that the complaint was issued against the Grid helper first.
[...] Interesting reading over on StackOverflow regards Lambda [...]