将Lambda表达式树转换预编译为常量?

时间:2010-06-17 00:40:57

标签: c# .net linq

采用表达式树并将其转换为其他形式(例如字符串表示形式(例如this questionthis question)是相当常见的,我怀疑Linq2Sql做了类似的事情)。

在许多情况下,甚至大多数情况下,表达式树转换将始终相同,即如果我有一个函数

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

然后任何具有相同参数的调用将始终返回相同的结果,例如:

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"

所以,实质上,使用特定参数的函数调用实际上只是一个常量,除了在运行时浪费时间重新计算它。

假设为了论证,转换足够复杂以致引起明显的性能损失,有没有办法将函数调用预编译为实际的常量?

修改 似乎没有办法在C#本身内完全做到这一点。你可以在c#中最接近的是接受的答案(当然你要确保缓存本身并不比重新生成慢)。要实际转换为真常量,我怀疑通过一些工作,您可以使用mono-cecil之类的东西来修改编译后的字节码。

2 个答案:

答案 0 :(得分:2)

优秀的LINQ IQueryable Toolkit项目有一个查询缓存,可以执行类似于您所描述的操作。它包含一个ExpressionComparer类,它遍历两个表达式的层次结构并确定它们是否等效。此技术还用于收集参数化和删除冗余连接的公共属性的引用。

您需要做的就是提出表达式哈希策略,以便将已处理表达式的结果存储在字典中,以备将来重用。

您的方法看起来像这样:

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

答案 1 :(得分:1)

首先,我怀疑你关于编译导致性能损失的表达式的假设实际上并不会实现。我的经验表明,在常规“好”代码导致问题之前,还有更多因素(数据库访问,网络延迟,非常差的算法)导致性能瓶颈。过早优化是所有邪恶的根源,因此构建应用程序并运行压力测试以找到实际的性能瓶颈,因为它们往往不是您所期望的。

话虽如此,我认为预编译取决于表达式被转换成什么。我知道使用LINQ to SQL,您可以调用DataContext.GetCommand(Expression)并检索DBCommand,然后可以缓存并重用。