Cache compile from Expression<Func<T>>
Asked Answered
H

3

9

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(() => anArg);
}

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?

Histogenesis answered 15/2, 2011 at 9:31 Comment(4)
I would use PostSharp or IL: abdullin.com/journal/2008/12/19/…Agent
@Ruben, you're right I hacked about a bit in the browser before posting it. I'll correct it.Histogenesis
May I ask why it needs to be Expression<Func>'s you put in to the argument checker... why not just the value? Is only to be able so throw that exception?Debor
Could you provide a sample of two expression that will conflict?Debor
U
4

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.

Ulbricht answered 15/2, 2011 at 10:13 Comment(0)
M
2

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.

Moorfowl answered 21/5, 2015 at 0:2 Comment(0)
A
0

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?

Aldon answered 15/2, 2011 at 10:9 Comment(2)
The download link is broken, you don't happen to have it or know if it can be found anywhere else? (I failed to find it, the file itself was unfortunately not retrievable from archive.org.)Francklyn
@HåkanLindqvist: Sorry the msdn link is retired and I have no source code. I'm afraid you need to read the blogs written in Chinese (with the help of google translate maybe?), there are some code pieces in the articles.Aldon

© 2022 - 2024 — McMap. All rights reserved.