缓存反射数据

本文关键字:数据 反射 缓存 | 更新日期: 2023-09-27 18:01:17

缓存从反射获得的昂贵数据的最佳方法是什么?例如,大多数快速序列化器都会缓存这些信息,这样它们就不需要在每次遇到相同类型时都进行反映。它们甚至可能生成一个动态方法,从类型中查找。

.net 4之前的

传统上我使用普通的静态字典。例如:

private static ConcurrentDictionary<Type, Action<object>> cache;
public static DoSomething(object o)
{
    Action<object> action;
    if(cache.TryGetValue(o.GetType(), out action)) //Simple lookup, fast!
    {
        action(o);
    }
    else
    {
        // Do reflection to get the action
        // slow
    }
} 

这会泄漏一些内存,但由于它只对每个类型泄漏一次,并且类型的寿命与AppDomain一样长,所以我不认为这是一个问题。

从。net 4

但是现在。net 4引入了用于动态类型生成的可收集程序集。如果我在可收集程序集中声明的对象上使用了DoSomething,那么该程序集将永远不会被卸载。哎哟.

那么,在。net 4中缓存每种类型信息的最佳方法是什么呢?我能想到的最简单的解决方案是:

private static ConcurrentDictionary<WeakReference, TCachedData> cache.

但是我必须使用的IEqualityComparer<T>会表现得非常奇怪,也可能违反合同。我也不确定查找会有多快。

另一个想法是使用过期超时。这可能是最简单的解决方案,但感觉有点不优雅。


在类型作为泛型参数提供的情况下,我可以使用嵌套的泛型类,这样就不会出现这个问题。但是,如果类型是在变量中提供的,则不起作用。

class MyReflection
{
    internal Cache<T>
    {
        internal static TData data;
    }
    void DoSomething<T>()
    {
        DoSomethingWithData(Cache<T>.data);
        //Obviously simplified, should have similar creation logic to the previous code.
    }
}

更新:我刚刚有一个想法是使用Type.AssemblyQualifiedName作为关键。它应该唯一地标识该类型,而无需将其保存在内存中。我甚至可以在这个字符串上使用引用恒等式。

这个解决方案仍然存在的一个问题是,缓存的值也可能保留对该类型的引用。如果我为此使用弱引用,它很可能会在程序集卸载之前过期。而且我不确定从弱引用中获得正常引用有多便宜。看来我需要做一些测试和基准。

缓存反射数据

ConcurrentDictionary<WeakReference, CachedData>在这种情况下是不正确的。假设我们尝试缓存类型T的信息,所以是WeakReference.Target==typeof(T)。CachedData很可能也包含typeof(T)的引用。由于ConcurrentDictionary<TKey, TValue>将项目存储在Node<TKey, TValue>的内部集合中,您将拥有强引用链:ConcurrentDictionary instance -> Node instance -> Value property (CachedData instance) -> typeof(T)。一般来说,当值可能有对其键的引用时,使用WeakReference是不可能避免内存泄漏的。

有必要添加对ephemerons的支持,以使这种场景在不发生内存泄漏的情况下成为可能。幸运的是。net 4.0支持它们,并且我们有ConditionalWeakTable<TKey, TValue>类。看来引入它的原因与你的任务很接近。

此方法还解决了更新中提到的问题,因为只要加载Assembly,对Type的引用就会存在。

你应该看看fastreflect库

你可以使用正常反射来动态地生成新的代码&然后发出/编译它,然后缓存编译后的版本。我认为集合的想法是有希望的,避免内存泄漏,而不必从一个单独的应用程序域加载/卸载。但是,内存泄漏应该可以忽略不计,除非您正在编译数百个方法。

这是一篇关于在运行时动态编译代码的博文:http://introspectingcode.blogspot.com/2011/06/dynamically-compile-code-at-runtime.html

下面是我过去用来存储MethodInfo/PropertyInfo对象的类似并发字典方法&它看起来确实更快,但我认为那是在Silverlight的旧版本中。我相信。net有它自己的内部反射缓存,这使得它没有必要。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.Concurrent;
namespace NetSteps.Common.Reflection
{
    public static class Reflection
    {
        private static ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>> reflectionPropertyCache = new ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>>();
        public static List<PropertyInfo> FindClassProperties(Type objectType)
        {
            if (reflectionPropertyCache.ContainsKey(objectType))
                return reflectionPropertyCache[objectType].Values.ToList();
            var result = objectType.GetProperties().ToDictionary(p => p.Name, p => p);
            reflectionPropertyCache.TryAdd(objectType, result);
            return result.Values.ToList();
        }
    }
}

我可能在这里说的很明显,但是:

缓存提供程序通常不序列化数据到一个源吗?

那么反序列化过程肯定会比简单地反映一个新实例更昂贵吗?

还是我错过了什么?

关于装箱和拆箱的时间成本有很多争论……我不确定这算不算。

编辑:

这个怎么样(希望这能更好地解释问题)…

Dictionary<string, Type> typecache = new Dictionary<string, Type>();
// finding a type from say a string that points at a type in an assembly not referrenced
// very costly but we can cache that
Type myType = GetSomeTypeWithReflection();
typecache.Add("myType", myType);
// creating an instance you can use very costly
MyThingy thingy = Activator.CreateInstance(typecache["myType"]);

你正在寻找缓存"东西"吗?