Perché la classe serializzabile XML ha bisogno di un costruttore senza parametri


173

Sto scrivendo codice per eseguire la serializzazione Xml. Con la funzione di seguito.

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 l'argomento è un'istanza di classe senza costruttore senza parametri, genererà un'eccezione.

Eccezione non gestita: System.InvalidOperationException: CSharpConsole.Foo non può essere serializzato perché non ha un costruttore senza parametri. at System.Xml.Serialization.TypeDesc.CheckSupported () at System.Xml.Serialization.TypeScope.GetTypeDesc (Type type, MemberInfo sourc e, Boolean directReference, Boolean throwOnError) at System.Xml.Serialization.ModelScope.GetTypeModel (Type, Riferimento diretto booleano) su System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Tipo di tipo, radice XmlRootAttribute, String defaultNamespace) su System.Xml.Serialization.XmlSerializer..ctor (Tipo di tipo, String defaultName space) su System.Xml.Serialization. XmlSerializer..ctor (Tipo di tipo)

Perché deve esistere un costruttore senza parametri per consentire la serializzazione XML?

EDIT: grazie per la risposta di cfeduke. Il costruttore senza parametri può essere privato o interno.


1
Se sei interessato, ho scoperto come creare oggetti senza bisogno del costruttore (vedi aggiornamento) - ma questo non aiuterà affatto XmlSerializer - lo richiede comunque. Utile per il codice personalizzato, forse.
Marc Gravell

1
XmlSerializerrichiede un costruttore senza parametri predefinito per la deserializzazione.
Amit Kumar Ghosh,

Risposte:


243

Durante la deserializzazione di un oggetto, la classe responsabile della deserializzazione di un oggetto crea un'istanza della classe serializzata e procede quindi a popolare i campi e le proprietà serializzati solo dopo aver acquisito un'istanza da popolare.

Puoi creare il tuo costruttore privateo, internalse lo desideri, purché non abbia parametri.


1
Oh, quindi, posso rendere il ctor senza parametri privato o interno e la serializzazione funziona ancora. Grazie per la tua risposta.
Morgan Cheng,

2
Sì, lo faccio spesso, anche se sono arrivato ad accettare che i costruttori pubblici senza parametri sono fantastici perché ti permettono di usare "new ()" con generics e la nuova sintassi di inizializzazione. Per i costruttori con parametri utilizzare metodi factory statici o l'implementazione del pattern builder.
cfeduke,

14
Il suggerimento sull'accessibilità è buono, ma la tua spiegazione non ha senso per la serializzazione. Un oggetto deve essere creato solo per la serializzazione. Immagino che il codice di verifica del tipo sia incorporato nel costruttore XmlSerializer perché una singola istanza può essere utilizzata in entrambi i modi.
Tomer Gabel,

7
@jwg Un esempio è quando stai inviando il tuo XML a un servizio web di qualche tipo e non sei interessato a ricevere quegli oggetti nel tuo componente.
Tomer Gabel,

5
Tieni presente che anche se costruisci il tuo costruttore senza parametri privateo internal, tutte le tue proprietà i cui valori sono stati serializzati devono avere publicsetter.
Chrnola,

75

Questa è una limitazione di XmlSerializer. Nota che BinaryFormattere DataContractSerializer non lo richiedono: possono creare un oggetto non inizializzato dall'etere e inizializzarlo durante la deserializzazione.

Dato che stai usando xml, potresti considerare di usare DataContractSerializere contrassegnare la tua classe con [DataContract]/ [DataMember], ma nota che questo cambia lo schema (per esempio, non esiste un equivalente di [XmlAttribute]- tutto diventa elemento).

Aggiornamento: se vuoi davvero saperlo, BinaryFormatterusa anche FormatterServices.GetUninitializedObject()per creare l'oggetto senza invocare il costruttore. Probabilmente pericoloso; Non consiglio di usarlo troppo spesso ;-p Vedi anche le osservazioni su MSDN:

Poiché la nuova istanza dell'oggetto è inizializzata su zero e non vengono eseguiti costruttori, l'oggetto potrebbe non rappresentare uno stato considerato valido da quell'oggetto. Il metodo corrente deve essere utilizzato per la deserializzazione quando l'utente intende popolare immediatamente tutti i campi. Non crea una stringa non inizializzata, poiché la creazione di un'istanza vuota di tipo immutabile non serve a nulla.

Ho il mio proprio motore di serializzazione, ma io non intendo fare esso uso FormatterServices; Mi piace sapere che un costruttore ( qualsiasi costruttore) ha effettivamente eseguito.


