开发者

Cache compile from Expression<Func<T>>

I have a class that I use for the checking method arguments, which you call in the form:

public void SomeMethod(string anArg)
{
    Ensure.ArgumentNotNull(() => anA开发者_开发百科rg);
}

If the argument is null then an ArgumentNullException with the name of the property is thrown. This is done like so:

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

Where GetMemberName is an extension method I've written.

The problem I'm having is that the call to Compile is very slow, so I'd like to cache the result, but I don't seem to be able to come up with a cache key that will be unique enough to prevent cache conflicts, but not so unique that the cache becomes invalid.

My best effort so far is:

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

But this still causes cache key conflicts. What might be a better approach?


Where do the exrpessions come from, are they created new? If they are reused, you could just use the expression itself as the key.:

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

Else you could snoop around int he source code for the DLR http://dlr.codeplex.com/, I believe they address this kind of questions quite well.


Instead of using a Dictionary<T,V>, if you are more concern about race conditions and readability than about performance (I'm not sure if it's going to be worst), you might consider using a ConcurrentDictionary<T,V>.

It already has a GetOrAdd method that make you write less code, and as it comes with .NET 4.0 it's ensured to work and be well documented.

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

Besides, it will probably be a little less error prone to race conditions. But you have to know that the GetOrAdd is not thread safe either. And if you care about that, take a look at this question on CodeReview.SE in which they seem to find a solution to that.

Disclaimer: I know this is an old question that is more about forming a proper key than about a better implementation of the cache. But I think people looking for this today, might find it useful.


Jeffery Zhao has some excellent posts on this topic, unfortunately they are written in Chinese. A good news is that you can download the full implementation code for ExpressionTree caching here. And personally I have another suggestion: Why not Code Contracts?

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