开发者

Precompile Lambda Expression Tree conversions as constants?

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 a开发者_如何学Cppears 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.


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);
    }
}


First of all, I suspect that your assumption about compiling an expression causing a performance hit will not actually pan out in reality. My experience shows that there are many more factors (database access, network latency, very poor algorithms) that cause performance bottlenecks before regular "good" code causes issues. Premature optimization is the root of all evil, so build your application and run stress tests to find the actual performance bottlenecks, as they are often not where you would expect.

With that said, I think that pre-compilation depends on what the Expression is being tranlated into. I know that with LINQ to SQL you can call DataContext.GetCommand(Expression) and retrieve a DBCommand, which you could then cache and reuse.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