Por que a classe XML-Serializable precisa de um construtor sem parâmetros

173

Estou escrevendo código para fazer serialização de XML. Com função abaixo.

public static string SerializeToXml(object obj)
{
    XmlSerializer serializer = new XmlSerializer(obj.GetType());
    using (StringWriter writer = new StringWriter())
    {
        serializer.Serialize(writer, obj);
        return writer.ToString();
    }
}

Se o argumento for uma instância da classe sem construtor sem parâmetros, lançará uma exceção.

Exceção sem tratamento: System.InvalidOperationException: CSharpConsole.Foo não pode ser serializado porque não possui um construtor sem parâmetros. em System.Xml.Serialization.TypeDesc.CheckSupported () em System.Xml.Serialization.TypeScope.GetTypeDesc (Type type, MemberInfo sourc e, Boolean directReference, Boolean throwOnError) em System.Xml.Serialization.ModelScope.GetTypeModel (Type type, Referência direta booleana) em System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Tipo de tipo, raiz XmlRootAttribute, String defaultNamespace) em System.Xml.Serialization.XmlSerializer..ctor (Tipo de tipo, String defaultName space) em System.Xml.Serialization. XmlSerializer..ctor (tipo Type)

Por que deve haver um construtor sem parâmetros para permitir que a serialização xml seja bem-sucedida?

EDIT: obrigado pela resposta de cfeduke. O construtor sem parâmetros pode ser privado ou interno.

Morgan Cheng
fonte
1
Se você estiver interessado, descobri como criar objetos sem precisar do construtor (consulte a atualização) - mas isso não ajuda em nada o XmlSerializer - ele ainda exige. Útil para código personalizado, talvez.
Marc Gravell
1
XmlSerializerrequer um construtor sem parâmetros padrão para desserialização.
Amit Kumar Ghosh

Respostas:

243

Durante a desserialização de um objeto, a classe responsável pela desserialização de um objeto cria uma instância da classe serializada e passa a preencher os campos e propriedades serializados somente depois de adquirir uma instância a ser preenchida.

Você pode criar seu construtor privateou, internalse quiser, desde que não tenha parâmetros.

cfeduke
fonte
1
Ah, então, posso tornar o ctor sem parâmetros privado ou interno e a serialização ainda funciona. Obrigado pela sua resposta.
Morgan Cheng
2
Sim, eu faço isso com frequência, apesar de aceitar que construtores públicos sem parâmetros são ótimos porque permitem que você use "new ()" com genéricos e com a nova sintaxe de inicialização. Para construtores parametrizados, use métodos estáticos de fábrica ou a implementação do padrão do construtor.
cfeduke 6/11/08
14
A dica de acessibilidade é boa, mas sua explicação não faz sentido para serialização. Um objeto precisa ser criado apenas para desserialização. Eu arriscaria adivinhar que o código de verificação de tipo está embutido no construtor XmlSerializer porque uma única instância pode ser usada nos dois sentidos.
Tomer Gabel
7
@jwg Um exemplo é quando você está enviando seu XML para um serviço da Web de algum tipo e não está interessado em receber esses objetos em seu próprio componente.
precisa saber é o seguinte
5
Lembre-se de que, mesmo que você faça seu construtor sem parâmetros privateou internal, todas as suas propriedades cujos valores foram serializados devem ter publicsetters.
chrnola
75

Esta é uma limitação de XmlSerializer. Observe que BinaryFormattere DataContractSerializer não exige isso - eles podem criar um objeto não inicializado a partir do éter e inicializá-lo durante a desserialização.

Como você está usando xml, considere usar DataContractSerializere marcar sua classe com [DataContract]/ [DataMember], mas observe que isso altera o esquema (por exemplo, não há equivalente a [XmlAttribute]- tudo se torna elementos).

Atualização: se você realmente quer saber, BinaryFormatteret al use FormatterServices.GetUninitializedObject()para criar o objeto sem chamar o construtor. Provavelmente perigoso; Não recomendo usá-lo com muita frequência ;-p Consulte também as observações no MSDN:

Como a nova instância do objeto é inicializada como zero e nenhum construtor é executado, o objeto pode não representar um estado considerado válido por esse objeto. O método atual deve ser usado apenas para desserialização quando o usuário pretende preencher imediatamente todos os campos. Ele não cria uma sequência não inicializada, pois a criação de uma instância vazia de um tipo imutável não serve para nada.

