2009-10-07 3 views
17

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

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

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

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

+1

Хорошо, похоже, проблема та t сборка сериализации создается для каждого экземпляра сериализатора, если вы укажете для сериализатора ничего, кроме параметра типа! Вот почему, я полагаю, я вижу такое ужасное выступление. Кто-нибудь знает причину, по которой XmlSerializer по умолчанию будет делать это? Я не понимаю, почему просто указание имени корневого узла означает, что кеш нельзя использовать? – Dougc

ответ

23

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

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

+0

+1, короткие и сладкие. –

+6

Небольшая небольшая оптимизация. TryGet значение в if-clause, если вы собираетесь делать много этих поисков. ContainsKey более эффективен, если вы только хотите знать, существует ли элемент, но не получить его, но поскольку вы всегда возвращаете значение, попробуйте лучше: http://dotnetperls.com/dictionary-lookup –

+0

Затем вы также можете улучшить " return ", поскольку у вас уже есть экземпляр в переменной и не нужно искать через" cache [key] " – Sielu

18

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

XmlSerializer(Type) 
XmlSerializer(Type, String) 

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

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

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

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

Вы, возможно, видели это, но в случае, если у вас нет, the XmlSerializer class documentation обсуждает обходной путь:

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

(я опустил пример здесь)

+0

Отлично. Спасибо за помощь Джеффа. Я понятия не имею, почему я не читал документацию MSDN, doh! – Dougc

+0

Есть ли примеры использования этого HashTable, где вы не имеете контроля над Xml Serializer? Я думаю конкретно в справочной службе WCF или веб-ссылке? У меня есть эта проблема, но я не могу найти какие-либо способы ее исправить ...! – harrisonmeister

0

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

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

Просто нужно было сделать что-то вроде этого и использовали немного более оптимизированная версия решения @ 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)); 
    } 
} 
Смежные вопросы