从Expression <func <t>&gt; </func <t>缓存编译

时间:2011-02-15 09:31:40

标签: c# caching expression

我有一个用于检查方法参数的类,您可以在表单中调用:

public void SomeMethod(string anArg)
{
    Ensure.ArgumentNotNull(() => anArg);
}

如果参数为null,则抛出具有该属性名称的ArgumentNullException。这样做是这样的:

public static void ArgumentNotNull<T>(Expression<Func<T>> expression) where T : class 
{
    var value = expression.Compile()();
    if (value == null)
    {
        throw new ArgumentNullException(expression.GetMemberName());
    }
}

GetMemberName是我编写的扩展方法。

我遇到的问题是对Compile的调用非常慢,所以我想缓存结果,但我似乎无法想出一个足够独特的缓存键防止缓存冲突,但不是那么独特,以致缓存变得无效。

到目前为止我的最大努力是:

internal static class ExpressionCache<T>
{
    private static readonly Dictionary<string, Func<T>> Cache = new Dictionary<string, Func<T>>();

    public static Func<T> CachedCompile(Expression<Func<T>> targetSelector)
    {
        Func<T> cachedFunc;
        var cacheKey = targetSelector + targetSelector.Body.ToString();

        if (!Cache.TryGetValue(cacheKey, out cachedFunc))
        {
            cachedFunc = targetSelector.Compile();
            Cache[cacheKey] = cachedFunc;
        }

        return cachedFunc;
    }
}

但这仍会导致缓存密钥冲突。什么是更好的方法?

3 个答案:

答案 0 :(得分:2)

exrpessions来自哪里,是否创造了新的?如果它们被重用,你可以只使用表达式本身作为键。:

internal static class ExpressionCache<T>
{
    private static readonly Dictionary<Expression<Func<T>, Func<T>> Cache = new Dictionary<Expression<Func<T>, Func<T>>();

    public static Func<T> CachedCompile(Expression<Func<T>> targetSelector)
    {
        Func<T> cachedFunc;
        if (!Cache.TryGetValue(targetSelector, out cachedFunc))
        {
            cachedFunc = targetSelector.Compile();
            Cache[targetSelector] = cachedFunc;
        }

        return cachedFunc;
    }
}

否则你可以窥探DLR http://dlr.codeplex.com/的源代码,我相信他们很好地解决了这类问题。

答案 1 :(得分:1)

如果您更关注竞争条件和可读性而不是性能(我不确定它是否会变得最糟糕),而不是使用Dictionary<T,V>,您可以考虑使用ConcurrentDictionary<T,V>

它已经有一个GetOrAdd方法,可以让你编写更少的代码,而且随着.NET 4.0的推出,它可以确保工作并有良好的文档记录。

var dict = new ConcurrentDictionary<Expression<Func<T>, Func<T>>();
...
var cacheKey = targetSelector; //or whatever as long as it's unique
var cachedFunc = dict.GetOrAdd(cacheKey, key => targetSelector.Compile());

此外,它可能会更容易出现竞争条件。但是你必须知道GetOrAdd也不是线程安全的。如果您关心这一点,请查看它们似乎找到解决方案的question on CodeReview.SE

  

免责声明:我知道这是一个老问题,更多的是关于形成   一个正确的关键,而不是更好的缓存实现。   但我认为今天寻找这个的人可能会发现它很有用。

答案 2 :(得分:0)

Jeffery Zhao对此主题有some excellent posts,不幸的是它们是用中文写的。一个好消息是您可以下载ExpressionTree缓存here的完整实现代码。我个人还有另一个建议:为什么不Code Contracts