Immutability and XML Serialization
Asked Answered
N

6

36

I have several classes that are immutable once their initial values are set. Eric Lippert calls this write-once immutability.

Implementing write-once immutability in C# usually means setting the initial values via the constructor. These values initialize readonly fields.

But if you need to serialize a class like this to XML, using either the XmlSerializer or the DataContractSerializer, you must have a parameterless constructor.

Does anyone have suggestions for how to work around this problem? Are there other forms of immutability that work better with serialization?

EDIT: As @Todd pointed out, the DataContractSerializer does not require a parameterless constructor. According to the DataContractSerializer documentation on MSDN, DataContractSerializer "does not call the constructor of the target object."

Nw answered 18/8, 2009 at 14:35 Comment(3)
I think you are confusing things... what you describe is popsicle immutability. It looks more like write-once immutability to me.Passably
edit: ... is not popsicle...Passably
You're absolutely right, @Martinho. I've corrected my question to read "write-once immutability."Nw
S
13

If the class is truly immutable, just use public readonly fields marked with attributes.

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

      public Immutable(string member)
      {
           Member = member;
      }
 }
Siberia answered 9/9, 2011 at 10:19 Comment(3)
Ah, this! public fields! been trying to get it to work off properties... thanks for this underloved answer (yes, I know it's 5 years later)Outsole
This works, but I found it was finicky for collection types. It wouldn't work if the member's type was IEnumerable<T> or ReadOnlyCollection<T>, but I did get it to work with ICollection<T> and IList<T>, with a ReadOnlyCollection<T> object stored inside them. Not sure what else might work or not work.Supertax
The question talked about using either the XmlSerializer or the DataContractSerializer. This solution seems to work using the DataContractSerializer. But I guess you cannot have such a simple solution if you must use an XmlSerializer(?)Righteous
M
12

Assuming this is your "immutable" object :

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; }
}

You can create a dummy class to represent that immutable object during serialization/deserialization :

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);
    }
}

When you have a property of type Immutable, don't serialize it, and instead serialize a 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, this is a bit long for something that looks so simple... but it should work ;)

Micky answered 18/8, 2009 at 14:53 Comment(1)
This is nasty, but appears to be the approach recommended by Microsoft.Carpophagous
E
10

"Realio-trulio" immutability involves the constructor. Popsicle immutability is where you can do, for example:

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

(or the same with an object initializer)

This fits DataContractSerializer quite well, as long as you handle to on-serialized callback to do the Freeze. XmlSerializer doesn't do serialization callbacks, so is more work.

Either would suit if you use custom serialization (IXmlSerializable), though. Likewise, custom serialization is broadly doable with realio-trulio immutability, but is painful - and it is a bit of a lie, as it is "create once, then call interface method" - so not really properly immutable.

For true immutability, use a DTO.

Elberfeld answered 18/8, 2009 at 14:53 Comment(2)
What is 'Realio-trulio' immutability?Etiolate
@ChibuezeOpata something that is actually immutable (at least, without cheating) - readonly, etcElberfeld
L
2

I recommend taking a look at the discussion here: Why XML-Serializable class need a parameterless constructor.

You should consider using DataContractSerializer. As far as I can tell from the docs, this do not require a parameterless constructor, and can serialize/deserialize private members.

Landre answered 18/8, 2009 at 15:23 Comment(2)
You're right, Todd. According to MSDN, the DataContractSerializer does not call the constructor: msdn.microsoft.com/en-us/library/…Nw
Note that DataContractSerializer is only available since .NET 3.5. You can not use it with .NET 2.0Corvin
L
1

I just had a look at the article you linked to. In his terminology, objects using readonly fields initialized in the constructor are called "write-only immutability".

"Popsicle immutability" is a bit different. Lippert gives two examples of where it would be useful: deserialization (the problem which you are trying to solve), and circular references where A and B need to be created independently but A needs have a reference to B, and B a reference to A.

The two more obvious ways to achieve this result have been mentioned here (as I was writing this, in fact). The pattern Thomas Levesque mentions is basically the "Builder" pattern. But it's rather unwieldly in this case because you need to not only go from Builder to Immutable, but also from Immutable to Builder so that the serialization/deserialization is symmetrical.

So the "lock" pattern, as mentioned by Marc Gravell, seems more useful. I'm not familiar with C# though, so I'm not sure how best to implement it. I guess probably a private property such as locked. Then all the getter methods need to explicitly check whether the object is locked (aka "frozen") yet. If so they should throw an exception (it's a runtime error; you cannot enforce popstick immutability at compile time).

Landre answered 18/8, 2009 at 15:4 Comment(2)
Thanks, Todd. I've corrected my question to read "write-once" immutability. I'll investigate using a Freeze method with DataContractSerializer.Nw
Have a look at my other (second) answer. If DataContractSerializer can deserialize private members then you shouldn't need "popsicle immutability" or a Freeze() method at all.Landre
B
0

I did some research on my own, because frankly I don't like workarounds like proxy properties or readonly fields too much.

There is a NuGet library called Extended XML Serializer, which supports serializing immutable classes. It is really well written and extensible, but what I don't like about it is that it relies on keeping reflection information in the XML (for instance namespaces and class names), what potentially opens up a security risk (you can carefully craft XML so that not intended classes gets instantiated).

To solve this problem I wrote my own XML serialization library, which supports serializing immutable models, but it relies solely on the model structure and doesn't store any code-related information in the XML (the usage is similar to System.Xml.Serialization).

You can find Spooksoft.Xml.Serialization library in the NuGet repository. It is relatively simple and lightweight, but out of the box supports collections (List<T>, IReadOnlyList<T>, T[]) and dictionaries (for now Dictionary<TKey,TValue>), varying types, binary properties etc. The manual is on the library repository page.

Beverlee answered 29/4 at 8:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.