Проблема производительности XmlSerializer при указании атрибута XmlRootAttribute - PullRequest
17 голосов
/ 08 октября 2009

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

У меня есть довольно сложный тип, который я пытаюсь сериализовать с использованием класса XmlSerializer. Это на самом деле работает нормально, и тип сериализуется правильно, но, похоже, это займет очень много времени; около 5 секунд в зависимости от данных в объекте.

После небольшого профилирования я сузил проблему - причудливо - до указания атрибута XmlRootAttribute при вызове XmlSerializer.Serialize. Я делаю это, чтобы изменить имя сериализуемой коллекции из ArrayOf на что-то более осмысленное. Как только я уберу параметр, операция будет практически мгновенной!

Любые мысли или предложения были бы превосходны, так как я целиком и полностью озадачен этим!

Ответы [ 4 ]

24 голосов
/ 11 октября 2009

Только для тех, кто сталкивается с этой проблемой; вооружившись ответом выше и примером из 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);

Мое приложение теперь выполняется снова!

18 голосов
/ 09 октября 2009

Как уже упоминалось в последующем комментарии к исходному вопросу, .NET генерирует сборки при создании XmlSerializer и кэширует сгенерированную сборку, если она создана с использованием одного из этих двух конструкторов:

XmlSerializer(Type)
XmlSerializer(Type, String)

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

Почему? Этот ответ, вероятно, не очень удовлетворителен, но, взглянув на это в Reflector, вы можете видеть, что ключ, используемый для хранения и доступа к сгенерированным XmlSerializer сборкам (TempAssemblyCacheKey), представляет собой простой составной ключ, созданный из сериализуемого типа и (опционально) его пространство имен.

Таким образом, нет механизма, позволяющего определить, имеет ли кэшированный XmlSerializer для SomeType специальный XmlRootAttribute или значение по умолчанию.

Трудно придумать техническую причину, по которой ключ не может вместить больше элементов, так что это, вероятно, просто функция, которую никто не успел реализовать (тем более что это повлекло бы за собой изменение в противном случае стабильных классов).

Возможно, вы видели это, но в противном случае в документации XmlSerializer класса обсуждается обходной путь:

Если вы используете какой-либо другой конструкторы, несколько версий одна и та же сборка генерируется и никогда выгружен, что приводит к памяти утечка и низкая производительность. Самый легкий Решение заключается в использовании одного из ранее упоминалось два конструктора. В противном случае вы должны кэшировать сборки в Hashtable,, как показано на следующий пример.

(пример здесь опущен)

1 голос
/ 05 марта 2016

Просто пришлось реализовать что-то подобное и использовать чуть более оптимизированную версию решения @ 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));
    }
}
0 голосов
/ 07 июня 2011

Существует более сложная реализация, объясненная здесь . Однако проект больше не активен.

Соответствующие классы видны здесь: 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();
}
...