Warum XML-Serializable-Klasse einen parameterlosen Konstruktor benötigt

173

Ich schreibe Code für die XML-Serialisierung. Mit unten stehender Funktion.

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

Wenn das Argument eine Klasseninstanz ohne parameterlosen Konstruktor ist, wird eine Ausnahme ausgelöst.

Nicht behandelte Ausnahme: System.InvalidOperationException: CSharpConsole.Foo kann nicht serialisiert werden, da es keinen parameterlosen Konstruktor gibt. unter System.Xml.Serialization.TypeDesc.CheckSupported () unter System.Xml.Serialization.TypeScope.GetTypeDesc (Typtyp, MemberInfo-Quelle, Boolean directReference, Boolean throwOnError) unter System.Xml.Serialization.ModelScope.GetType Boolesche direkte Referenz) unter System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Typ type, XmlRootAttribute root, String defaultNamespace) unter System.Xml.Serialization.XmlSerializer..ctor (Typ type, String defaultName space) unter System.Xml.Serialization. XmlSerializer..ctor (Typ Typ)

Warum muss es einen parameterlosen Konstruktor geben, damit die XML-Serialisierung erfolgreich ist?

EDIT: Danke für die Antwort von cfeduke. Der parameterlose Konstruktor kann privat oder intern sein.

Morgan Cheng
quelle
1
Wenn Sie interessiert sind, habe ich herausgefunden, wie Sie Objekte erstellen können, ohne den Konstruktor zu benötigen (siehe Update) - aber das hilft XmlSerializer überhaupt nicht - es erfordert es immer noch. Vielleicht nützlich für benutzerdefinierten Code.
Marc Gravell
1
XmlSerializererfordert einen standardmäßigen parameterlosen Konstruktor für die Deserialisierung.
Amit Kumar Ghosh

Antworten:

243

Während der De-Serialisierung eines Objekts erstellt die Klasse, die für die De-Serialisierung eines Objekts verantwortlich ist, eine Instanz der serialisierten Klasse und füllt die serialisierten Felder und Eigenschaften erst aus, nachdem eine Instanz zum Auffüllen erfasst wurde.

Sie können Ihren Konstruktor erstellen privateoder internalwenn Sie möchten, nur solange er parameterlos ist.

cfeduke
quelle
1
Oh, also kann ich den parameterlosen CTOR privat oder intern machen und die Serialisierung funktioniert immer noch. Danke für deine Antwort.
Morgan Cheng
2
Ja, ich mache das oft, obwohl ich akzeptiert habe, dass öffentliche parameterlose Konstruktoren großartig sind, weil sie es Ihnen ermöglichen, "new ()" mit Generika und der neuen Initialisierungssyntax zu verwenden. Verwenden Sie für parametrisierte Konstruktoren statische Factory-Methoden oder die Implementierung des Builder-Musters.
cfeduke
14
Der Tipp zur Barrierefreiheit ist gut, aber Ihre Erklärung macht für die Serialisierung keinen Sinn. Ein Objekt muss nur zur De-Serialisierung erstellt werden. Ich würde eine Vermutung wagen, dass der Typprüfungscode in den XmlSerializer-Konstruktor integriert ist, da eine einzelne Instanz in beide Richtungen verwendet werden kann.
Tomer Gabel
7
@jwg Ein Beispiel ist, wenn Sie Ihr XML an einen Webdienst senden und nicht daran interessiert sind, diese Objekte in Ihrer eigenen Komponente zu empfangen.
Tomer Gabel
5
Beachten Sie , dass alle Eigenschaften, deren Werte serialisiert wurden, Setter haben müssen , auch wenn Sie Ihren parameterlosen Konstruktor privateoder erstellen. internalpublic
Chrnola
75

Dies ist eine Einschränkung von XmlSerializer. Beachten Sie, dass BinaryFormatterund DataContractSerializer nicht diese benötigen - sie eine nicht initialisierte Objekt aus dem Äther erstellen und initialisieren es während der Deserialisierung.

Da Sie XML verwenden, können Sie DataContractSerializerIhre Klasse verwenden und mit [DataContract]/ [DataMember] markieren. Beachten Sie jedoch, dass dies das Schema ändert (z. B. gibt es kein Äquivalent zu [XmlAttribute]- alles wird zu Elementen).

Update: Wenn Sie es wirklich wissen möchten, verwenden Sie BinaryFormatterua FormatterServices.GetUninitializedObject(), um das Objekt zu erstellen, ohne den Konstruktor aufzurufen. Wahrscheinlich gefährlich; Ich empfehle nicht, es zu oft zu verwenden ;-p Siehe auch die Anmerkungen zu MSDN:

Da die neue Instanz des Objekts auf Null initialisiert wird und keine Konstruktoren ausgeführt werden, stellt das Objekt möglicherweise keinen Status dar, der von diesem Objekt als gültig angesehen wird. Die aktuelle Methode sollte nur zur Deserialisierung verwendet werden, wenn der Benutzer beabsichtigt, alle Felder sofort auszufüllen. Es wird keine nicht initialisierte Zeichenfolge erstellt, da das Erstellen einer leeren Instanz eines unveränderlichen Typs keinen Zweck erfüllt.

Ich habe meine eigene Serialisierungs-Engine, habe aber nicht die Absicht, sie zu verwenden FormatterServices. Ich mag es zu wissen, dass ein Konstruktor ( jeder Konstruktor) tatsächlich ausgeführt hat.

