ShouldSerialize * () vs * Patrón de serialización condicional especificado

Conozco tanto el patrón ShouldSerialize * como el * Patrón especificado y cómo funcionan, pero ¿hay alguna diferencia entre los dos?

¿Hay algún “truco” usando un método frente al otro cuando ciertas cosas se deben serializar condicionalmente?

Esta pregunta es específica del uso de XmlSerializer , pero también se XmlSerializer la información general sobre este tema.

Hay muy poca información sobre este tema, por lo que puede deberse a que realizan exactamente el mismo propósito y es una elección de estilo. Sin embargo, parece extraño que los implementadores de .NET analizarían la clase a través de la reflexión y buscarían ambos patrones para determinar cómo se comportaría el serializador generado, ya que ralentiza la generación del serializador a menos que sea solo un artefacto de compatibilidad con versiones anteriores.

EDITAR: para aquellos que no estén familiarizados con los dos patrones si el método ShouldSerialize* Propiedad *Specified o ShouldSerialize* devuelve verdadero, entonces esa propiedad se serializa.

 public string MyProperty { get; set; } //*Specified Pattern [XmlIgnore] public bool MyPropertySpecified { get{ return !string.IsNullOrWhiteSpace(this.MyProperty); } } //ShouldSerialize* Pattern public bool ShouldSerializeMyProperty() { return !string.IsNullOrWhiteSpace(this.MyProperty); } 

La intención del patrón {propertyName}Specified está documentada en Compatibilidad de enlace de esquemas XML: Compatibilidad de enlace de atributo MinOccurs . Se agregó para admitir un elemento de esquema XSD en el que:

  • El está involucrado.
  • minOccurs es cero.
  • El atributo maxOccurs dicta una única instancia.
  • El tipo de datos se convierte a un tipo de valor.

En este caso, xsd.exe /classes generará automáticamente (o puede generar manualmente) una propiedad con el mismo nombre que el elemento de esquema y una {propertyName}Specified Boolean {propertyName}Specified get / set que rastrea si el elemento se encontró en el XML y debe ser serializado de vuelta a XML. Si se encuentra el elemento, {propertyName}Specified se establece en true , de lo contrario es false . Por lo tanto, la instancia deserializada puede determinar si la propiedad no se ajustó (en lugar de configurarse explícitamente a su valor predeterminado) en el XML original.

Lo inverso también se implementa para la generación de esquema. Si define un tipo de C # con un par de propiedades que coincidan con el patrón anterior, luego use xsd.exe para generar un archivo XSD correspondiente, se minOccurrs un minOccurrs apropiado al esquema. Por ejemplo, dado el siguiente tipo:

 public class ExampleClass { [XmlElement] public decimal Something { get; set; } [XmlIgnore] public bool SomethingSpecified { get; set; } } 

Se generará el siguiente esquema, y ​​viceversa:

         

Tenga en cuenta que, aunque xsd.exe está documentado solo para generar automáticamente una {propertyName}Specified para las propiedades de tipo de valor, XmlSerializer respetará el patrón cuando se use manualmente para propiedades de tipo de referencia.

Podría preguntar, ¿por qué xsd.exe no se une a un Nullable en este caso? Quizás porque:

  • Nullables se utilizan para apoyar el xsi:nil="true" lugar. Ver Xsi: nil Attribute Binding Support .
  • Nullables no se introdujeron hasta .Net 2.0, ¿entonces quizás era demasiado tarde para usarlos con este propósito?

xsd.exe tener en cuenta este patrón porque xsd.exe algunas veces lo generará automáticamente, sin embargo, la interacción entre una propiedad y su propiedad Specified es extraña y puede producir errores. Puede completar todas las propiedades de su clase, luego serializarlas en XML y perder todo porque tampoco estableció establecer las propiedades Specified correspondientes en true . Este “gotcha” aparece aquí de vez en cuando aquí, ver, por ejemplo, esta pregunta o esta también .

