Pourquoi la classe XML-Serializable a besoin d'un constructeur sans paramètre

173

J'écris du code pour faire la sérialisation Xml. Avec la fonction ci-dessous.

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

Si l'argument est une instance de classe sans constructeur sans paramètre, il lèvera une exception.

Exception non gérée: System.InvalidOperationException: CSharpConsole.Foo ne peut pas être sérialisé car il n'a pas de constructeur sans paramètre. à System.Xml.Serialization.TypeDesc.CheckSupported () à System.Xml.Serialization.TypeScope.GetTypeDesc (Type type, MemberInfo sourc e, Boolean directReference, Boolean throwOnError) à System.Xml.Serialization.ModelScope.GetTypeModel (Type type, Boolean direct Reference) à System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Type type, racine XmlRootAttribute, String defaultNamespace) à System.Xml.Serialization.XmlSerializer..ctor (Type type, String defaultName space) à System.Xml.Serialization. XmlSerializer..ctor (type de type)

Pourquoi doit-il y avoir un constructeur sans paramètre pour permettre à la sérialisation XML de réussir?

EDIT: merci pour la réponse de cfeduke. Le constructeur sans paramètre peut être privé ou interne.

Morgan Cheng
la source
1
Si vous êtes intéressé, j'ai trouvé comment créer des objets sans avoir besoin du constructeur (voir mise à jour) - mais cela n'aidera pas du tout XmlSerializer - il l'exige toujours. Utile pour le code personnalisé, peut-être.
Marc Gravell
1
XmlSerializernécessite un constructeur sans paramètre par défaut pour la désérialisation.
Amit Kumar Ghosh

Réponses:

243

Lors de la désérialisation d'un objet, la classe responsable de la désérialisation d'un objet crée une instance de la classe sérialisée, puis procède au remplissage des champs et propriétés sérialisés uniquement après l'acquisition d'une instance à remplir.

Vous pouvez créer votre constructeur privateou internalsi vous le souhaitez, du moment qu'il est sans paramètre.

cfeduke
la source
1
Oh, donc, je peux rendre le ctor sans paramètre privé ou interne et la sérialisation fonctionne toujours. Merci pour votre réponse.
Morgan Cheng
2
Oui, je le fais souvent, même si j'en suis venu à accepter que les constructeurs publics sans paramètre sont excellents car ils vous permettent d'utiliser "new ()" avec des génériques et la nouvelle syntaxe d'initialisation. Pour les constructeurs paramétrés, utilisez des méthodes de fabrique statiques ou l'implémentation du modèle de générateur.
cfeduke
14
L'astuce d'accessibilité est bonne, mais votre explication n'a aucun sens pour la sérialisation. Un objet doit être créé uniquement pour la désérialisation. J'imagine que le code de contrôle de type est intégré au constructeur XmlSerializer car une seule instance peut être utilisée dans les deux sens.
Tomer Gabel
7
@jwg Un exemple est lorsque vous envoyez votre XML à un service Web quelconque et que vous n'êtes pas intéressé par la réception de ces objets dans votre propre composant.
Tomer Gabel
5
Gardez à l'esprit que même si vous créez votre constructeur sans paramètre privateou internal, toutes vos propriétés dont les valeurs ont été sérialisées doivent avoir des publicsetters.
chrnola
75

Ceci est une limitation de XmlSerializer. Notez que BinaryFormatteret DataContractSerializer ne l' exigent pas - ils peuvent créer un objet non initialisé à partir de l'éther et l'initialiser pendant la désérialisation.

Puisque vous utilisez xml, vous pouvez envisager d'utiliser DataContractSerializeret de marquer votre classe avec [DataContract]/ [DataMember], mais notez que cela change le schéma (par exemple, il n'y a pas d'équivalent de [XmlAttribute]- tout devient des éléments).

Mise à jour: si vous voulez vraiment savoir, BinaryFormatteret al utilisent FormatterServices.GetUninitializedObject()pour créer l'objet sans appeler le constructeur. Probablement dangereux; Je ne recommande pas de l'utiliser trop souvent ;-p Voir aussi les remarques sur MSDN:

Étant donné que la nouvelle instance de l'objet est initialisée à zéro et qu'aucun constructeur n'est exécuté, l'objet peut ne pas représenter un état considéré comme valide par cet objet. La méthode actuelle ne doit être utilisée pour la désérialisation que lorsque l'utilisateur a l'intention de remplir immédiatement tous les champs. Il ne crée pas de chaîne non initialisée, car la création d'une instance vide d'un type immuable ne sert à rien.

