¿Por qué la clase serializable XML necesita un constructor sin parámetros?

173

Estoy escribiendo código para hacer la serialización Xml. Con la siguiente función.

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 el argumento es una instancia de clase sin un constructor sin parámetros, arrojará una excepción.

Excepción no controlada: System.InvalidOperationException: CSharpConsole.Foo no se puede serializar porque no tiene un constructor sin parámetros. en System.Xml.Serialization.TypeDesc.CheckSupported () en System.Xml.Serialization.TypeScope.GetTypeDesc (Tipo de tipo, MemberInfo sourc e, Boolean directReference, Boolean throwOnError) en System.Xml.Serialization.ModelScope.GetTypeModel (Type type, modelo Referencia directa booleana) en System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping (Type type, XmlRootAttribute root, String defaultNamespace) en System.Xml.Serialization.XmlSerializer..ctor (Type type, String defaultName space) en System.Xml.Serialization. XmlSerializer..ctor (Tipo de tipo)

¿Por qué debe haber un constructor sin parámetros para permitir que la serialización xml tenga éxito?

EDITAR: gracias por la respuesta de cfeduke. El constructor sin parámetros puede ser privado o interno.

Morgan Cheng
fuente
1
Si está interesado, descubrí cómo crear objetos sin necesidad del constructor (vea la actualización), pero esto no ayudará en absoluto a XmlSerializer, todavía lo exige. Útil para el código personalizado, tal vez.
Marc Gravell
1
XmlSerializerrequiere un constructor sin parámetros predeterminado para la deserialización.
Amit Kumar Ghosh

Respuestas:

243

Durante la deserialización de un objeto, la clase responsable de deserializar un objeto crea una instancia de la clase serializada y luego completa los campos y propiedades serializados solo después de adquirir una instancia para rellenar.

Puede hacer su constructor privateo internalsi lo desea, siempre que no tenga parámetros.

cfeduke
fuente
1
Ah, entonces, puedo hacer que el ctor sin parámetros sea privado o interno y la serialización aún funciona. Gracias por tu respuesta.
Morgan Cheng el
2
Sí, lo hago a menudo, aunque he llegado a aceptar que los constructores públicos sin parámetros son excelentes porque te permiten usar "new ()" con genéricos y la nueva sintaxis de inicialización. Para los constructores parametrizados, use métodos de fábrica estáticos o la implementación del patrón de construcción.
cfeduke el
14
La sugerencia de accesibilidad es buena, pero su explicación no tiene sentido para la serialización. Un objeto solo debe crearse para la deserialización. Me arriesgaría a adivinar que el código de verificación de tipo está integrado en el constructor XmlSerializer porque una sola instancia se puede usar en ambos sentidos.
Tomer Gabel el
77
@jwg Un ejemplo es cuando envía su XML a un servicio web de algún tipo y no está interesado en recibir esos objetos en su propio componente.
Tomer Gabel
55
Tenga en cuenta que incluso si crea su constructor sin parámetros privateo internal, todas sus propiedades cuyos valores se serializaron deben tener publicdefinidores.
chrnola
75

Esta es una limitación de XmlSerializer. Tenga en cuenta que BinaryFormattery DataContractSerializer no lo requiere: pueden crear un objeto no inicializado del éter e inicializarlo durante la deserialización.

Como está utilizando xml, puede considerar usar DataContractSerializery marcar su clase con [DataContract]/ [DataMember], pero tenga en cuenta que esto cambia el esquema (por ejemplo, no hay equivalente de [XmlAttribute]- todo se convierte en elementos).

Actualización: si realmente quieres saber, BinaryFormatteret al. Usa FormatterServices.GetUninitializedObject()para crear el objeto sin invocar al constructor. Probablemente peligroso; No recomiendo usarlo con demasiada frecuencia ;-p Vea también los comentarios en MSDN:

Debido a que la nueva instancia del objeto se inicializa a cero y no se ejecutan constructores, el objeto podría no representar un estado que ese objeto considere válido. El método actual solo debe usarse para la deserialización cuando el usuario tiene la intención de rellenar inmediatamente todos los campos. No crea una cadena no inicializada, ya que crear una instancia vacía de un tipo inmutable no sirve para nada.

Tengo mi propio motor de serialización, pero no tengo la intención de utilizarlo FormatterServices; Me gusta mucho saber que un constructor ( cualquier constructor) realmente se ha ejecutado.

