使用Func body的参数唯一标识匿名方法

时间:2013-01-15 09:49:40

标签: .net caching expression anonymous func

我正在尝试编写一些用于处理可能或不可能的缓存的魔术代码。基本上,我们的想法是让CacheManager类具有静态方法,该方法接受Func作为参数执行。在静态方法的主体中,它将能够执行该Func并使用缓存键来缓存结果,该缓存键唯一地标识传递的Func的内部(具有0或更多参数的匿名方法)。使用提供的相同参数对该静态方法的后续调用将导致相同的缓存键并返​​回缓存的结果。

我需要一种唯一标识传入的匿名函数的方法。

编辑:在调整匿名函数语法后,Expression提供了答案。

我担心在运行时编译表达式会对性能产生影响。鉴于这是一种支持缓存性能的尝试,编译需要花费大量时间才是愚蠢的。有什么想法吗?

测试的基本存储库:

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
}

public class ProductRepository
{
    private List<Product> products { get; set; }

    public ProductRepository()
    {
        products = new List<Product>() { new Product() { ID = 1, Name = "Blue Lightsaber" }, new Product() { ID = 2, Name = "Green Lightsaber" }, new Product() { ID = 3, Name = "Red Lightsaber" } };
    }

    public Product GetByID(int productID)
    {
        return products.SingleOrDefault(p => p.ID == productID);
    }
}

的CacheManager:

public class CacheManager
{
    public static TResult Get<TResult>(Expression<Func<TResult>> factory)
    {
        if (factory == null) throw new ArgumentNullException("factory");

        var methodCallExpression = factory.Body as MethodCallExpression;
        if (methodCallExpression == null) throw new ArgumentException("factory must contain a single MethodCallExpression.");

        string cacheKey = "|Repository:" + methodCallExpression.Method.DeclaringType.FullName + "|Method:" + methodCallExpression.Method.Name + "|Args";
        foreach (var arg in methodCallExpression.Arguments)
        {
            cacheKey += ":" + (arg is ConstantExpression ? ((ConstantExpression)arg).Value : Expression.Lambda(arg).Compile().DynamicInvoke());
        }

        if (HttpContext.Current.Cache[cacheKey] == null)
        {
            HttpContext.Current.Cache[cacheKey] = factory.Compile().Invoke();
        }
        return (TResult)HttpContext.Current.Cache[cacheKey];
    }
}

用法:

ProductRepository productRepository = new ProductRepository();
int productID = 1;
Product product;

// From repo
product = CacheManager.Get<Product>(() => productRepository.GetByID(1));

// From cache
product = CacheManager.Get<Product>(() => productRepository.GetByID(productID));

1 个答案:

答案 0 :(得分:1)

看起来你想做Memoization之类的事情。 Memoization是一种存储已经计算过的结果的方法,这似乎是您的要求。

更好的方法是创建一个与原始对象不同的新函数对象,因为它将结果存储在Dictionary中并对提供的参数执行查找。这个新函数将处理缓存,并在未命中的情况下添加到缓存中。

该类将创建一个参数的函数的备忘版本:

public static class memofactory
{
    public static Func<In, Out> Memoize<In, Out>(Func<In, Out> BaseFunction)
    {
        Dictionary<In,Out> ResultsDictionary = new Dictionary<In, Out>();

        return Input =>
            {
                Out rval;
                try
                {
                    rval = ResultsDictionary[Input];
                    Console.WriteLine("Cache hit"); // tracing message
                }
                catch (KeyNotFoundException)
                {
                    Console.WriteLine("Cache miss"); // tracing message
                    rval = BaseFunction(Input);
                    ResultsDictionary[Input] = rval;
                }
                return rval;
            };
    }
}

根据您的示例,用法是:

        ProductRepository productRepository = new ProductRepository();
        int productID = 1;
        Product product;

        Func<int, Product> MemoizedGetById = memofactory.Memoize<int, Product>(productRepository.GetByID);

        // From repo
        product = MemoizedGetById(1);

        // From cache
        product = MemoizedGetById(productID);