Кеширование результатов делегата

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

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

Фильтр часто бывает одним из обычных ...

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

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
0
3 181
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

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

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

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

«Метод - это простая часть ключа кеша». Жаль, что .Net совершенно не использует его. Хэш-код (обычного) делегата основан ТОЛЬКО на типах делегатов в его списке вызовов. Итак, любой делегат Action с 4 подписчиками имеет одинаковый хэш-код = (stackoverflow.com/questions/6624151/…

Ark-kun 18.12.2012 15:40

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

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

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

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

Jon Skeet 17.10.2008 17:32
Ответ принят как подходящий

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

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

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

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

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

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

Сообщите мне, как это происходит, мне интересно узнать ответ о delegate.Equals.

Я ответил ниже некоторыми тестами delegate.Equals. Я, наверное, добавлю еще в понедельник, когда попытаюсь использовать идеи по-настоящему.

stevemegson 18.10.2008 17:25

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

Хранение кешированных результатов в Dictionary <Predicate <Foo>, List <Foo>> неудобно для меня, потому что я хочу, чтобы кеш ASP.NET обрабатывал истечение срока для меня, а не кешировал все результаты навсегда, но в остальном это хорошее решение. Я думаю, что в конечном итоге перейду к Will's 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 с явно одним и тем же анонимным методом, не будут использовать кешированные результаты, но это должно происходить довольно редко.

Другие вопросы по теме