Marc Gravell
quelle
Vielen Dank für den Tipp zu FormatterServices.GetUninitializedObject (Type). :)
Omer van Kloeten
6
Heh; Es stellt sich heraus, dass ich meinen eigenen Rat nicht befolge. protobuf-net hat (optional) die FormatterServicesVerwendung für Ewigkeiten
Marc Gravell
1
Was ich jedoch nicht verstehe, ist, dass der Compiler, falls kein Konstruktor angegeben ist, einen öffentlichen parameterlosen Konstruktor erstellt. Warum ist das nicht gut genug für die XML-Deserialisierungs-Engine?
Toddmo
Wenn ich XML deserialisieren und bestimmte Objekte mit ihrem Konstruktor initialisieren möchte (damit die Elemente / Attribute über den Konstruktor bereitgestellt werden), gibt es eine Möglichkeit, dies zu erreichen? Gibt es keine Möglichkeit, den Serialisierungsprozess so anzupassen, dass die Objekte mithilfe ihrer Konstruktoren erstellt werden?
Shimmy Weitzhandler
1
@ Shimmy nein; das wird nicht unterstützt. Es gibt IXmlSerializable aber a: das passiert nach dem Konstruktor und b: es ist sehr hässlich und schwer, es richtig zu machen (insbesondere Deserialisierung) - ich empfehle dringend, nicht zu versuchen, dies zu implementieren, aber: es erlaubt Ihnen nicht, Konstruktoren zu verwenden
Marc Gravell
4

Die Antwort lautet: ohne guten Grund.

Im Gegensatz zu ihrem Namen wird die XmlSerializerKlasse nicht nur zur Serialisierung, sondern auch zur Deserialisierung verwendet. Es führt bestimmte Überprüfungen für Ihre Klasse durch, um sicherzustellen, dass es funktioniert, und einige dieser Überprüfungen sind nur für die Deserialisierung relevant, aber es führt sie trotzdem alle durch, da es nicht weiß, was Sie später tun möchten.

Die Prüfung, die Ihre Klasse nicht besteht, ist eine der Prüfungen, die nur für die Deserialisierung relevant sind. Folgendes passiert:

  • Während der Deserialisierung muss die XmlSerializerKlasse Instanzen Ihres Typs erstellen.

  • Um eine Instanz eines Typs zu erstellen, muss ein Konstruktor dieses Typs aufgerufen werden.

  • Wenn Sie keinen Konstruktor deklariert haben, hat der Compiler bereits einen standardmäßigen parameterlosen Konstruktor angegeben. Wenn Sie jedoch einen Konstruktor deklariert haben, ist dies der einzige verfügbare Konstruktor.

  • Wenn der von Ihnen deklarierte Konstruktor Parameter akzeptiert, können Sie Ihre Klasse nur instanziieren, indem Sie den Konstruktor aufrufen, der Parameter akzeptiert.

  • Es kann jedoch XmlSerializerkeinen Konstruktor außer einem parameterlosen Konstruktor aufrufen, da nicht bekannt ist, welche Parameter an Konstruktoren übergeben werden sollen, die Parameter akzeptieren. Es wird also überprüft, ob Ihre Klasse einen parameterlosen Konstruktor hat, und da dies nicht der Fall ist, schlägt dies fehl.

Wenn die XmlSerializerKlasse so geschrieben worden wäre, dass nur die für die Serialisierung relevanten Prüfungen durchgeführt würden, würde Ihre Klasse bestehen, da die Serialisierung absolut nichts erfordert, was einen parameterlosen Konstruktor erforderlich macht.

Wie andere bereits betont haben, besteht die schnelle Lösung für Ihr Problem darin, einfach einen parameterlosen Konstruktor hinzuzufügen. Leider ist es auch eine schmutzige Lösung, da Sie keine readonlyMitglieder aus Konstruktorparametern initialisieren können.

Darüber hinaus könnte die XmlSerializerKlasse so geschrieben worden sein, dass eine gleichmäßige Deserialisierung von Klassen ohne parameterlose Konstruktoren möglich ist. Alles was es brauchen würde, wäre "The Factory Method Design Pattern" (Wikipedia) zu verwenden. . Wie es aussah, entschied Microsoft, dass dieses Entwurfsmuster für DotNet-Programmierer viel zu weit fortgeschritten ist, die anscheinend nicht unnötig mit solchen Dingen verwechselt werden sollten. Daher sollten sich DotNet-Programmierer laut Microsoft besser an parameterlose Konstruktoren halten.

Mike Nakis
quelle
Lol Sie sagen, For no good reason whatsoever,dann sagen Sie weiter: 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.Wenn es nicht weiß, welche Parameter an einen Konstruktor übergeben werden sollen, wie würde es dann wissen, welche Parameter an eine Fabrik übergeben werden sollen? Oder welche Fabrik? Ich kann mir nicht vorstellen, dass dieses Tool einfacher zu verwenden ist. Sie möchten eine deserialisierte Klasse, lassen den Deserializer dann eine Standardinstanz erstellen und füllen dann jedes von Ihnen markierte Feld aus. Einfach.
Chuck
0

Zuallererst das, was in der Dokumentation geschrieben steht . Ich denke, es ist eines Ihrer Klassenfelder, nicht das Hauptfeld - und wie soll der Deserialisierer es ohne parameterlose Konstruktion wieder aufbauen?

Ich denke, es gibt eine Problemumgehung, um den Konstruktor privat zu machen.

Dmitry Khalatov
quelle