J'ai mon propre moteur de sérialisation, mais je n'ai pas l'intention de l'utiliser FormatterServices; J'aime bien savoir qu'un constructeur ( n'importe quel constructeur) s'est réellement exécuté.

Marc Gravell
la source
Merci pour le conseil sur FormatterServices.GetUninitializedObject (Type). :)
Omer van Kloeten
6
Il h; se trouve que je ne suis pas mon propre conseil; protobuf-net a (facultativement) autorisé l' FormatterServicesutilisation pour les âges
Marc Gravell
1
Mais ce que je ne comprends pas, c'est que dans le cas où aucun constructeur n'est spécifié, le compilateur crée un constructeur public sans paramètre. Alors pourquoi n'est-ce pas assez bon pour le moteur de désérialisation XML?
toddmo
Si je veux désérialiser XML et initialiser certains objets en utilisant leur constructeur (afin que les éléments / attributs soient fournis via le constructeur), existe-t-il un moyen d'y parvenir? N'existe-t-il pas un moyen de personnaliser le processus de sérialisation afin qu'il crée les objets à l'aide de leurs constructeurs?
Shimmy Weitzhandler
1
@Shimmy non; ce n'est pas pris en charge. Il y a IXmlSerializable , mais a: qui se produit après le constructeur, et b: c'est très moche et difficile à faire (surtout la désérialisation) - je déconseille fortement d'essayer de l'implémenter, mais: cela ne vous permettra pas d'utiliser des constructeurs
Marc Gravell
4

La réponse est: sans aucune raison valable.

Contrairement à son nom, la XmlSerializerclasse est utilisée non seulement pour la sérialisation, mais aussi pour la désérialisation. Il effectue certains contrôles sur votre classe pour s'assurer que cela fonctionnera, et certains de ces contrôles ne sont pertinents que pour la désérialisation, mais il les effectue tous de toute façon, car il ne sait pas ce que vous comptez faire plus tard.

La vérification que votre classe ne réussit pas est l'une des vérifications qui ne concernent que la désérialisation. Voici ce qui se passe:

  • Lors de la désérialisation, la XmlSerializerclasse devra créer des instances de votre type.

  • Afin de créer une instance d'un type, un constructeur de ce type doit être appelé.

  • Si vous n'avez pas déclaré de constructeur, le compilateur a déjà fourni un constructeur sans paramètre par défaut, mais si vous avez déclaré un constructeur, alors c'est le seul constructeur disponible.

  • Donc, si le constructeur que vous avez déclaré accepte des paramètres, la seule façon d'instancier votre classe est d'appeler ce constructeur qui accepte les paramètres.

  • Cependant, XmlSerializern'est pas capable d'appeler un constructeur sauf un constructeur sans paramètre, car il ne sait pas quels paramètres passer aux constructeurs qui acceptent des paramètres. Donc, il vérifie si votre classe a un constructeur sans paramètre, et comme ce n'est pas le cas, il échoue.

Donc, si la XmlSerializerclasse avait été écrite de manière à n'effectuer que les vérifications pertinentes pour la sérialisation, alors votre classe passerait, car il n'y a absolument rien dans la sérialisation qui rend nécessaire d'avoir un constructeur sans paramètre.

Comme d'autres l'ont déjà souligné, la solution rapide à votre problème consiste simplement à ajouter un constructeur sans paramètre. Malheureusement, c'est aussi une solution sale, car cela signifie que vous ne pouvez pas avoir de readonlymembres initialisés à partir des paramètres du constructeur.

En plus de tout cela, la XmlSerializerclasse aurait pu être écrite de manière à permettre même la désérialisation des classes sans constructeurs sans paramètre. Tout ce qu'il faudrait serait d'utiliser "The Factory Method Design Pattern" (Wikipedia) . À première vue, Microsoft a décidé que ce modèle de conception était beaucoup trop avancé pour les programmeurs DotNet, qui ne devraient apparemment pas être confondus inutilement avec de telles choses. Ainsi, les programmeurs DotNet devraient mieux s'en tenir aux constructeurs sans paramètre, selon Microsoft.

Mike Nakis
la source
Lol vous dites, For no good reason whatsoever,puis continuez en disant, 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.s'il ne sait pas quels paramètres passer à un constructeur, alors comment saurait-il quels paramètres passer à une usine? Ou quelle usine utiliser? Je ne peux pas imaginer que cet outil soit plus simple à utiliser - vous voulez une classe désérialisée, puis laissez le désérialiseur créer une instance par défaut, puis remplissez chaque champ que vous avez balisé. Facile.
Chuck
0

Tout d'abord, c'est ce qui est écrit dans la documentation . Je pense que c'est l'un de vos champs de classe, pas le principal - et comment voulez-vous que le désérialiseur le reconstruise sans construction sans paramètre?

Je pense qu'il existe une solution de contournement pour rendre le constructeur privé.

Dmitry Khalatov
la source