Grazie per il suggerimento su FormatterServices.GetUninitializedObject (Type). :)
Omer van Kloeten,

6
Heh; si scopre che non seguo il mio consiglio; protobuf-net ha (facoltativamente) consentito l' FormatterServicesutilizzo per anni
Marc Gravell

1
Ma ciò che non capisco è, nel caso in cui non sia specificato alcun costruttore, il compilatore crea un costruttore pubblico senza parametri. Quindi perché non è abbastanza buono per il motore di deserializzazione XML?
toddmo

Se voglio deserializzare XML e inizializzare un determinato oggetto usando il loro costruttore (in modo che gli elementi / attributi siano forniti tramite il costruttore), c'è UN MODO per raggiungere questo obiettivo? Non c'è un modo per personalizzare il processo di serializzazione in modo che costruisca gli oggetti usando i loro costruttori?
Shimmy Weitzhandler,

1
@Shimmy no; questo non è supportato. Non v'è IXmlSerializable , ma un: che accade dopo il costruttore, e B: è molto brutto e difficile da ottenere (in particolare deserializzazione) - vi raccomando vivamente contro il tentativo di attuare tale, ma: non vi permetterà di utilizzare i costruttori
Marc Gravell

4

La risposta è: senza una buona ragione.

Contrariamente al suo nome, la XmlSerializerclasse viene utilizzata non solo per la serializzazione, ma anche per la deserializzazione. Esegue alcuni controlli sulla tua classe per assicurarsi che funzionerà e alcuni di questi controlli sono pertinenti solo alla deserializzazione, ma li esegue comunque tutti, perché non sa cosa intendi fare in seguito.

Il controllo che la tua classe non riesce a superare è uno dei controlli pertinenti solo alla deserializzazione. Ecco cosa succede:

  • Durante la deserializzazione, la XmlSerializerclasse dovrà creare istanze del tuo tipo.

  • Per creare un'istanza di un tipo, è necessario richiamare un costruttore di quel tipo.

  • Se non hai dichiarato un costruttore, il compilatore ha già fornito un costruttore senza parametri predefinito, ma se hai dichiarato un costruttore, questo è l'unico costruttore disponibile.

  • Quindi, se il costruttore dichiarato ha accettato i parametri, l'unico modo per creare un'istanza della classe è invocare quel costruttore che accetta i parametri.

  • Tuttavia, XmlSerializernon è in grado di invocare alcun costruttore tranne un costruttore senza parametri, perché non sa quali parametri passare ai costruttori che accettano parametri. Quindi, controlla se la tua classe ha un costruttore senza parametri e, poiché non lo è, fallisce.

Quindi, se la XmlSerializerclasse fosse stata scritta in modo tale da eseguire solo i controlli pertinenti alla serializzazione, allora la tua classe passerebbe, perché non c'è assolutamente nulla nella serializzazione che rende necessario avere un costruttore senza parametri.

Come altri hanno già sottolineato, la soluzione rapida al tuo problema è semplicemente aggiungere un costruttore senza parametri. Sfortunatamente, è anche una soluzione sporca, perché significa che non è possibile readonlyinizializzare alcun membro dai parametri del costruttore.

Oltre a tutto ciò, la XmlSerializerclasse avrebbe potuto essere scritta in modo tale da consentire una deserializzazione uniforme delle classi senza costruttori senza parametri. Basterebbe usare "The Factory Method Design Pattern" (Wikipedia) . A quanto pare, Microsoft ha deciso che questo modello di progettazione è troppo avanzato per i programmatori DotNet, che apparentemente non dovrebbero essere inutilmente confusi con tali cose. Quindi, secondo Microsoft, i programmatori DotNet dovrebbero attenersi meglio ai costruttori senza parametri.


Lol dici, For no good reason whatsoever,poi continua dicendo: 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 non sa quali parametri passare a un costruttore, come farebbe a sapere quali parametri passare a una fabbrica? O quale fabbrica usare? Non riesco a immaginare che questo strumento sia più semplice da usare: vuoi una classe deserializzata, quindi lascia che il deserializer crei un'istanza predefinita e quindi compili ogni campo che hai taggato. Facile.
Chuck,

0

Prima di tutto, questo è ciò che è scritto nella documentazione . Penso che sia uno dei campi della tua classe, non quello principale - e come vuoi che il deserializzatore lo ricostruisca senza una costruzione senza parametri?

Penso che ci sia una soluzione alternativa per rendere il costruttore privato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.