Problema de desempenho XmlSerializer ao especificar XmlRootAttribute

Atualmente estou tendo um problema muito estranho e não consigo descobrir como resolvê-lo.

Eu tenho um tipo bastante complexo que estou tentando serializar usando a class XmlSerializer. Isso realmente funciona bem e o tipo é serializado corretamente, mas parece demorar muito tempo para isso; cerca de 5 segundos, dependendo dos dados no object.

Depois de um pouco de criação de perfil, reduzi a questão – de forma bizarra – à especificação de um XmlRootAttribute ao chamar XmlSerializer.Serialize. Eu faço isso para mudar o nome de uma coleção sendo serializada de ArrayOf para algo um pouco mais significativo. Depois de remover o parâmetro, a operação é quase instantânea!

Qualquer pensamento ou sugestão seria excelente, pois estou completamente perplexo com este!

Apenas para qualquer outra pessoa que tenha esse problema; armado com a resposta acima e o exemplo do MSDN consegui resolver esse problema usando a seguinte class:

public static class XmlSerializerCache { private static readonly Dictionary cache = new Dictionary(); 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]; } } 

Em vez de usar o construtor XmlSerializer padrão que leva um XmlRootAttribute, eu uso o seguinte em vez disso:

 var xmlRootAttribute = new XmlRootAttribute("ExampleElement"); var serializer = XmlSerializerCache.Create(target.GetType(), xmlRootAttribute); 

Minha aplicação agora está se apresentando novamente!

Como mencionado no comentário de acompanhamento da pergunta original, o .NET emite assemblies ao criar XmlSerializers e armazena em cache o assembly gerado se ele for criado usando um desses dois construtores:

 XmlSerializer(Type) XmlSerializer(Type, String) 

Os assemblies gerados usando os outros construtores não são armazenados em cache, portanto, o .NET precisa gerar novos assemblies todas as vezes.

Por quê? Esta resposta provavelmente não é muito satisfatória, mas observando isso no Reflector, você pode ver que a chave usada para armazenar e acessar os assemblies XmlSerializer gerados ( TempAssemblyCacheKey ) é apenas uma simples chave composta construída a partir do tipo serializável e (opcionalmente) namespace.

Portanto, não há nenhum mecanismo para dizer se um XmlSerializer cache para SomeType tem um XmlRootAttribute especial ou o padrão.

É difícil pensar em uma razão técnica de que a chave não pudesse acomodar mais elementos, portanto, esse provavelmente é apenas um recurso que ninguém teve tempo de implementar (especialmente porque envolveria a mudança de classs de outra forma estáveis).

Você pode ter visto isso, mas caso você não tenha, a documentação da class XmlSerializer discute uma solução alternativa:

Se você usar qualquer um dos outros construtores, várias versões do mesmo assembly serão geradas e nunca serão descarregadas, resultando em um memory leaks e desempenho insatisfatório. A solução mais fácil é usar um dos dois construtores mencionados anteriormente. Caso contrário, você deve armazenar em cache os assemblies em uma Hashtable, conforme mostrado no exemplo a seguir.

(Eu omiti o exemplo aqui)

Apenas tive que implementar algo assim e usei uma versão um pouco mais otimizada da solução do @ Dougc com uma sobrecarga de conveniência:

 public static class XmlSerializerCache { private static readonly Dictionary cache = new Dictionary(); 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)); } } 

Há uma implementação mais complexa explicada aqui . No entanto, o projeto não está mais ativo.

As classs relevantes são visíveis aqui: http://mvpxml.codeplex.com/SourceControl/changeset/view/64156#258382

Em particular, a seguinte function para gerar uma chave única pode ser útil:

 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(); }