0

How can I serve an object from the cache, without fear that the code using the object will change the source in the cache?

Example:

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 :(

I would expect the cache to be unchangeable, maybe by serialization or another clone method, but that I would not worry about it!

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
dovid
  • 6,354
  • 3
  • 33
  • 73
  • Just tossing your ideas back at you... I believe you would want to create that clone method you noted and clone coming in and going out of the cache. So when setting -- you clone and set in cache; when getting -- you get from cache, clone and return. Otherwise, your serialization idea? – jimnkey Apr 22 '21 at 19:24
  • Oh -- and for serialization -- internally you might have your cache be key->string (i.e., json of object) - sure serialize/deserialize required -- but you don't have to create those cloning methods. – jimnkey Apr 22 '21 at 19:30
  • Search terms you are looking for covered in https://stackoverflow.com/questions/13691612/how-to-freeze-a-popsicle-in-net-make-a-class-immutable – Alexei Levenkov Apr 22 '21 at 20:17
  • Is it enough that the cache always returns the same/unchanged data or do you truly expect the instances returned to be immutable (i.e. `instance.Text = "123";` should return an error or simply be no-op)? – M.Babcock Apr 22 '21 at 22:56
  • 1
    Store the cache data in Redis / Couchbase. This will force serialisation / deserialisation, and thus (as a side effect) give you what you want. – mjwills Apr 22 '21 at 23:27
  • @M.Babcock no. i want allow all, but ensure the original cached state. – dovid Apr 23 '21 at 08:40

3 Answers3

1

If you only need the cache to return the same value as it was cached, you could serialize the object and deserialize it to get the original values out. The following code is untested, but I've used the idea before for other purposes:

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);
    }
}

If you actually want the objects returned to be immutable (meaning changing their value should fail or be no-op) then there's no way to accomplish that generically as you say you want. One option that comes to mind is using an abstract, read-only abstract base class that you store in the cache and create a non-abstract child class that you use when you need the data to be writable.

As suggested by the answers in the question recommended in the comments by Alexei, another option is to implement a write-once class but that on its own is not a small feat and may not provide the flexibility you require.

M.Babcock
  • 18,753
  • 6
  • 54
  • 84
  • Thanks, I mentioned this way in my question, just bothering me to be in charge of it. I can equally manage almost the entire cache... I expect `MemoryCache` or any other reputable class to give me this feature (it should be noted that JSON as string is an expensive format in memory volume, it does have solutions, but this is just the beginning of things to deal with). – dovid Apr 23 '21 at 08:46
0

Modify SomeClass so the text is set in the constructor, privately, like so:

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

    public SomeClass(string text)
    {
        Text = text;
    }
}
Matthew M.
  • 932
  • 10
  • 17
  • First I do not want to prevent change at all, but a change in the **source** in the cache. And this is not about a particular object, I want to be able to cache any type of object. – dovid Apr 22 '21 at 19:12
  • Then you can make a generic implementation of your cache class. `public class MyCache where T: class, new() { public readonly T MyCacheObject; public MyCache(T objectToCache) { MyCacheObject = objectToCache; } }` – Matthew M. Apr 22 '21 at 19:23
0

Here is an idea that you can do this:

Use an interface to define the cache behave:

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

Implement a read-only(or somehow you want) cache:

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();
    }
    ...
}
Gellio Gao
  • 835
  • 6
  • 19