从Expression<Func<T>>缓存编译

9

我有一个用于检查方法参数的类,你可以按以下形式调用:

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

如果参数为空,则会抛出一个带有属性名的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;
    }
}

但这仍然会导致缓存键冲突。有什么更好的方法吗?

我会使用PostSharp或IL:http://abdullin.com/journal/2008/12/19/how-to-get-parameter-name-and-argument-value-from-c-lambda-v.html - Jaroslav Jandek
@Ruben,你说得对,在发布之前我在浏览器中进行了一些修改。我会纠正它的。 - ilivewithian
我可以问一下为什么需要将Expression<Func>放入参数检查器中吗?为什么不只是值?这样做只是为了能够抛出异常吗? - asgerhallas
你能提供两个会冲突的表达式样例吗? - asgerhallas
3个回答

4

这些表达式是从哪里来的,它们是新创建的吗?如果它们被重复使用,你可以直接将表达式本身作为键。

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/,我相信他们很好地解决了这种问题。

2

如果您更关心并发问题和可读性,而不是性能(我不确定它是否会更糟),则可以考虑使用ConcurrentDictionary<T,V>而不是使用Dictionary<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 可能会减少竞争条件的出错率。但您必须知道 GetOrAdd 也不是线程安全的。如果您关心这一点,请查看 CodeReview.SE 上的问题,他们似乎找到了解决方案。
免责声明:我知道这是一个关于形成正确键而不是更好地实现缓存的旧问题。但我认为今天寻找这个问题的人可能会发现它有用。

0

下载链接已经失效了,你不会碰巧有它或者知道它在其他地方可以找到吗?(我没有找到它,很遗憾文件本身也无法从archive.org中检索出来。) - Håkan Lindqvist
@HåkanLindqvist:抱歉,msdn链接已经停用,我没有源代码。恐怕你需要阅读用中文写的博客(也许可以借助谷歌翻译?),这些文章中有一些代码片段。 - Cheng Chen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接