Запретить изменение кэшированного объекта

Как я могу обслуживать объект из кеша, не опасаясь, что код, использующий объект, изменит источник в кеше?

Пример:

var instance = new SomeClass();
instance.Text = "Abc";

MemoryCache.Default.Set("Key", instance, new CacheItemPolicy() {  });
instance.Text = "123";

Console.WriteLine((SomeClass)MemoryCache.Default.Get("Key")).Text);
// 123 :(

Я бы ожидал, что кеш будет неизменяемым, возможно, с помощью сериализации или другого метода клонирования, но я бы не стал об этом беспокоиться!


person dovid    schedule 22.04.2021    source источник
comment
Просто отбрасываю вам ваши идеи... Я полагаю, вы хотели бы создать тот метод клонирования, который вы отметили, и клонирование, входящее и выходящее из кеша. Итак, при настройке -- вы клонируете и устанавливаете в кеше; при получении -- вы получаете из кеша, клонируете и возвращаете. В противном случае, ваша идея сериализации?   -  person jimnkey    schedule 22.04.2021
comment
О, и для сериализации - внутренне ваш кеш может быть ключевой строкой (т. Е. JSON объекта) - конечно, требуется сериализация/десериализация - но вам не нужно создавать эти методы клонирования.   -  person jimnkey    schedule 22.04.2021
comment
Условия поиска, которые вы ищете, описаны в stackoverflow.com/questions/13691612/   -  person Alexei Levenkov    schedule 22.04.2021
comment
Достаточно ли того, что кеш всегда возвращает одни и те же/неизменные данные, или вы действительно ожидаете, что возвращаемые экземпляры будут неизменяемыми (т. е. instance.Text = "123"; должен возвращать ошибку или просто быть неактивным)?   -  person M.Babcock    schedule 23.04.2021
comment
Храните данные кеша в Redis/Couchbase. Это вызовет сериализацию/десериализацию и, таким образом (как побочный эффект), даст вам то, что вы хотите.   -  person mjwills    schedule 23.04.2021
comment
@ М.Бэбкок нет. я хочу разрешить все, но обеспечить исходное кешированное состояние.   -  person dovid    schedule 23.04.2021


Ответы (3)


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

using Newtonsoft.Json;

public class CacheObject<T>
{
    private string serializedValue;

    public CacheObject(T value)
    {
        // TODO: Add-in serializer settings as needed
        this.serializedValue = JsonConvert.SerializeObject(value);
    }

    public T Value
    {
        get
        {
            // TODO: Add-in serializer settings as needed
            return JsonConvert.DeserializeObject<T>(this.serializedValue);
        }
    }
}

public static class CacheExtensions
{
    public static void Set<T>(this ObjectCache cache, string key, T value, CacheItemPolicy policy)
    {
        cache.Set(key, new CacheObject<T>(value), policy);
    }

    public static T Get<T>(this ObjectCache cache, string key)
    {
        return (T)(cache.Get(key)?.Value);
    }
}

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

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

person M.Babcock    schedule 22.04.2021
comment
Спасибо, я упомянул об этом в своем вопросе, просто надоело мне отвечать за это. Я могу одинаково управлять почти всем кешем... Я ожидаю, что MemoryCache или любой другой авторитетный класс даст мне эту функцию (следует отметить, что JSON как строка является дорогим форматом в объеме памяти, у него есть решения, но это просто начало вещей, с которыми нужно иметь дело). - person dovid; 23.04.2021

Измените SomeClass, чтобы текст был установлен в конструкторе конфиденциально, например:

public class SomeClass
{
    public string Text { get; private set; }

    public SomeClass(string text)
    {
        Text = text;
    }
}
person Matthew M.    schedule 22.04.2021
comment
Во-первых, я хочу предотвратить изменение вовсе не изменения, а изменение источника в кеше. И это не про конкретный объект, я хочу иметь возможность кэшировать любой тип объекта. - person dovid; 22.04.2021
comment
Затем вы можете сделать общую реализацию вашего класса кеша. public class MyCache<T> where T: class, new() { public readonly T MyCacheObject; public MyCache(T objectToCache) { MyCacheObject = objectToCache; } } - person Matthew M.; 22.04.2021

Вот идея, что вы можете сделать это:

Используйте интерфейс для определения поведения кеша:

public interface ICache
{
    ...
    void Set(string key, object value, some other parameters);
    object Get(string key);
    ...
}

Реализуйте кеш только для чтения (или как хотите):

public class ReadOnlyCache : ICache
{
    ...
    void Set(string key, object value, some other parameters)
    {
        ...
        // Of cause, the DeepClone() method can be anyone that makes a copy of the instance.
        this.cache.Set(key, value.DeepClone(), some other parameters);
        ...
    }

    object Get(string key)
    {
        ...
        var value = this.cache.Get(key);
        // Of cause, the DeepClone() method can be anyone that makes a copy of the instance.
        return value.DeepClone();
    }
    ...
}
person Gellio Gao    schedule 22.04.2021