This is a third companion post to Scott Guthrie’s ASP.NET MVC 2: Strongly Typed Html Helpers.

Today instead of reading code, I’d thought I’d write some. I threw together a class which does the analysis performed by the FastTrack class in ASP.NET MVC2. To recap – the FastTrack class:

  • decides if an expression can be “fast tracked”
  • if it can – builds, compiles and caches it
  • returns the cached expression in future calls

This code demonstrates the bit that does the deciding, and gives us something to play around with.

using System;
using System.Linq.Expressions;

namespace MvcHarness
{
  public class FastTrackAnalyser<TModel>
  {
    public void Analyse<TValue>(Expression<Func<TModel, TValue>> lambda)
    {
      ParameterExpression modelParameter = lambda.Parameters[0];
      Expression body = lambda.Body;

      if (modelParameter == body)
      {
        Console.WriteLine("{0} is a function returning its own argument", lambda);
        return;
      }

      ConstantExpression constantExpression = body as ConstantExpression;
      if (constantExpression != null)
      {
        Console.WriteLine("{0} is a function returning a constant value", lambda);
        return;
      }

      MemberExpression memberExpression = body as MemberExpression;
      if (memberExpression != null)
      {
        if (memberExpression.Expression == null)
        {
          Console.WriteLine("{0} is a function returning a static member of either the model or another type", lambda);
          return;
        }

        if (memberExpression.Expression == modelParameter)
        {
          Console.WriteLine("{0} is a function returning a member of the model", lambda);
          return;
        }

        ConstantExpression constantExpression2 = memberExpression.Expression as ConstantExpression;
        if (constantExpression2 != null)
        {
          Console.WriteLine("{0} is a function representing a captured local variable", lambda);
          return;
        }
      }
      Console.WriteLine("{0} cannot be fast tracked", lambda);
    }
  }
}


I called it like this (with output inline in the comments):

using System;

namespace MvcHarness
{
  class Program
  {
    static void Main(string[] args)
    {
      FastTrackAnalyser<string> analyser = new FastTrackAnalyser<string>();

      analyser.Analyse(x => x);
      // outputs "x => x is a function returning its own argument"

      analyser.Analyse(x => 999);
      // outputs "x => 999 is a function returning a constant value"

      analyser.Analyse(x => String.Empty);
      // outputs "x => String.Empty is a function returning a static member of either the model or another type"

      analyser.Analyse(x => x.Length);
      // outputs "x => x.Length is a function returning a member of the model"

      string name = "Joe Field";
      analyser.Analyse(x => name);
      // outputs "x => value(MvcHarness.Program+<>c__DisplayClass0).name is a function representing a captured local variable"

      analyser.Analyse(x => name.Length);
      // outputs "x => value(MvcHarness.Program+<>c__DisplayClass0).name.Length cannot be fast tracked"
    }
  }
}

The really interesting calls are the last two. They are analysing expressions which use captured local variables. The first one succeeds, but the second fails because it attempts to access a member of the variable.

This is a second companion post to Scott Guthrie’s ASP.NET MVC 2: Strongly Typed Html Helpers.

In my previous post I drilled down into the code executed when ASP.NET MVC 2 turned:

<%= Html.TextBoxFor(model => model.ProductName) %>

into:

<input id="ProductName" name="ProductName" type="text" value="Aniseed Syrup" />

We found that in order to do it ASP.NET MVC had performed two distinct operations:

  1. parsed the expression tree produced by the compiler for model => model.ProductName to extract the name of the property ("ProductName") to use in the id and name attributes of the input element
  2. compiled the expression and invoked the resulting delegate to produce the "Aniseed Syrup" value

Having examined the first case I started looking at how the expression was compiled into an executable delegate.

At this point we ran into this line of code:

return CachedExpressionCompiler.Process(expression)(container);

The problem

MVC is faced with a dilemma. It wants to use expression trees rather than delegates.

It likes to use the metadata obtained from the expression to, amongst other things, extract the property name.

But compiling expression trees is expensive.

To get round this problem it maintains an in-process cache of compiled expressions.

Disclaimer – this is a simple case

We are only going to look at the simple property access case:

model => model.ProductName

If we didn’t we would vanish into CachedExpressionCompiler for a long time. Many different types of expressions are compiled and cached, using different strategies.

