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

Ошибка производительности XmlSerializer при указании XmlRootAttribute

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

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

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

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

4b9b3361

Ответ 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();
}