Marc Gravell
fuente
Gracias por el consejo sobre FormatterServices.GetUninitializedObject (Type). :)
Omer van Kloeten
66
Je Resulta que no sigo mis propios consejos; protobuf-net ha permitido (opcionalmente) el FormatterServicesuso por edades
Marc Gravell
1
Pero lo que no entiendo es que, en caso de que no se especifique un constructor, el compilador crea un constructor público sin parámetros. Entonces, ¿por qué no es lo suficientemente bueno para el motor de deserialización xml?
toddmo
Si quiero deserializar XML e inicializar cierto objeto usando su constructor (para que los elementos / atributos se proporcionen a través del constructor), ¿hay ALGUNA forma de lograr esto? ¿No hay una manera de personalizar el proceso de serialización para que construya los objetos usando sus constructores?
Shimmy Weitzhandler
1
@Shimmy nope; Eso no es compatible. No es IXmlSerializable sino una: lo que sucede después de la constructora, y b: es muy desagradable y difícil de conseguir a la derecha (en especial deserialización) - Yo recomiendo no tratar de implementar, pero: no se permitirá utilizar constructores
Marc Gravell
4

La respuesta es: sin ninguna buena razón.

Contrariamente a su nombre, la XmlSerializerclase se usa no solo para la serialización, sino también para la deserialización. Realiza ciertas comprobaciones en su clase para asegurarse de que funcionará, y algunas de esas comprobaciones solo son pertinentes para la deserialización, pero las realiza todas de todos modos, porque no sabe qué piensa hacer más adelante.

El control que su clase no pasa es uno de los controles que solo son pertinentes para la deserialización. Esto es lo que pasa:

  • Durante la deserialización, la XmlSerializerclase necesitará crear instancias de su tipo.

  • Para crear una instancia de un tipo, se debe invocar un constructor de ese tipo.

  • Si no declaró un constructor, el compilador ya ha proporcionado un constructor sin parámetros predeterminado, pero si declaró un constructor, entonces ese es el único constructor disponible.

  • Entonces, si el constructor que declaró acepta parámetros, entonces la única forma de instanciar su clase es invocando ese constructor que acepta parámetros.

  • Sin embargo, XmlSerializerno es capaz de invocar ningún constructor, excepto un constructor sin parámetros, porque no sabe qué parámetros pasar a los constructores que aceptan parámetros. Por lo tanto, verifica si su clase tiene un constructor sin parámetros y, como no lo tiene, falla.

Entonces, si la XmlSerializerclase se hubiera escrito de tal manera que solo realizara las verificaciones pertinentes a la serialización, entonces su clase pasaría, porque no hay absolutamente nada sobre la serialización que haga necesario tener un constructor sin parámetros.

Como otros ya han señalado, la solución rápida a su problema es simplemente agregar un constructor sin parámetros. Desafortunadamente, también es una solución sucia, porque significa que no puede tener ningún readonlymiembro inicializado a partir de los parámetros del constructor.

Además de todo esto, la XmlSerializerclase podría haberse escrito de tal manera que permita incluso la deserialización de clases sin constructores sin parámetros. Todo lo que se necesitaría sería hacer uso del "Patrón de diseño del método de fábrica" ​​(Wikipedia) . Por lo que parece, Microsoft decidió que este patrón de diseño es demasiado avanzado para los programadores de DotNet, que aparentemente no deberían confundirse innecesariamente con tales cosas. Entonces, los programadores de DotNet deberían adherirse mejor a los constructores sin parámetros, según Microsoft.

Mike Nakis
fuente
Lol dices, For no good reason whatsoever,luego continúa, 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.si no sabe qué parámetros pasarle a un constructor, ¿cómo sabría qué parámetros pasarle a una fábrica? ¿O qué fábrica usar? No puedo imaginar que esta herramienta sea más fácil de usar: desea una clase deserializada, luego deje que el deserializador haga una instancia predeterminada y luego complete cada campo que etiquetó. Fácil.
Chuck
0

En primer lugar, esto es lo que está escrito en la documentación . Creo que es uno de los campos de su clase, no el principal, y ¿cómo desea que el deserialiser lo vuelva a construir sin construcción sin parámetros?

Creo que hay una solución alternativa para hacer que el constructor sea privado.

Dmitry Khalatov
fuente