Cannot use a lambda expression as an argument to a dynamically dispatched operation

Since the introduction of the Web Pages framework, ASP.NET has been making use of the dynamic type introduced in C# 4.0. Along with that comes some new error messages which at first glance don't make a lot of sense - mainly because they are unexpected. I have already looked at how dynamics do not support extension methods, and a recent question in the ASP.NET forums illustrated another way in which the dynamic type can catch you out.

The full text of the error message was

Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type 

The code that caused this compilation error looked something like this:

var db = Database.Open("Northwind");
var category = db.QueryValue("SELECT CategoryId FROM Categories WHERE CategoryName = 'Beverages'");
var products = db.Query("SELECT ProductId, ProductName FROM Products WHERE CategoryId = @0", category);
var selectListItems = products.Select(i => new SelectListItem { //error here
    Value = i.ProductId.ToString(), 
    Text = i.ProductName
});

This is illustrative only, so disregard the fact that the two SQL statements can be merged into one.

The error was raised at the point where the questioner attempted to project the results of the second query (variable products) into a new form - an Enumerable<SelectListItem> - in preparation for an HTML DropDown helper. The projection was being attempted through the use of the Enumerable.Select method that takes a delegate represented as a lambda expression. But where is the "dynamically dispatched operation" that doesn't like lambda expressions? The return type from a Database.Query method call is an IEnumerable, not a dynamic type. However, the return type of the Database.QueryValue method is a dynamic. At the moment this isn't clear from the documentation on MSDN, which says that the return type is System.Object. It also inn't clear from WebMatrix, where "IntelliSense" in version 2 is little more than a code completion tool, and doesn't offer any information on compiler errors. However, it is clear from Visual Studio (Express) if you hover over the var keyword:

So how does that affect the products variable? Well, the thing about dynamic is that any operation that it is involved in will be resolved at runtime. In other words, they become dynamic too. And this can be evidenced by hovering over the var keyword relating to the products variable. In other words, the return type of the Database.Query call is no longer an IEnumerable. It's a dynamic:

An interesting thing about dynamic - you can pass one in as an argument to pretty much any method as a parameter. As soon as you do of course, the return type of the method changes to dynamic.

A lambda needs to know the data type of the parameter at compile time. It doesn't like parameters whose type will be resolved at runtime. It needs to be able to infer what type i is in the example above. The advice given in the error message is somewhat typical of .NET error messages that require a ton of research to become clear. However, the solution to the problem is a lot more straightforward. Just as you can pass a dynamic as an argument to any method, you can cast a dynamic to any type. The compiler will happily trust what you are doing and leave it up to the DLR (Dynamic Language Runtime) to complain vigorously if the type you have specified for your dynamic is unnacceptable in the context in which you have tried to use it.

In the case above, you can simply explicitly specify category as an int. Object and string will do just as well in this context, as the value will be passed in to a SQL command as a parameter value - and they are always sent as strings over the wire:

var db = Database.Open("Northwind");
int category = db.QueryValue("SELECT CategoryId FROM Categories WHERE CategoryName = 'Beverages'");
var products = db.Query("SELECT ProductId, ProductName FROM Products WHERE CategoryId = @0", category);
var selectListItems = products.Select(i => new SelectListItem { 
    Value = i.ProductId.ToString(), 
    Text = i.ProductName
});

Or you can opt to force the return type of the Database.Query method to an Enumerable:

var db = Database.Open("Northwind");
var category = db.QueryValue("SELECT CategoryId FROM Categories WHERE CategoryName = 'Beverages'");
IEnumerable<dynamic> products = db.Query("SELECT ProductId, ProductName FROM Products WHERE CategoryId = @0", category);
var selectListItems = products.Select(i => new SelectListItem {
    Value = i.ProductId.ToString(), 
    Text = i.ProductName
});

Either way, the error goes away as the type is now known to the compiler.

So far, I have looked at the WebMatrix data access library, which features a lot of use of the dynamic type. It is also used within the Web Pages framework to provide a storage and access mechanism for the App and Page properties, which allow you to access AppState and PageData entries using dot notation as if they were properties of a class. There are other areas in ASP.NET that make use of dynamics - in particular the ViewBag property that was introduced in MVC 3. This kind of code has already been posted to forums with the same compiler error being reported:

var CountrySelectList = ViewBag.Countries.Select(c => new SelectListItem {
    Value = c.CountryId.ToString(),
    Text = c.CountryName
});

Again, ignoring whether this is fundamentally a good approach, whatever is stored in ViewBag.Countries is a dynamic type. So it needs to be cast to an appropriate type for the operation:

var CountrySelectList = ((IEnumerable<Country>)ViewBag.Countries).Select(c => new SelectListItem {
    Value = c.CountryId.ToString(),
    Text = c.CountryName
});

The dynamic type provides a lot of flexibility and a nice syntax for accessing arbitrary values that might otherwise require dictionaries for storage. But as you have seen, dynamic re-writes some of the rules that you are used to when working with static type checking. So long as you are aware where dynamic is used in Web Pages and MVC, you should be able to avoid the pitfalls.