Подтвердить что ты не робот

Данные отражения кэширования

Какой лучший способ кэшировать дорогостоящие данные, полученные от отражения? Например, самые быстрые сериализаторы кэшируют такую ​​информацию, поэтому им не нужно отражать каждый раз, когда они снова сталкиваются с одним и тем же типом. Они могут даже генерировать динамический метод, который они ищут из типа.

До .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 как ключ. Это должно однозначно идентифицировать этот тип, не сохраняя его в памяти. Я мог бы даже уйти с использованием ссылочной идентификации в этой строке.

Одна из проблем, которая остается в этом решении, заключается в том, что кешированное значение может также содержать ссылку на этот тип. И если я использую слабую ссылку для этого, она, скорее всего, истечет задолго до того, как сборка будет выгружена. И я не уверен, насколько дешево получить нормальную ссылку из слабой ссылки. Похоже, мне нужно провести тестирование и бенчмаркинг.

4b9b3361

Ответ 1

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 в случае, когда значения могут иметь ссылки на их ключи.

Необходимо было добавить поддержку эфемеронов, чтобы сделать такой сценарий возможным без утечек памяти. К счастью,.NET 4.0 поддерживает их, и у нас есть класс ConditionalWeakTable<TKey, TValue>. Кажется, причины, чтобы представить его, близки к вашей задаче.

Этот подход также решает проблему, указанную в вашем обновлении, поскольку ссылка на Type будет работать точно столько же, сколько будет загружена сборка.

Ответ 2

Вы должны проверить более быстрый библиотека

Вы можете использовать нормальное отражение для динамического создания нового кода & затем сгенерируйте/скомпилируйте его и затем кэшируйте скомпилированную версию. Я думаю, что идея коллекционной сборки является многообещающей, чтобы избежать утечки памяти без необходимости загружать/выгружать из отдельного домена приложения. Однако утечка памяти должна быть незначительной, если вы не компилируете сотни методов.

Вот блог о динамической компиляции кода во время выполнения: 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();
        }

    }
}

Ответ 3

Я мог бы заявить очевидное здесь, но:

Не кэшировать поставщиков обычно сериализуют данные в источник?

Таким образом, процесс десериализации будет более дорогостоящим, чем просто отражением нового экземпляра?

Или я что-то пропустил?

И вот весь аргумент в отношении бокса и времени распаковки... не уверен, действительно ли это имеет значение.

Edit:

Как насчет этого (надеюсь, это объясняет проблему немного лучше)...

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"]);

Вы хотите кэшировать "thingy"?