采用表达式树并将其转换为其他形式(例如字符串表示形式(例如this question和this 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之类的东西来修改编译后的字节码。
答案 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
,然后可以缓存并重用。