Otro “problema” con este patrón es que, si necesita serializar su tipo con un serializador que no admite este patrón, es posible que desee suprimir manualmente el resultado de esta propiedad durante la serialización, y probablemente deba configurarlo manualmente durante la deserialización. . Dado que cada serializador puede tener su propio mecanismo personalizado para suprimir propiedades (¡o no tener ningún mecanismo!), Hacer esto puede volverse cada vez más oneroso con el tiempo.

(Por último, estoy un poco sorprendido de que MyPropertySpecified funcione correctamente sin un setter. Parece que recuerdo una versión de .Net 2.0 en la que un setter {propertyName}Specified faltante MyPropertySpecified el MyPropertySpecified una excepción. Pero ya no es reproducible en versiones posteriores, y no tengo 2.0 para probar. Así que eso podría ser un tercer problema.)

El soporte para el método ShouldSerialize{PropertyName}() se documenta en Propiedades en los controles de Windows Forms: definición de valores predeterminados con los métodos ShouldSerialize y Reset . Como puede ver, la documentación se encuentra en la sección de Windows Forms de MSDN, no en la sección XmlSerializer , por lo que es, de hecho, una funcionalidad semi-oculta. No tengo idea de por qué el soporte para este método y la propiedad Specified existen en XmlSerializer . ShouldSerialize se introdujo en .Net 1.1 y creo que el soporte de enlace MinOccurs se agregó en .Net 2.0 , por lo que tal vez la funcionalidad anterior no cumplía con las necesidades (o el gusto) del equipo de desarrollo xsd.exe .

Como es un método, no una propiedad, carece de los “errores” del patrón {propertyName}Specified . También parece ser más popular en la práctica, y ha sido adoptado por otros serializadores que incluyen:

  • Json.NET
  • protobuf-net (que afirma que admite ambos patrones )

Entonces, ¿qué patrón usar?

  1. Si xsd.exe genera xsd.exe una {propertyName}Specified , o si su tipo necesita rastrear si un elemento específico apareció o no en el archivo XML, o si necesita que su XSD generado automáticamente indique que un determinado valor es opcional, usa este patrón y ten cuidado con los “errores”.

  2. De lo contrario, utilice el patrón ShouldSerialize{PropertyName}() . Tiene menos errores y puede ser más ampliamente compatible.

Para agregar a la respuesta muy detallada de @dbc, me encontré con un problema con la administración de la serialización en las clases derivadas. En mi situación, tenía una clase base y una clase derivada en la que se Prop una propiedad Prop .

 public class BaseClass { public virtual string Prop {get; set;} } public class Derived: BaseClass { public string Comp1 {get; set;} public string Comp2 {get; set;} public override string Prop {get => Comp1 + Comp2; set {}} } 

Como se calcula la propiedad Prop en la clase derivada, para la clase Derived quería serializar Comp1 y Comp2 pero no Prop . Resulta que establecer el atributo XmlIgnore en la propiedad Prop en la clase Derived no funciona y Prop se serializa de todos modos.

También traté de agregar un método ShouldSerializeProp y una propiedad PropSpecified en la clase Derived , pero ninguno de los dos funciona. Traté de establecer puntos de interrupción para ver si se llaman y no lo son.

Resulta que el XmlSerializer está mirando la clase original donde la propiedad Prop aparece por primera vez en la jerarquía de clases para decidir si serializar una propiedad o no. Para poder controlar la serialización en una clase derivada, primero tuve que agregar un virtual ShouldSerializeProp en la clase Base .

 public class Base { ..... public virtual bool ShouldSerializeProp() {return true;} } 

Entonces podría anular el ShouldSerializeProp en la clase Derived y devolver falso.

 public class Derived: Base { ..... public override bool ShouldSerializeProp() {return false;} } 

Este patrón permite que diferentes clases derivadas elijan qué propiedades de la clase padre serializan. Espero que esto ayude.