Кэширование результатов делегата - PullRequest
7 голосов
/ 17 октября 2008

У меня есть метод C #, который принимает предикат и возвращает список подходящих элементов ...

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

Фильтр часто будет одним из общего набора ...

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

... но может быть анонимным делегатом.

Теперь я бы хотел, чтобы этот метод кэшировал свои результаты в кэше ASP.NET, поэтому повторные вызовы с одним и тем же делегатом просто возвращают кэшированный результат. Для этого мне нужно создать ключ кеша от делегата. Будет ли Delegate.GetHashCode () давать разумные результаты для этой цели? Есть ли какой-нибудь другой член Делегата, на которого я должен взглянуть? Вы бы сделали это совсем по-другому?

Ответы [ 5 ]

4 голосов
/ 17 октября 2008

Чтобы выполнить задачу кэширования, вы можете следовать другим советам и создать словарь , List > (статический для глобального или поле члена в противном случае), который кэширует результаты. Перед фактическим выполнением предиката вам необходимо проверить, существует ли результат в словаре.

Общее название для этой детерминированной функции кэширования называется Memoization - и это здорово:)

С тех пор, как в C # 3.0 были добавлены лямбда-выражения и замена делегатов Func / Action, добавить Memoization в C # довольно просто.

У Уэса Дайера есть замечательный пост , в котором концепция C # приводится с некоторыми замечательными примерами.

Если вы хотите, чтобы я показал вам, как это сделать, дайте мне знать ... в противном случае пост Уэса должен быть адекватным.

В ответ на ваш запрос о хэш-кодах делегата. Если два делегата одинаковы, d1.GetHashCode () должен быть равен d2.GetHashCode (), но я не на 100% об этом. Вы можете быстро это проверить, запустив Memoization и добавив WriteLine в метод FindAll. Если это не соответствует действительности, другим вариантом является использование Linq.Expression > в качестве параметра. Если выражения не являются замыканиями, то выражения, которые делают то же самое, должны быть равны.

Дайте мне знать, как это происходит, мне интересно узнать ответ о делегате. Уравнения.

2 голосов
/ 18 октября 2008

Хранение кэшированных результатов в словаре , List > неудобно для меня, потому что я хочу, чтобы кэш ASP.NET обрабатывал для меня срок действия, а не кэшировал все результаты навсегда, но в остальном это хорошо решение. Я думаю, что в конечном итоге я буду использовать словарь Уилла , string> для кэширования строки, которую я могу использовать в ключе кэширования ASP.NET.

Некоторые первоначальные тесты показывают, что равенство делегатов делает «правильную вещь», как говорили другие, но Delegate.GetHashCode патологически бесполезен. Отражатель показывает

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

Таким образом, любой предикат возвращает тот же результат.

Моя оставшаяся проблема заключалась в том, как работает равенство для анонимных делегатов. Что значит «один и тот же метод, вызываемый для одной и той же цели»? Кажется, что, пока делегат был определен в том же месте, ссылки равны. Делегаты с одинаковым телом, определенным в разных местах, не являются.

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 с явно одним и тем же анонимным методом, не будут предоставлять кешированные результаты, но это должно быть довольно редко.

2 голосов
/ 17 октября 2008

Равенство делегатов просматривает каждый вызов в списке вызовов, проверяет равенство вызываемого метода и цель метода.

Метод представляет собой простую часть ключа кэша, но цель метода (экземпляр, для которого он вызывается - предполагается, что метод экземпляра) может быть невозможно кэшировать сериализуемым способом. В частности, для анонимных функций, которые фиксируют состояние, это будет экземпляр вложенного класса, созданного для захвата этого состояния.

Если все это находится в памяти, то можно просто сохранить сам делегат в качестве хеш-ключа - хотя это может означать, что некоторые объекты, которые клиенты ожидают собрать как мусор, зависают. Если вам нужно сериализовать это в базу данных, он получает хайриер.

Не могли бы вы заставить свой метод также принимать ключ кэша (например, строку)? (Предполагается, что кэш в памяти не подходит.)

1 голос
/ 17 октября 2008

Если вы не уверены, что реализация делегата GetHashCode является детерминированной и не приводит к каким-либо коллизиям, я бы ей не поверил.

Вот две идеи. Сначала сохраните результаты делегатов в словаре Predicate / List, используя предикат в качестве ключа, а затем сохраните весь словарь результатов под одним ключом в кэше. Плохо то, что вы потеряете все свои кэшированные результаты, если элемент кэша потерян.

Альтернативой может быть создание метода расширения для Predicate, GetKey (), который использует словарь объектов / строк для хранения и извлечения всех ключей для всех предикатов. Вы индексируете в словаре делегата и возвращаете его ключ, создавая его, если не нашли его. Таким образом, вы уверены, что получаете правильный ключ для каждого делегата, и нет никаких коллизий. Наивным будет имя типа + Guid.

0 голосов
/ 18 октября 2008

Один и тот же экземпляр объекта всегда будет возвращать один и тот же хэш-код (требование GetHashCode () в .Net). Если ваши предикаты находятся внутри статического списка, и вы не переопределяете их каждый раз, я не вижу проблемы в использовании их в качестве ключей.

...