Expression<Func<..>>.Compile()实际类型?表达式缓存类型转换异常排查
问题分析与解决方案
嘿,我来帮你拆解下这个问题~
首先回答你的第一个问题:Expression<Func<T>>.Compile()的实际类型是什么?
Expression<Func<T>>.Compile()方法编译表达式树后,返回的实际类型就是**Func<T>**——也就是你定义的那个委托类型,它把表达式树转换成了可以直接执行的委托实例。
为什么第二次调用会抛出InvalidCastException?
你代码里踩了一个很容易忽略的小坑:你把表达式对象本身存进了缓存字典,而不是编译后的委托!
看这段关键代码:
result = e.Compile(); // result是编译好的Func<T>委托 dict.Add(1, e); // 这里错误地把表达式e存进去了,而不是result!
第一次调用时,你直接用result(正确的委托)执行,没问题;但第二次调用时,从字典里取出来的是Expression<Func<int>>对象,你强行把它转成Func<int>,这两种类型完全不兼容,自然就抛出了错误提示里的转换异常。
修正后的代码
我们只需要把缓存的对象改成编译后的委托,同时优化下缓存的key(你之前用固定的1,会导致不同表达式互相覆盖),比如用表达式的哈希值作为key:
static Dictionary<int, Delegate> _compiledDelegateCache = new Dictionary<int, Delegate>(); static T TestMethod<T>(Expression<Func<T>> expression) { var key = expression.GetHashCode(); if (!_compiledDelegateCache.TryGetValue(key, out var compiledDelegate)) { compiledDelegate = expression.Compile(); _compiledDelegateCache.Add(key, compiledDelegate); } return ((Func<T>)compiledDelegate)(); } static void Main(string[] args) { var firstTest = TestMethod(() => default(int)); var secondTest = TestMethod(() => default(int)); var stringTest = TestMethod(() => "Hello World!"); // 不同表达式也能正常缓存 }
额外注意点
- 缓存key的选择:用
expression.GetHashCode()简单高效,但存在极小的哈希碰撞概率,如果你的场景对缓存正确性要求极高,可以自定义表达式比较器,或者将表达式转换为字符串作为key(性能会稍差)。 - 泛型缓存的替代方案:如果你的场景中只会处理单一类型的表达式,也可以把字典定义为
static Dictionary<int, Func<T>>——因为泛型方法的静态字段会基于泛型参数生成独立实例,TestMethod<int>和TestMethod<string>会有各自的缓存字典,不会互相干扰。
内容的提问来源于stack exchange,提问作者CookedCthulhu
相关产品推荐
相关产品推荐

