Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Precompile Lambda Expression Tree conversions as constants?

Tags:

c#

.net

linq

It is fairly common to take an Expression tree, and convert it to some other form, such as a string representation (for example this question and this question, and I suspect Linq2Sql does something similar).

In many cases, perhaps even most cases, the Expression tree conversion will always be the same, i.e. if I have a function

public string GenerateSomeSql(Expression<Func<TResult, TProperty>> expression)

then any call with the same argument will always return the same result for example:

GenerateSomeSql(x => x.Age)  //suppose this will always return "select Age from Person"
GenerateSomeSql(x => x.Ssn)  //suppose this will always return "select Ssn from Person"

So, in essence, the function call with a particular argument is really just a constant, except time is wasted at runtime re-computing it continuously.

Assuming, for the sake of argument, that the conversion was sufficiently complex to cause a noticeable performance hit, is there any way to pre-compile the function call into an actual constant?

Edit It appears that there is no way to do this exactly within C# itself. The closest you can probably come within c# is the accepted answer (though of course you would want to make sure that the caching itself wasn't slower than regenerating). To actually convert to true constants, I suspect that with some work, you could use something like mono-cecil to modify the bytecodes after compilation.

like image 676
Nathan Avatar asked Oct 14 '22 04:10

Nathan


1 Answers

The excellent LINQ IQueryable Toolkit project has a query cache that does something similar to what you've described. It contains an ExpressionComparer class that walks the hierarchy of two expressions and determines if they are equivalent. This technique is also used to collect references to common properties for parameterization and in the removal of redundant joins.

All you would need to do is come up with an expression hashing strategy so you can store the results of your processed expressions in a dictionary, ready for future reuse.

Your method would then look something like this:

private readonly IDictionary<Expression, string> _cache
    = new Dictionary<Expression, string>(new ExpressionEqualityComparer());

public string GenerateSomeSql(Expression<Func<TResult, TProperty>> expression)
{
    string sql;
    if (!_cache.TryGetValue(expression, out sql))
    {
        //process expression
        _cache.Add(expression, sql);
    }
    return sql;
}

class ExpressionEqualityComparer : IEqualityComparer<Expression>
{
    public bool Equals(Expression x, Expression y)
    {
        return ExpressionComparer.AreEqual(x, y);
    }

    public int GetHashCode(Expression obj)
    {
        return ExpressionHasher.GetHash(obj);
    }
}
like image 109
Nathan Baulch Avatar answered Nov 15 '22 13:11

Nathan Baulch