Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement Generic Get<T> for MemoryCache (or any Cache)

I'm trying to write a "simple" Generic Get<T>; extension for System.Runtime.MemoryCache.

Why "simple" ? Because generally I know object's real type before caching it, so when I retrieve it from cache, I'm not going to convert it in unpredictable ways.

For example: if boolean "true" value is stored in cache with cacheKey "id", so

Get<string>("id") == "true";
Get<int>("id") == 1; // any result > 0 is okay
Get<SomeUnpredictableType> == null; // just ignore these trouble conversions

Here's my incomplete implemention:

public static T DoGet<T>(this MemoryCache cache, string key) {
    object value = cache.Get(key);
    if (value == null) {
        return default(T);
    }
    if (value is T) {
        return (T)value;
    }

    // TODO: (I'm not sure if following logic is okay or not)
    // 1. if T and value are both numeric type (e.g. long => double), how to code it?
    // 2. if T is string, call something like Convert.ToString()

    Type t = typeof(T);
    t = (Nullable.GetUnderlyingType(t) ?? t);
    if (typeof(IConvertible).IsAssignableFrom(value.GetType())) {
        return (T)Convert.ChangeType(value, t);
    }
    return default(T);
}

Any suggestions are highly appreciated.

===================================

Update (04/11/2016):

For those nice suggestions given, I implement my first version of Get<T>

public class MemCache {
    private class LazyObject<T> : Lazy<T> {
        public LazyObject(Func<T> valueFactory) : base(valueFactory) { }
        public LazyObject(Func<T> valueFactory, LazyThreadSafetyMode mode) : base(valueFactory, mode) { }
    }

    private static T CastValue<T>(object value) {
        if (value == null || value is DBNull) {
            return default(T);
        }
        Type valType = value.GetType();
        if (valType.IsGenericType && valType.GetGenericTypeDefinition() == typeof(LazyObject<>)) {
            return CastValue<T>(valType.GetProperty("Value").GetValue(value));
        }
        if (value is T) {
            return (T)value;
        }
        Type t = typeof(T);
        t = (Nullable.GetUnderlyingType(t) ?? t);
        if (typeof(IConvertible).IsAssignableFrom(t) && typeof(IConvertible).IsAssignableFrom(value.GetType())) {
            return (T)Convert.ChangeType(value, t);
        }
        return default(T);
    }

    private MemoryCache m_cache;

    public T Get<T>(string key) {
        return CastValue<T>(m_cache.Get(key));
    }

    public void Set<T>(string key, T value, CacheDependency dependency) {
        m_cache.Set(key, value, dependency.AsCacheItemPolicy());
    }

    public T GetOrAdd<T>(string key, Func<T> fnValueFactory, CacheDependency dependency) {
        LazyObject<T> noo = new LazyObject<T>(fnValueFactory, LazyThreadSafetyMode.ExecutionAndPublication);
        LazyObject<T> old = m_cache.AddOrGetExisting(key, noo, dependency.AsCacheItemPolicy()) as LazyObject<T>;
        try {
            return CastValue<T>((old ?? noo).Value);
        } catch {
            m_cache.Remove(key);
            throw;
        }
    }

    /* Remove/Trim ... */
}
like image 901
ineztia Avatar asked Apr 07 '16 04:04

ineztia


2 Answers

The essential work is to write a CastValue<T> to convert any object to desired type. And it doesn't have to handle very complicate condition because object types in cache is predictable for the programmer. And here's my version.

public static T CastValue<T>(object value) {
    if (value == null || value is DBNull) {
        return default(T);
    }
    if (value is T) {
        return (T)value;
    }
    Type t = typeof(T);
    t = (Nullable.GetUnderlyingType(t) ?? t);
    if (typeof(IConvertible).IsAssignableFrom(t) && typeof(IConvertible).IsAssignableFrom(value.GetType())) {
        return (T)Convert.ChangeType(value, t);
    }
    return default(T);
}
like image 65
ineztia Avatar answered Nov 08 '22 10:11

ineztia


Proposal:

public static T DoGet<T>(this MemoryCache cache, string key) 
{
    object value = cache.Get(key);
    if (value == null) {
        return default(T);
    }
    // support for nullables. Do not waste performance with 
    // type conversions if it is not a nullable.
    var underlyingType = Nullable.GetUnderlyingType(t);
    if (underlyingType != null)
    {
        value = Convert.ChangeType(value, underlyingType);
    }
    return (T)value;
}

Usage (supposed you have an id of type int in the cache):

int id = Get<int>("id"); 
int? mayBeId = Get<int?>("id");
string idAsString = Get<int?>("id")?.ToString();
double idAsDouble = (double)Get<int>("id"); 

I haven't test it.

like image 22
Stefan Steinegger Avatar answered Nov 08 '22 10:11

Stefan Steinegger