7

我有一个接受 Predicate<Foo> 并返回匹配项列表的 C# 方法...

public static List<Foo> FindAll( Predicate<Foo> filter )
{
    ...
}

过滤器通常是常见的一组...

public static class FooPredicates
{
    public static readonly Predicate<Foo> IsEligible = ( foo => ...)
    ...
}

...但可能是匿名代表。

我现在想让这个方法在 ASP.NET 缓存中缓存它的结果,所以重复调用同一个委托只会返回缓存的结果。为此,我需要从委托创建一个缓存键。Delegate.GetHashCode() 会为此目的产生合理的结果吗?还有其他我应该看的代表成员吗?你会完全以另一种方式来做这件事吗?

4

5 回答 5

4

要执行缓存任务,您可以遵循其他建议并创建缓存结果的 Dictionary<Predicate<Foo>,List<Foo>> (全局为静态,否则为成员字段)。在实际执行 Predicate<Foo> 之前,您需要检查结果是否已存在于字典中。

这种确定性函数缓存的通用名称称为记忆化——它真棒:)

自从 C# 3.0 添加了 lambda 和 Func/Action 委托后,将 Memoization 添加到 C# 就非常容易了。

Wes Dyer 有一篇很棒的文章,通过一些很好的例子将这个概念带到了 C# 中。

如果您希望我向您展示如何做到这一点,请告诉我……否则,Wes 的帖子应该足够了。

回答您关于委托哈希码的查询。如果两个代表相同,d1.GetHashCode() 应该等于 d2.GetHashCode(),但我对此不是 100%。您可以通过使用 Memoization 并在 FindAll 方法中添加 WriteLine 来快速检查这一点。如果这最终不是真的,另一种选择是使用 Linq.Expression<Predicate<Foo>> 作为参数。如果表达式不是闭包,那么做同样事情的表达式应该是相等的。

让我知道这是怎么回事,我很想知道关于 delegate.Equals 的答案。

于 2008-10-17T13:40:24.377 回答
2

委托相等性查看调用列表中的每个调用,测试要调用的方法的相等性以及方法的目标。

该方法是缓存键的一个简单部分,但方法的目标(调用它的实例 - 假设是实例方法)可能无法以可序列化的方式缓存。特别是,对于捕获状态的匿名函数,它将是为捕获该状态而创建的嵌套类的实例。

如果这一切都在内存中,只需将委托本身保留为哈希键就可以了 - 尽管这可能意味着客户端希望被垃圾收集的某些对象会挂起。如果您需要将其序列化到数据库中,它会变得更加复杂。

你能让你的方法也接受一个缓存键(例如一个字符串)吗?(这是假设内存缓存不足。)

于 2008-10-17T13:23:15.643 回答
2

将缓存的结果保存在 Dictionary<Predicate<Foo>,List<Foo>> 对我来说很尴尬,因为我希望 ASP.NET 缓存为我处理到期而不是永远缓存所有结果,但这是一个很好的解决方案。我想我最终会使用 Will 的 Dictionary<Predicate<Foo>,string> 来缓存我可以在 ASP.NET 缓存键中使用的字符串。

一些初步测试表明,委托平等做了其他人所说的“正确的事情”,但 Delegate.GetHashCode 在病态上没有帮助。反光板揭示

public override int GetHashCode()
{
    return base.GetType().GetHashCode();
}

所以任何 Predicate<Foo> 返回相同的结果。

我剩下的问题是匿名代表如何实现平等。那么“在同一个目标上调用相同的方法”是什么意思呢?似乎只要在同一个地方定义了委托,引用就相等。在不同地方定义的具有相同主体的代表不是。

static Predicate<int> Test()
{
    Predicate<int> test = delegate(int i) { return false; };
    return test;
}

static void Main()
{
    Predicate<int> test1 = Test();
    Predicate<int> test2 = Test();
    Console.WriteLine(test1.Equals( test2 )); // True

    test1 = delegate(int i) { return false; };
    test2 = delegate(int i) { return false; };
    Console.WriteLine(test1.Equals( test2 )); // False
}

这应该可以满足我的需要。带有预定义谓词的调用将被缓存。对一个使用匿名方法调用 FindAll 的方法进行多次调用应该会获得缓存结果。使用显然相同的匿名方法调用 FindAll 的两个方法不会共享缓存的结果,但这应该是相当罕见的。

于 2008-10-18T13:20:57.613 回答
1

除非您确定 Delegate 的 GetHashCode 实现是确定性的并且不会导致任何冲突,否则我不会相信它。

这里有两个想法。首先,将委托的结果存储在 Predicate/List 字典中,使用谓词作为键,然后将整个结果字典存储在缓存中的单个键下。不好的是,如果缓存项丢失,您会丢失所有缓存的结果。

另一种方法是为 Predicate 创建一个扩展方法 GetKey(),它使用对象/字符串字典来存储和检索所有 Predicate 的所有键。您使用委托对字典进行索引并返回其键,如果找不到则创建一个。通过这种方式,您可以确保每个代表都获得了正确的密钥,并且没有任何冲突。一个简单的方法是类型名称 + Guid。

于 2008-10-17T13:25:23.923 回答
0

对象的相同实例将始终返回相同的哈希码(.Net 中 GetHashCode() 的要求)。如果您的谓词在静态列表中并且您没有每次都重新定义它们,那么我看不出将它们用作键的问题。

于 2008-10-18T05:06:26.787 回答