ineztia
ineztia

Reputation: 825

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 ... */
}

Upvotes: 10

Views: 4592

Answers (2)

Stefan Steinegger
Stefan Steinegger

Reputation: 64638

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.

Upvotes: 2

ineztia
ineztia

Reputation: 825

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

Upvotes: 4

Related Questions