Casting anonymous type from type Object

4.9k views Asked by At

I’m trying to utilise the System.Runtime.Caching.MemoryCache class in .NET 4.0. I have a method that is generic so I can pass any type into the memory cache and get it back when invoked.

The method returns an object of type object which is an anonymous type with a field Value which contains the cached object.

My question is, how can I cast the object I’m getting back into its corresponding type?

Below is my code…

public static class ObjectCache
{
    private static MemoryCache _cache = new MemoryCache("GetAllMakes");

    public static object GetItem(string key)
    {
        return AddOrGetExisting(key, () => InitialiseItem(key));
    }

    private static T AddOrGetExisting<T>(string key, Func<T> valueFactory)
    {
        var newValue = new Lazy<T>(valueFactory);
        var oldValue = _cache.AddOrGetExisting(key, newValue, new CacheItemPolicy()) as Lazy<T>;

        try
        {
            return (oldValue ?? newValue).Value;
        }
        catch
        {
            _cache.Remove(key);
            throw;
        }
    }

    /// <summary>
    /// How can i access Value and cast to type "List<IBrowseStockVehicle>"
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    private static object InitialiseItem(string key)
    {
        // SearchVehicleData.GetAllMakes(false) is of type List<IBrowseStockVehicle>
        return new { Value = SearchVehicleData.GetAllMakes(false) };
    }
}

and the unit test...

    [TestMethod]
    public void TestGetAllMakes_Cached()
    {
        dynamic ReturnObj = ObjectCache.GetItem("GetAllMakes");

        // *********************************************
        // cannot do this as tester is of type Object and doesnt have teh field Value
        foreach(IBrowseStockVehicle item in ReturnObj.Value)
        {

        }
    }
3

There are 3 answers

0
Matías Fidemraizer On BEST ANSWER

My question is, how can I cast the object I’m getting back into its corresponding type?

You can't do this! Anonymous types are anonymous from a high-level/semantic point of view (i.e. you can't cast to an unknown type, can you?), and they're internal and with a random name from a low level point of view. That is, they're inaccessible.

I can suggest you two approaches:

Dynamic objects to the rescue

In your question you said that you can't access a property of object, but you can implement a simple DynamicObject to access any object property dynamically:

public sealed class DynamicWrapper : DynamicObject
{
    public DynamicWrapper(object target)
    {
        Target = target;

        // We store property names and property metadata in a dictionary
        // to speed up things later (we'll find if a requested
        // property exists with a time complexity O(1)!)
        TargetProperties = target.GetType()
                                    .GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                    .ToDictionary(p => p.Name, p => p);

    }

    private IDictionary<string, PropertyInfo> TargetProperties { get; }
    private object Target { get; }


    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        // We don't support setting properties!
        throw new NotSupportedException();
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        PropertyInfo property;

        if(TargetProperties.TryGetValue(binder.Name, out property))
        {
            result = property.GetValue(Target); 

            return true;
        }
        else

        {
            result = null;

            return false;
        }
    }
}

And use the whole wrapper as follows:

var obj = new { Text = "hello world" };

dynamic dynObj = new DynamicWrapper(obj);
string text = dynObj.Text;

Conclusion

  • Store and retrieve your cached object wrapped with something like DynamicWrapper and it will work as you expect!

  • Otherwise use dictionaries.

  • Or, like other answerers have already said, don't use anonymous type and store concrete types.

0
Peter B On

You'd better use generics all over the place instead of just for AddOrGetExisting<T>.

Also, better not make the Cache responsible for creating new objects. It should be a utility class, it should adhere to the Single Responsibility principle, and it should not have links to your business or data layers.


As an example I will add a class that I use for MVC. It does not use MemoryCache, instead it uses HttpRuntime.Cache so it may not be the answer you need, but it could guide you towards a better solution with respect to using generics and the Single Responsibility principle.

namespace Xyz.WebLibrary
{
    public static class Cache
    {
        // Get the value from the HttpRuntime.Cache that was stored using the cacheKey (if any). Returns true if a matching object of requested type T was found in the cache. Otherwise false is returned, along with a default(T) object or value.
        public static bool Get<T>(string cacheKey, out T result)
        {
            if (!string.IsNullOrEmpty(cacheKey))
            {
                object o = HttpRuntime.Cache.Get(cacheKey);
                if (o != null && o is T)
                {
                    result = (T)o;
                    return true;
                }
            }
            result = default(T);
            return false;
        }

        // Store a value in the HttpRuntime.Cache using the cacheKey and the specified expiration time in minutes.
        public static void Set(string cacheKey, object o, int slidingMinutes)
        {
            if (!string.IsNullOrEmpty(cacheKey) && slidingMinutes > 0)
                HttpRuntime.Cache.Insert(cacheKey, o, null, DateTime.MaxValue, TimeSpan.FromMinutes(slidingMinutes), CacheItemPriority.Normal, null);
        }

        // Erase the value from the HttpRuntime.Cache that was stored using the cacheKey (if any).
        public static void Erase(string cacheKey)
        {
            if (!string.IsNullOrEmpty(cacheKey) && HttpRuntime.Cache.Get(cacheKey) != null)
                HttpRuntime.Cache.Remove(cacheKey);
        }
    }
}

Usage:

ProductInfo p;
int id = 12345;
string key = "ProductInfo_" + id;
if (!Cache.Get(key, out p))
{
    p = GetProductInfoFromDB(id);
    Cache.Set(key, p, slidingMinutes: 5);
}
0
Patrick Hofman On

You can't. Anonymous types are... anonymous. They have no type name you can use, so use a type instead.

You could still use Reflection of course, but that might not be really usable in this case:

var x = ReturnObj.GetType().GetProperty("Value").GetValue(ReturnObj);