Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize / deserialize immutable list type in c#

If I have a class defined

[DataContract()]
class MyObject {
    [DataMember()]
    ImmutableList<string> Strings { get; private set}
}

The ImmutableList<T> type comes from the immutables library https://www.nuget.org/packages/Microsoft.Bcl.Immutable. Note that the class ImmutableList does not have a default constructor or a mutable Add method. Adding things to the list take the form.

myList = myList.Add("new string");

Can I add some custom support to the .NET serialization mechanism to support this type and show it how to deserialize it?

Currently the collection is just skipped on deserialization though it is fine to serialize it.

like image 456
bradgonesurfing Avatar asked Sep 23 '13 09:09

bradgonesurfing


2 Answers

One way to do this is to use a proxy mutable list and use the OnSerializing and OnDeserialized hooks

[DataContract()]
class MyObject {

    public ImmutableList<string> Strings { get; private set}

    [DataMember(Name="Strings")]
    private List<String> _Strings;

    [OnSerializing()]
    public void OnSerializing(StreamingContext ctxt){
        _Strings = Strings.ToList();
    }

    [OnDeserialized()]
    public void OnDeserialized(StreamingContext ctxt){
        Strings = ImmutableList<string>.Empty.AddRange(_Strings);
    }
}

It's not super pretty but as Marc Gravell noted in his answer, DataContract serializer is broken with respects to immutable collections and there are no simple hooks to teach it how to behave without the above type of hack.

UPDATE

DataContract serializer is not broken. There is a way to hook surrogates in. See this separate answer showing an alternate technique.

https://stackoverflow.com/a/18957739/158285

like image 119
bradgonesurfing Avatar answered Oct 24 '22 01:10

bradgonesurfing


Heh; I can imagine what is happening here... the generated code is probably doing (paraphrasing):

var list = obj.Strings;
while(CanReadNextItem()) {
    list.Add(ReadNextItem());
}

The problem is that the BCL immutable API would require you to catch the result each time, i.e.

var list = obj.Strings;
while(CanReadNextItem()) {
    list = list.Add(ReadNextItem());
}
obj.Strings = list; // the private set is not a problem for this

Pre-existing list deserialization code doesn't work this way because it has never needed to - and indeed, there are many different implementations of Add, some of which return non-void results which are required to be ignored.

The lack of a non-public constructor may also upset it a bit, but if this was the main problem, I would kinda expect an exception when it tries to create a non-null list.

Of course, in performance terms, the list = list.Add(...) API probably isn't the most appropriate one to use anyway (although it should work).

I blogged on this topic recently (in the context of protobuf-net, which has now been updated to work with these collection types): http://marcgravell.blogspot.co.uk/2013/09/fun-with-immutable-collections.html Hopefully this blog article should explain why the differences mean that it doesn't play nicely with existing serialization techniques, and how serialization libraries might be updated to work with this scenario.

To answer the question directly, I would say the answer is simply: because the required changes to support immutable collections have not yet been made to DataContractSerializer. I have no knowledge of whether there is a plan to address this. But: I happily declare: "works in protobuf-net" ;p

like image 4
Marc Gravell Avatar answered Oct 23 '22 23:10

Marc Gravell