Ошибка производительности XmlSerializer при указании XmlRootAttribute
В настоящее время у меня очень странная проблема, и я не могу понять, как ее решить.
У меня довольно сложный тип, который я пытаюсь сериализовать с помощью класса XmlSerializer. Это фактически отлично работает, и тип сериализуется правильно, но, похоже, занимает очень много времени; около 5 секунд в зависимости от данных в объекте.
После небольшого профилирования я сузил проблему вниз - причудливо - чтобы указать XmlRootAttribute при вызове XmlSerializer.Serialize. Я делаю это, чтобы изменить имя коллекции, сериализованной из ArrayOf, на нечто более значимое. Как только я удаляю параметр, операция почти мгновенная!
Любые мысли или предложения были бы превосходными, поскольку я полностью зациклен на этом!
Ответы
Ответ 1
Просто для всех, кто сталкивается с этой проблемой; с ответом выше и примером из MSDN мне удалось решить эту проблему, используя следующий класс:
public static class XmlSerializerCache
{
private static readonly Dictionary<string, XmlSerializer> cache =
new Dictionary<string, XmlSerializer>();
public static XmlSerializer Create(Type type, XmlRootAttribute root)
{
var key = String.Format(
CultureInfo.InvariantCulture,
"{0}:{1}",
type,
root.ElementName);
if (!cache.ContainsKey(key))
{
cache.Add(key, new XmlSerializer(type, root));
}
return cache[key];
}
}
Затем вместо использования конструктора XmlSerializer по умолчанию, который принимает XmlRootAttribute, вместо этого я использую следующее:
var xmlRootAttribute = new XmlRootAttribute("ExampleElement");
var serializer = XmlSerializerCache.Create(target.GetType(), xmlRootAttribute);
Теперь мое приложение снова выполняется!
Ответ 2
Как упоминалось в последующем комментарии к исходному вопросу,.NET испускает сборки при создании XmlSerializers и кэширует сгенерированную сборку, если она создается с использованием одного из этих двух конструкторов:
XmlSerializer(Type)
XmlSerializer(Type, String)
Ассембли, сгенерированные с использованием других конструкторов, не кэшируются, поэтому .NET должен каждый раз генерировать новые сборки.
Почему? Этот ответ, вероятно, не очень приятен, но, глядя на него в Reflector, вы можете видеть, что ключ, используемый для хранения и доступа к сгенерированным сборкам XmlSerializer
(TempAssemblyCacheKey
), представляет собой простой составной ключ, построенный из сериализуемого типа и (необязательно) его пространство имен.
Таким образом, нет механизма, чтобы определить, имеет ли кешированный XmlSerializer
для SomeType
специальный XmlRootAttribute
или стандартный.
Трудно думать о технической причине, что ключ не мог разместить больше элементов, поэтому это, вероятно, просто функция, которую никто не успел реализовать (особенно потому, что он включал бы изменение в противном случае стабильных классов).
Возможно, вы видели это, но в случае, если вы этого не сделали, документация XmlSerializer
обсуждает обходной путь:
Если вы используете какой-либо другой конструкторы, несколько версий сгенерированы одна и та же сборка выгружается, что приводит к утечки и низкой производительности. Простейший решение заключается в использовании одного из ранее упомянутые два конструктора. В противном случае вы должны кэшировать сборок в Hashtable,
, как показано в в следующем примере.
(здесь я опущен здесь)
Ответ 3
Просто нужно было реализовать что-то подобное и использовать немного более оптимизированную версию решения @Dougc с удобной перегрузкой:
public static class XmlSerializerCache {
private static readonly Dictionary<string, XmlSerializer> cache = new Dictionary<string, XmlSerializer>();
public static XmlSerializer Get(Type type, XmlRootAttribute root) {
var key = String.Format("{0}:{1}", type, root.ElementName);
XmlSerializer ser;
if (!cache.TryGetValue(key, out ser)) {
ser = new XmlSerializer(type, root);
cache.Add(key, ser);
}
return ser;
}
public static XmlSerializer Get(Type type, string root) {
return Get(type, new XmlRootAttribute(root));
}
}
Ответ 4
Ниже описана более сложная реализация здесь. Однако проект больше не активен.
Соответствующие классы видны здесь:
http://mvpxml.codeplex.com/SourceControl/changeset/view/64156#258382
В частности, может понадобиться следующая функция для генерации уникального ключа:
public static string MakeKey(Type type
, XmlAttributeOverrides overrides
, Type[] types
, XmlRootAttribute root
, String defaultNamespace) {
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.Append(type.FullName);
keyBuilder.Append("??");
keyBuilder.Append(SignatureExtractor.GetOverridesSignature(overrides));
keyBuilder.Append("??");
keyBuilder.Append(SignatureExtractor.GetTypeArraySignature(types));
keyBuilder.Append("??");
keyBuilder.Append(SignatureExtractor.GetXmlRootSignature(root));
keyBuilder.Append("??");
keyBuilder.Append(SignatureExtractor.GetDefaultNamespaceSignature(defaultNamespace));
return keyBuilder.ToString();
}