代码之家  ›  专栏  ›  技术社区  ›  Fernando

缓存已编译的lambda表达式

  •  3
  • Fernando  · 技术社区  · 15 年前

    我需要得到一些作为lambda表达式传递给某些方法的信息。基本上,这是向数据库查询添加信息的一种方法。一个简单的例子是:

    companyData.GetAll(
       where => "SomeField = @SOMEFIELD",
       order => "Id",
       SOMEFIELD => new Parameter {DbType = DbType.String, Value = "some value"}
    )
    

    它工作得很好,除了我需要打电话 LambdaExpression.Compile 获取参数对象,这对性能有很大影响。

    为了获得更快的结果,我提供了这个简单的缓存测试:

    class ExpressionCache<T,U>
    {
    
        private static ExpressionCache<T, U> instance;
        public static ExpressionCache<T, U> Instance
        {
            get
            {
                if (instance == null) {
                    instance = new ExpressionCache<T, U>();
                }
                return instance;
            }
        }
    
        private ExpressionCache() { }
    
        private Dictionary<string, Func<T, U>> cache = new Dictionary<string, Func<T, U>>();
    
        public Func<T, U> Get(Expression<Func<T, U>> expression)
        {
            string key = expression.Body.ToString();
            Func<T,U> func;
            if (cache.TryGetValue(key, out func)) {
                return func;
            }
            func = expression.Compile();
            cache.Add(key, func);
            return func;
        }
    }
    

    这个类产生了巨大的不同:从10000次迭代中的35000毫秒到大约700毫秒。

    现在问题来了:使用表达式体作为字典的键会遇到什么样的问题?

    2 回复  |  直到 15 年前
        1
  •  9
  •   Jon Skeet    15 年前

    我不明白如果你要把表达式树编译成委托,为什么你需要它们。为什么不使用委托开始并让编译器将lambda表达式转换为委托而不是表达式树?

    至于使用字符串的主体,您可能会遇到使用不同类型但属性名相同的奇怪情况。但是,考虑到您已经将这些类型用作泛型类型参数,这可能不是一个问题…我想它会起作用的,尽管我想 发誓 对它。

    哦,顺便说一句,你的单点缓存不是线程安全的。我建议您初始化 instance 静态初始值设定项中的变量。这会导致代码更简单,而且更安全…

    private static readonly ExpressionCache<T, U> instance
         = new ExpressionCache<T, U>();
    public static ExpressionCache<T, U> Instance { get { return instance; } }
    
        2
  •  9
  •   Joel Coehoorn    15 年前

    lambda表达式可以创建闭包。当这种情况发生时,表达式主体并不包含进入结果代码的所有内容。可能缺少包含的可能重要的局部变量。