2009-08-18 20 views
29

Tengo varias clases que son inmutables una vez que se establecen sus valores iniciales. Eric Lippert llama a esto write-once immutability.Inmutabilidad y serialización XML

Implementar la inmutabilidad de escritura única en C# generalmente significa establecer los valores iniciales a través del constructor. Estos valores inicializan campos de solo lectura.

Pero si necesita serializar una clase como esta en XML, utilizando el XmlSerializer o el DataContractSerializer, debe tener un constructor sin parámetros.

¿Alguien tiene sugerencias sobre cómo solucionar este problema? ¿Hay otras formas de inmutabilidad que funcionan mejor con la serialización?

EDITAR: Como señaló @Todd, el DataContractSerializer no requiere un constructor sin parámetros. De acuerdo con el DataContractSerializer documentation on MSDN, DataContractSerializer "no llama al constructor del objeto de destino".

+1

Creo que estás confundiendo las cosas ... lo que describes es la inmutabilidad de las paletas. Se parece más a la inmutabilidad de escritura única para mí. –

+0

editar: ... es ** no ** paletas ... –

+0

Tiene toda la razón, @Martinho. He corregido mi pregunta para leer "inmutabilidad de escritura única". – dthrasher

Respuesta

9

Asumiendo que este es su objeto "inmutable":

public class Immutable 
{ 
    public Immutable(string foo, int bar) 
    { 
     this.Foo = foo; 
     this.Bar = bar; 
    } 

    public string Foo { get; private set; } 
    public int Bar { get; private set; } 
} 

Usted puede crear una clase ficticia para representar ese objeto inmutable durante la serialización/deserialización:

public class DummyImmutable 
{ 
    public DummyImmutable(Immutable i) 
    { 
     this.Foo = i.Foo; 
     this.Bar = i.Bar; 
    } 

    public string Foo { get; set; } 
    public int Bar { get; set; } 

    public Immutable GetImmutable() 
    { 
     return new Immutable(this.Foo, this.Bar); 
    } 
} 

Cuando usted tiene una propiedad de tipo Inmutable, no serializarlo, y en su lugar serializar un DummyImmutable:

[XmlIgnore] 
public Immutable SomeProperty { get; set; } 

[XmlElement("SomeProperty")] 
public DummyImmutable SomePropertyXml 
{ 
    get { return new DummyImmutable(this.SomeProperty); } 
    set { this.SomeProperty = value != null ? value.GetImmutable() : null; } 
} 

OK, esto es un poco largo para algo que parece tan simple ... pero debería funcionar;)

+2

Esto es desagradable, pero parece ser el enfoque recomendado por Microsoft. –

8

La inmutabilidad "Realio-trulio" implica al constructor. inmutabilidad Popsicle es donde se puede hacer, por ejemplo:

Person p = new Person(); 
p.Name = "Fred"; 
p.DateOfBirth = DateTime.Today; 
p.Freeze(); // **now** immutable (edit attempts throw an exception) 

(o el mismo con un inicializador de objeto)

Esto encaja DataContractSerializer bastante bien, siempre y cuando se maneja a sobre-serializado de devolución de llamada para hacer el Freeze. XmlSerializerno hace hacer devoluciones de llamadas de serialización, por lo que es más trabajo.

Cualquiera se adaptaría si usa la serialización personalizada (IXmlSerializable). Asimismo, la serialización personalizada es ampliamente factible con la inmutabilidad de realio-trulio, pero es dolorosa, y es un poco mentira, ya que es "crear una vez, luego llamar al método de interfaz", así que realmente no correctamente inmutable.

Para una verdadera inmutabilidad, use un DTO.

+0

¿Qué es la inmutabilidad de 'Realio-trulio'? –

+1

@ChibuezeOpata algo que es * en realidad * inmutable (al menos, sin hacer trampa) - 'readonly', etc –

+0

¡Jaja, genial, gracias! :) –

0

Acabo de echar un vistazo al artículo que ha vinculado. En su terminología, los objetos que utilizan campos de solo lectura inicializados en el constructor se denominan "inmutabilidad de solo escritura".

"Immutability paleta" es un poco diferente. Lippert brinda dos ejemplos de dónde sería útil: deserialización (el problema que está tratando de resolver) y referencias circulares donde A y B necesitan crearse independientemente, pero A necesita una referencia a B y B una referencia a A .

Las dos formas más obvias de lograr este resultado se han mencionado aquí (ya que estaba escribiendo esto, de hecho). El patrón que menciona Thomas Levesque es básicamente el patrón "Constructor". Pero es bastante difícil de manejar en este caso porque no solo debe pasar de Constructor a Inmutable, sino también de Inmutable a Constructor para que la serialización/deserialización sea simétrica.

Por lo tanto, el patrón de "bloqueo", como lo menciona Marc Gravell, parece ser más útil. Aunque no estoy familiarizado con C#, entonces no estoy seguro de cómo implementarlo mejor. Supongo que probablemente una propiedad privada como bloqueó. Entonces, todos los métodos captadores necesitan comprobar explícitamente si el objeto está bloqueado (también conocido como "congelado"). Si es así, deberían lanzar una excepción (es un error en el tiempo de ejecución; no se puede forzar la inmutabilidad del popstick en el momento de la compilación).

+0

Gracias, Todd. He corregido mi pregunta para leer la inmutabilidad de "escribir una vez". Investigaré usando un método Freeze con DataContractSerializer. – dthrasher

+0

Eche un vistazo a mi otra (segunda) respuesta. Si DataContractSerializer puede deserializar miembros privados, entonces no debería necesitar ninguna "inmutabilidad de paleta" o un método Freeze(). –

2

Recomiendo echar un vistazo a la discusión aquí: Why XML-Serializable class need a parameterless constructor.

Debería considerar el uso de DataContractSerializer. Por lo que puedo decir de los documentos, esto no requiere un constructor sin parámetros, y puede serializar/deserializar miembros privados.

+0

Tienes razón, Todd. Según MSDN, el DataContractSerializer no llama al constructor: http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer.aspx – dthrasher

+0

Tenga en cuenta que DataContractSerializer solo está disponible desde .NET 3.5. No puede usarlo con .NET 2.0 – Maxence

6

Si la clase es realmente inmutable, solo use los campos públicos de solo marcado con atributos.

[DataContract()] 
public class Immutable 
{ 
     [DataMember(IsRequired=true)] 
     public readonly string Member; 

     public Immutable(string member) 
     { 
      Member = member; 
     } 
} 
+0

¡Ah, esto! campos públicos! estado tratando de hacer que funcionen las propiedades ... gracias por esta respuesta poco entusiasta (sí, sé que es 5 años después) –