我有一个 C# 方法,它接受 Predicate; 并返回匹配项的列表...
public static List<Foo> FindAll( Predicate<Foo> filter )
{
...
}
过滤器通常是公共集合之一...
public static class FooPredicates
{
public static readonly Predicate<Foo> IsEligible = ( foo => ...)
...
}
...但可能是匿名委托。
我现在希望此方法将其结果缓存在 ASP.NET 缓存中,因此使用同一委托重复调用只会返回缓存的结果。 为此,我需要从委托创建一个缓存键。 Delegate.GetHashCode() 会为此目的产生合理的结果吗? 我还应该关注其他代表成员吗? 你会完全用另一种方式来做这件事吗?
I have a C# method which accepts a Predicate<Foo> and returns a list of matching items...
public static List<Foo> FindAll( Predicate<Foo> filter )
{
...
}
The filter will often be one of a common set...
public static class FooPredicates
{
public static readonly Predicate<Foo> IsEligible = ( foo => ...)
...
}
...but may be an anonymous delegate.
I'd now like to have this method cache its results in the ASP.NET cache, so repeated calls with the same delegate just return the cached result. For this, I need to create a cache key from the delegate. Will Delegate.GetHashCode() produce sensible results for this purpose? Is there some other member of Delegate that I should look at? Would you do this another way entirely?
发布评论
评论(5)
要执行缓存任务,您可以按照其他建议创建一个 Dictionary,List> 。 (全局静态,否则成员字段)缓存结果。 在实际执行 Predicate 之前,您需要检查结果是否已存在于字典中。
这种确定性函数缓存的通用名称称为 Memoization - 非常棒:)
自从 C# 3.0 添加了 lambda 和 Func/Action 委托之后,向 C# 添加 Memoization 就非常容易了。
Wes Dyer 有一篇很棒的帖子,其中介绍了C# 的概念以及一些很好的例子。
如果您想让我向您展示如何执行此操作,请告诉我...否则,Wes 的帖子应该足够了。
回答您有关委托哈希码的查询。 如果两个委托相同,则 d1.GetHashCode() 应该等于 d2.GetHashCode(),但我对此并不是 100%。 您可以通过尝试 Memoization 并将 WriteLine 添加到 FindAll 方法中来快速检查这一点。 如果这最终不成立,另一种选择是使用 Linq.Expression> 作为参数。 如果表达式不是闭包,则执行相同操作的表达式应该相等。
让我知道这是怎么回事,我有兴趣知道有关 delegate.Equals 的答案。
To perform your caching task, you can follow the other suggestions and create a Dictionary<Predicate<Foo>,List<Foo>> (static for global, or member field otherwise) that caches the results. Before actually executing the Predicate<Foo>, you would need to check if the result already exists in the dictionary.
The general name for this deterministic function caching is called Memoization - and its awesome :)
Ever since C# 3.0 added lambda's and the swag of Func/Action delegates, adding Memoization to C# is quite easy.
Wes Dyer has a great post that brings the concept to C# with some great examples.
If you want me to show you how to do this, let me know...otherwise, Wes' post should be adequate.
In answer to your query about delegate hash codes. If two delegates are the same, d1.GetHashCode() should equal d2.GetHashCode(), but I'm not 100% about this. You can check this quickly by giving Memoization a go, and adding a WriteLine into your FindAll method. If this ends up not being true, another option is to use Linq.Expression<Predicate<Foo>> as a parameter. If the expressions are not closures, then expressions that do the same thing should be equal.
Let me know how this goes, I'm interested to know the answer about delegate.Equals.
委托相等性查看调用列表中的每个调用,测试要调用的方法和方法目标的相等性。
该方法是缓存键的一个简单部分,但该方法的目标(调用它的实例 - 假设是实例方法)可能无法以可序列化的方式缓存。 特别是,对于捕获状态的匿名函数,它将是为捕获该状态而创建的嵌套类的实例。
如果这一切都在内存中,则只需将委托本身保留为哈希键就可以了 - 尽管这可能意味着客户端希望垃圾收集的某些对象会徘徊。 如果您需要将其序列化到数据库,事情就会变得更加棘手。
你能让你的方法也接受缓存键(例如字符串)吗? (假设内存缓存不足。)
Delegate equality looks at each invocation in the invocation list, testing for equality of method to be invoked, and target of method.
The method is a simple piece of the cache key, but the target of the method (the instance to call it on - assuming an instance method) could be impossible to cache in a serializable way. In particular, for anonymous functions which capture state, it will be an instance of a nested class created to capture that state.
If this is all in memory, just keeping the delegate itself as the hash key will be okay - although it may mean that some objects which clients would expect to be garbage collected hang around. If you need to serialize this to a database, it gets hairier.
Could you make your method accept a cache key (e.g. a string) as well? (That's assuming an in memory cache is inadequate.)
将缓存结果保存在 Dictionary
一些初步测试表明,正如其他人所说,委托平等做了“正确的事情”,但 Delegate.GetHashCode 在病态上毫无帮助。 Reflector 揭示了
任何谓词 返回相同的结果。
我剩下的问题是匿名代表的平等如何发挥作用。 那么“对同一目标调用同一方法”是什么意思呢? 看来只要委托定义在同一个地方,引用都是平等的。 在不同地方定义的同一机构的代表则不然。
这应该可以满足我的需求。 具有预定义谓词的调用将被缓存。 对使用匿名方法调用 FindAll 的一个方法进行多次调用应该会获得缓存的结果。 使用明显相同的匿名方法调用 FindAll 的两个方法不会共享缓存结果,但这应该是相当罕见的。
Keeping the cached results in a Dictionary<Predicate<Foo>,List<Foo>> is awkward for me because I want the ASP.NET cache to handle expiry for me rather than caching all results forever, but it's otherwise a good solution. I think I'll end up going with Will's Dictionary<Predicate<Foo>,string> to cache a string that I can use in the ASP.NET cache key.
Some initial tests suggest that delegate equality does the "right thing" as others have said, but Delegate.GetHashCode is pathologically unhelpful. Reflector reveals
So any Predicate<Foo> returns the same result.
My remaining issue was how equality works for anonymous delegates. What does "same method called on the same target" mean then? It seems that as long as the delegate was defined in the same place, references are equal. Delegates with the same body defined in different places are not.
This should be OK for my needs. Calls with the predefined predicates will be cached. Multiple calls to one method that calls FindAll with an anonymous method should get cached results. Two methods calling FindAll with apparently the same anonymous method won't share cached results, but this should be fairly rare.
除非您确定 GetHashCode 的 Delegate 实现是确定性的并且不会导致任何冲突,否则我不会信任它。
这里有两个想法。 首先,将委托的结果存储在谓词/列表字典中,使用谓词作为键,然后将整个结果字典存储在缓存中的单个键下。 不好的是,如果缓存项丢失,您将丢失所有缓存结果。
另一种方法是为 Predicate 创建一个扩展方法 GetKey(),它使用对象/字符串字典来存储和检索所有 Predicate 的所有键。 您使用委托对字典进行索引并返回其键,如果找不到则创建一个。 这样,您就可以确保为每个委托获得正确的密钥,并且不会发生任何冲突。 一种天真的做法是类型名称 + Guid。
Unless you're sure Delegate's implementation of GetHashCode is deterministic and doesn't result in any collisions I wouldn't trust it.
Here's two ideas. First, store the results of the delegates within a Predicate/List dictionary, using the predicate as the key, and then store the entire dictionary of results under a single key in the cache. Bad thing is that you lose all your cached results if the cache item is lost.
An alternative would be to create an extension method for Predicate, GetKey(), that uses an object/string dictionary to store and retrieve all keys for all Predicates. You index into the dictionary with the delegate and return its key, creating one if you don't find it. This way you're assured that you are getting the correct key per delegate and there aren't any collisions. A naiive one would be type name + Guid.
对象的相同实例将始终返回相同的哈希码(.Net 中 GetHashCode() 的要求)。 如果您的谓词位于静态列表内并且您没有每次都重新定义它们,那么我看不出将它们用作键有问题。
The same instance of an object will always return the same hashcode (requirement of GetHashCode() in .Net). If your predicates are inside a static list and you are not redefining them each time, I can't see a problem in using them as keys.