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