By only looking at the property access case we will be able to visit the main classes, in particular FastTrack<TModel, TValue>.  We can look at the other caching strategies another time.

CachedExpressionCompiler.Process method

Here’s the CachedExpressionCompiler.Process() method in full – the comment describes it way better than I just did:

// This is the entry point to the cached expression tree compiler. The processor will perform a series of checks 
// and optimizations in order to return a fully-compiled func as quickly as possible to the caller. If the 
// input expression is particularly obscure, the system will fall back to a slow but correct compilation step. 
public static Func<TModel, TValue> Process<TModel, TValue>(Expression<Func<TModel, TValue>> lambdaExpression) { 
    return Processor<TModel, TValue>.GetFunc(lambdaExpression); 
} 

and here’s the beginning of Processor.GetFunc():

public static Func<TModel, TValue> GetFunc(Expression<Func<TModel, TValue>> lambdaExpression) { 
    // look for common patterns that don't need to be fingerprinted 
    Func<TModel, TValue> func = GetFuncFastTrack(lambdaExpression); 
    if (func != null) { 
        return func; 
    } 
... 
... 
... 
}

We’ll talk about fingerprinting another day. Suffice it to say that the call to GetFuncFastTrack() succeeds for our expression and we need go no further.

Here’s the whole of Processor.GetFuncFastTrack():

private static Func<TModel, TValue> GetFuncFastTrack(Expression<Func<TModel, TValue>> lambdaExpression) { 
    ParameterExpression modelParameter = lambdaExpression.Parameters[0]; 
    Expression body = lambdaExpression.Body; 

    return FastTrack<TModel, TValue>.GetFunc(modelParameter, body); 
}

The original expression has been split into its single parameter (our model) and its body and pased to the FastTrack class.

FastTrack<TModel, TValue>.GetFunc method

And finally here’s the relevant portion of the FastTrack<TModel, TValue>.GetFunc() method.

public static Func GetFunc(ParameterExpression modelParameter, Expression body) { 
    ... 
    ... 
    ... 
    { 
        MemberExpression memberExpression = body as MemberExpression; 
        if (memberExpression != null) { 
            ... 
            ... 
            ... 
            else if (memberExpression.Expression == modelParameter) { 
                // model => model.Member 
                return GetModelMemberLookupFunc(memberExpression.Member, false /* isStatic */); 
            } 
            ... 
            ... 
            ... 
        } 
    } 
    ... 
    ... 
    ... 
}

As you can see the expression body has been successfully cast to a MemberExpression. 

A MemberExpression has two public properties:

  • Expression – which represents the containing object (our "model")
  • Member – which is the MemberInfo instance representing the access (our "ProductName")

MVC compares the Expression property to the ParameterExpression.  They are equal so it calls:

GetModelMemberLookupFunc(memberExpression.Member, false /* isStatic */);

which is just a wrapper for this:

return _modelMemberLookupCache.GetFunc(member, isStatic);

I sense that we’re getting close to a cache…

ModelMemberLookupCache

This _modelMemberLookupCache is a static member of type ModelMemberLookupCache.  ModelMemberLookupCache is derived from an abstract base class called ReaderWriteCache<TKey, TValue>.  It provides a creation function for the case when the cache lookup fails i.e. the first time.  Here’s the creation function:

private static Func<TModel, TValue> CreateFunc(MemberInfo member, bool isStatic) { 
    ParameterExpression modelParam = Expression.Parameter(typeof(TModel), "model"); 
    MemberExpression memberExpr = Expression.MakeMemberAccess((!isStatic) ? modelParam : null, member); 
    Expression<Func<TModel, TValue>> lambda = Expression.Lambda<Func<TModel, TValue>>(memberExpr, modelParam); 
    return lambda.Compile(); 
}

In effect it recreates our original expression but "standardises" it by replacing the name of the parameter with "model".  This is then compiled into a delegate and stored in the cache with the MemberInfo instance as the key.

So the next time ASP.NET MVC encounters:

model => model.ProductName

it will be able to return a pre-compiled delegate from the cache – a significant boost to performance.

Next I’ll look at the other cases FastTrack caters for, and dig around a bit more in ReaderWriterCache<TKey, TValue>.