【问题标题】:Uniquely identifying anonymous method with parameters of Func body唯一标识具有 Func 主体参数的匿名方法
【发布时间】:2012-12-29 09:19:00
【问题描述】:

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

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

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

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

用于测试的基本存储库:

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

缓存管理器:

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

【问题讨论】:

  • 动作主体是否受到任何限制?或者它基本上可以是任意代码?
  • 这将是我的下一个问题......是否有可能在那里强制执行任何事情?我可能会限制 TInstance 类型。最好强制 Action 主体是对我的 out 参数的单个赋值语句,以减少将 volatile 作为 Action 传递的机会(假设这是实现最终解决方案所需的语法)。跨度>
  • 不能在编译时强制执行,只能在运行时借助表达式强制执行。一个想法:为什么分配给变量并具有与输出参数相同的语法的奇怪语法?为什么不简单地使用Func&lt;TRepository, TResult&gt; 并将GetValue 更改为此签名:TResult GetValue&lt;TRepository, TResult&gt;(TRepository repository, Func&lt;TRepository, TResult&gt; valueFactory)?这实际上会起作用,但您当前的代码不会。
  • 当前代码实际上确实正确地分配了一个值并且可以工作,但我同意这很奇怪。我会看看你对使用 Func 的建议并很快更新我的问题。这实际上是我考虑过的,但我没有完全探索它。感谢您的帮助!
  • 不要使用语句体。使用这个:(productsRepositoryInstance) =&gt; productsRepositoryInstance.GetByID(1)。如果您不想比较 IL 代码以获取缓存密钥,我认为您无法绕过调用 Compile。但是,使用 IL 代码可能是另一种选择:当您不使用表达式时,您不能在委托上调用 Method.GetMethodBody().GetILasByteArray()。我猜这个字节数组可以用作键。但是,我不确定拥有这样大小的密钥的性能如何。

标签: .net caching expression anonymous func


【解决方案1】:

您似乎想要执行Memoization 之类的操作。记忆是一种存储已计算结果的方法,这似乎是您的要求。

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

这个类将创建一个参数的函数的记忆版本:

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

【讨论】:

  • 谢谢 Brian,我从未听说过 Memoize 概念。似乎与我所追求的相似。我猜您的示例中的限制是它只能处理具有一个参数的 Func 输入,尽管您可以对 Memoize 进行许多覆盖,最多接受 In9 参数。
  • 我很高兴它有帮助。是的,您必须重写 Memoize 以处理更多参数,并且必须将参数打包到元组或结构中以用作字典的键。如果我的回答解决了你的问题,请采纳,如果有什么我可以更清楚的告诉我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-14
  • 1970-01-01
  • 2016-03-15
  • 2015-03-02
  • 1970-01-01
  • 2011-01-05
  • 2018-03-01
相关资源
最近更新 更多