从类型 Object 转换匿名类型

Casting anonymous type from type Object

我正在尝试使用 .NET 4.0 中的 System.Runtime.Caching.MemoryCache class。我有一个通用方法,因此我可以将任何类型传递到内存缓存并在调用时取回它。

方法returns object 类型的对象,它是一个匿名类型,具有包含缓存对象的字段 Value。

我的问题是,我怎样才能将我要返回的对象转换成它对应的类型?

下面是我的代码…

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

和单元测试...

    [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)
        {

        }
    }

你不能。匿名类型是……匿名的。它们没有您可以使用的类型名称,因此请改用类型。

当然你仍然可以使用反射,但在这种情况下可能无法真正使用:

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

你最好在所有地方都使用泛型,而不仅仅是 AddOrGetExisting<T>

另外,最好不要让缓存负责创建新对象。它应该是一个实用程序 class,它应该遵守单一职责原则,并且它不应该链接到您的业务或数据层。


作为示例,我将添加用于 MVC 的 class。它不使用 MemoryCache,而是使用 HttpRuntime.Cache,因此它可能不是您需要的答案,但它可以指导您在使用泛型和单一职责原则方面找到更好的解决方案。

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

用法:

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

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

你不能这样做!从 high-level/semantic 的角度来看,匿名类型是 anonymous(即你不能转换为未知类型,对吗?),并且它们是内部的并且具有随机性从低层次的角度命名。也就是说,它们无法访问

我可以向您推荐两种方法:

  • 将整个对象转换为字典并使用键访问其属性。我很久以前做的一些答案对于像您这样的简单案例应该对这种情况有用:Mapping object to dictionary and vice versa and How to convert class into Dictionary<string,string>?

  • 动态对象来拯救!

动态对象来拯救

在你的问题中你说你不能访问 object 的 属性,但是你可以实现一个简单的 DynamicObject 来动态访问任何对象 属性 :

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

并按如下方式使用整个包装器:

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

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

结论

  • 存储和检索用 DynamicWrapper 之类的东西包裹的缓存对象,它会按您预期的那样工作!

  • 否则用字典。

  • 或者像其他回答者已经说过的那样,不要使用匿名类型并存储具体类型。