Eu tenho meu próprio mecanismo de serialização, mas não pretendo usá-lo FormatterServices; Eu gosto de saber que um construtor ( qualquer construtor) realmente executou.

Marc Gravell
fonte
Obrigado pela dica sobre FormatterServices.GetUninitializedObject (Type). :)
Omer van Kloeten
6
Heh; Acontece que eu não sigo meu próprio conselho; O protobuf-net (opcionalmente) permite o FormatterServicesuso por idades
Marc Gravell
1
Mas o que eu não entendo é que, no caso de nenhum construtor ser especificado, o compilador cria um construtor público sem parâmetros. Então, por que isso não é bom o suficiente para o mecanismo de desserialização de xml?
toddmo
Se eu quiser desserializar XML e inicializar determinado objeto usando seu construtor (para que os elementos / atributos sejam fornecidos por meio do construtor), existe alguma maneira de conseguir isso? Não existe uma maneira de personalizar o processo de serialização para que ele construa os objetos usando seus construtores?
Shimmy Weitzhandler
1
@Shimmy nope; isso não é suportado. Não é IXmlSerializable , mas um: o que acontece depois de o construtor, e b: é muito feio e difícil de obter direito (especialmente desserialização) - Eu recomendo fortemente contra a tentar implementar isso, mas: não vai permitir que você usar construtores
Marc Gravell
4

A resposta é: sem nenhuma boa razão.

Ao contrário do nome, a XmlSerializerclasse é usada não apenas para serialização, mas também para desserialização. Ele executa certas verificações em sua classe para garantir que funcione, e algumas são pertinentes apenas à desserialização, mas as executam de qualquer maneira, porque não sabem o que você pretende fazer posteriormente.

A verificação que sua classe falha na aprovação é uma das que são pertinentes apenas à desserialização. Aqui está o que acontece:

  • Durante a desserialização, a XmlSerializerclasse precisará criar instâncias do seu tipo.

  • Para criar uma instância de um tipo, um construtor desse tipo precisa ser chamado.

  • Se você não declarou um construtor, o compilador já forneceu um construtor sem parâmetros padrão, mas se você declarou um construtor, esse é o único construtor disponível.

  • Portanto, se o construtor que você declarou aceitar parâmetros, a única maneira de instanciar sua classe é invocando o construtor que aceita parâmetros.

  • No entanto, XmlSerializernão é capaz de chamar qualquer construtor, exceto um construtor sem parâmetros, porque não sabe quais parâmetros passar para os construtores que aceitam parâmetros. Portanto, ele verifica se sua classe possui um construtor sem parâmetros e, como não possui, falha.

Portanto, se a XmlSerializerclasse tivesse sido escrita de forma a executar apenas as verificações pertinentes à serialização, sua classe passaria, porque não há absolutamente nada na serialização que torne necessário um construtor sem parâmetros.

Como outros já apontaram, a solução rápida para o seu problema é simplesmente adicionar um construtor sem parâmetros. Infelizmente, também é uma solução suja, porque significa que você não pode ter nenhum readonlymembro inicializado a partir de parâmetros do construtor.

Além de tudo isso, a XmlSerializerclasse poderia ter sido escrita de forma a permitir uma desserialização uniforme de classes sem construtores sem parâmetros. Tudo o que seria necessário seria fazer uso do "The Factory Method Design Pattern" (Wikipedia) . Pelo que parece, a Microsoft decidiu que esse padrão de design é avançado demais para programadores DotNet, que aparentemente não devem ser desnecessariamente confundidos com essas coisas. Portanto, os programadores do DotNet devem se ater melhor aos construtores sem parâmetros, de acordo com a Microsoft.

Mike Nakis
fonte
Lol você diz, For no good reason whatsoever,depois continua: XmlSerializer is not capable of invoking any constructor except a parameterless constructor, because it does not know what parameters to pass to constructors that accept parameters.Se ele não sabe quais parâmetros passar para um construtor, como saberia quais parâmetros passar para uma fábrica? Ou qual fábrica usar? Não consigo imaginar essa ferramenta mais simples de usar - você deseja que uma classe seja desserializada, deixe o desserializador criar uma instância padrão e preencha cada campo que você marcou. Fácil.
Chuck
0

Primeiro de tudo, é isso que está escrito na documentação . Eu acho que é um dos campos da sua classe, não o principal - e como você deseja que o desserializador o construa de volta sem a construção sem parâmetros?

Eu acho que existe uma solução alternativa para tornar o construtor privado.

Dmitry Khalatov
fonte