Monday, November 05, 2007

Serializing an ObservableCollection<T>

The Problem

.NET 3.0 added the ObservableCollection<T> class to support WPF's data binding infrastructure. It's just a collection that raises an event whenever it is changed: when an item is added, removed, moved, etcetera. Actually, it's just a handy default implementation of INotifyCollectionChanged, also added with .NET 3.0.

Handy until you need to serialize it, that is. The problem is that the events have not been marked as non-serialized. Therefore, whenever you try to serialize an instance of ObservableCollection<T>, you will also be attempting to serialize any event handlers. When you're using the collection for its primary scenario (data binding), you will have WPF controls attached to the events. Therefore, serialization will fail.

The implementation of ObservableCollection<T> teases us by providing overridable events and methods. You might be tempted to subclass ObservableCollection<T> for the sole purpose of supporting serialization. You will only get so far before running into trouble. Consider:

[Serializable]
private sealed class SObservableCollection : ObservableCollection<string>
{
    [field: NonSerialized]
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    [field: NonSerialized]
    protected override event PropertyChangedEventHandler PropertyChanged;

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handler = CollectionChanged;

        if (handler != null)
        {
            handler(this, e);
        }
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, e);
        }
    }
}

Looks like it might work, yes? Indeed it will to a certain extent. Any delegates attached to CollectionChanged will not be serialized. But bindings to properties of the collection itself (such as Count) will cause serialization errors. The reason is that ObservableCollection<T> implements INotifyPropertyChanged.PropertyChanged explicitly, and provides a protected PropertyChanged event for reasons I cannot fathom. The fact is, the data binding infrastructure will be attaching to the event via a reference to INotifyPropertyChanged. Therefore, it will always attach to the event declared in ObservableCollection<T>, which will be serialized.

A Solution

Now that we know how we can't solve this problem, let's look at a way we can. Before you rush off and write your own implementation of INotifyCollectionChanged, there is a much easier solution. It involves using a little-known feature of .NET's serialization infrastructure called serialization surrogates. A serialization surrogate is a class capable of obtaining the data for, and creating an instance of, another class. Here is a serialization surrogate for ObservableCollection<T>:

private sealed class ObservableCollectionSerializationSurrogate<T> : ISerializationSurrogate
{
    private const string _itemsKey = "items";

    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        Debug.Assert(obj is ObservableCollection<T>);
        ObservableCollection<T> observableCollection = obj as ObservableCollection<T>;
        T[] items = new T[observableCollection.Count];
        observableCollection.CopyTo(items, 0);
        info.AddValue(_itemsKey, items);
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        T[] items = info.GetValue(_itemsKey, typeof(T[])) as T[];
        return new ObservableCollection<T>(new List<T>(items));
    }
}

Simple, eh? All it does is grab the items in the collection (that's all we really need to serialize) and stick them in the SerializationInfo. Then, during deserialization, it just pulls the items back out and uses them to create a new instance of ObservableCollection<T>. Using the class is simple too:

BinaryFormatter bf = new BinaryFormatter();
SurrogateSelector ss = new SurrogateSelector();
ss.AddSurrogate(typeof(ObservableCollection<string>), new StreamingContext(StreamingContextStates.All), new ObservableCollectionSerializationSurrogate<string>());
bf.SurrogateSelector = ss;

using (MemoryStream ms = new MemoryStream())
{
    bf.Serialize(ms, DataContext);
    ms.Position = 0;
    ObservableCollection<string> o = bf.Deserialize(ms);
}

Bingo!

Update 29th Jan, 2008: Commenter dfoderick suggested an alternative way making the collection serializable. The suggestion was to have the subclass implement INotifyPropertyChanged explicitly, therefore causing the binding infrastructure to use that implementation of the PropertyChanged event rather than the one on the subclass. This had not crossed my mind - thanks dfoderick!

The implementation would look something like this:

[Serializable]
public class SObservableCollection<T> : ObservableCollection<T>, INotifyPropertyChanged
{
    [field:NonSerialized]
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    [field: NonSerialized]
    private PropertyChangedEventHandler _propertyChangedEventHandler;

    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
    {
        add
        {
            _propertyChangedEventHandler = Delegate.Combine(_propertyChangedEventHandler, value) as PropertyChangedEventHandler;
        }
        remove
        {
            _propertyChangedEventHandler = Delegate.Remove(_propertyChangedEventHandler, value) as PropertyChangedEventHandler;
        }
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handler = CollectionChanged;

        if (handler != null)
        {
            handler(this, e);
        }
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = _propertyChangedEventHandler;

        if (handler != null)
        {
            handler(this, e);
        }
    }
}

I did a simple test against this class, and it does appear to work OK. I'm torn between the two approaches now. This approach allows you to avoid the need for the surrogate but at the cost of heavier collections (the PropertyChanged delegate field in the subclass is now just wasting memory) and more code. The surrogate approach, on the other hand, allows you to keep your collections a little lighter and avoids this rather messy and unobvious code.

13 comments:

c_marius said...

Very instructive. Even though i've been using same surogates since .NET 1.x, who would have thought they shall find their way in fixing missing logic in today's .NET Fmk. versions.

Nice one,
C. Marius (c_marius@msn.com)

jaap.taal said...

I created a derived class from SurrogateSelector that uses reflection to find all ObservableCollection<> clases. Combined with a reflection version of the SerializationSurrogate this enables me to serialize every ObservableCollection!
However, reflection is slow and even though I optimized a bit, using Kents solution will be faster.

dfoderick said...

How about explicity implementing INotifyPropertyChanged in the subclass of ObservableCollection? Wouldn't that direct databinding to use that specific implementation which would use the non-serialized events implementation explicitly in the subclass?

Kent Boogaart said...

Great suggestion dfoderick. I've updated the post accordingly.

Daniel said...

Hi all,

I'm trying to use the second methid, but I get the same error as previously:

Soap Serializer does not support serializing Generic Types : SerializerTest.SObservableCollection`1[System.String].

Shouldn't this work with SOAP?

Kent Boogaart said...

@Daniel: nope, believe it or not: http://forums.msdn.microsoft.com/en-US/netfxremoting/thread/ee4a7a63-290e-432f-bd45-44f4cb7a3467/

Daniel said...

Thanks. So basically I have no posibility of serializing an ObservableCollection object other than in BinaryFormat, which has the disadvantage of not being readable (in the sense of opening it and reading the file content) and can only be deserialized by other .NET Framework based applications?

Kent Boogaart said...

@Daniel. You could use either DataContractSerializer (.NET 3.0) or XmlSerializer. Definitely use the former if you can since it supports much of the standard serialization stuff you would expect.

Daniel said...

Thanks a lot for the ideas. I'll use DataContractSerializer, even if it's opt-in (which is more control than I actually need in my app for serialization). Otherwise, it seems the perfect choice.

mcrimes said...

It's worth noting for anyone new finding this thread that with .NET 3.5 SP1, this issue was resolved as the events are now marked as [field: NonSerialized].

Anonymous said...

Hi guys, I fear not to be as good at developing as you are and I use a visual basic which you might not like ;-)
I met a similar problem with serialization of observable collections and tried your solutions which all work, but there is one I definitely could not solve.

When you try to serialize an object containing a serializable collection which contains... another serializable collection it becomes a mess. ... Any ideas?


What I have found out so far is that everything goes well until I reset the bindings to the first observable collection (in my example Solution.Voyages)... before that I can serialize (with binding "resumed" ie "on") .After reset there is an exception thrown saying something is not serializable... but I have checked absolutely everything and did not find anything that would not be serializable.

And here are my classes so you can make out the pb more easily.

Thanks!

[Serializable()] Public Class Solution

'implementation for update of bound controls
Implements System.ComponentModel.INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged

Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub

'XXXX this is the observable collection…
Public WithEvents Voyages As New ObservableCollection(Of Voyage)


Private Sub Voyages_CollectionChanged(ByVal sender As Object, ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs) Handles Voyages.CollectionChanged

‘here some stuff changing a form
End Sub

End Class

‘------------------------------
[
Serializable()] Public Class Voyage

'implementation for update of bound controls
Implements System.ComponentModel.INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged

Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub

‘XXXX and this sis the second observable collection…
'collections of objects (data objects) that compose the voyage
Public WithEvents Ships As New ObservableCollection(Of Ship)

End Class

‘-----------------------------

[Serializable()] Public Class Ship
Implements System.ComponentModel.INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged


Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub

‘here some properties
End Class

martin said...

Sorry, I could not log on with my identity, so the anonymous was me

Mark Johnsen said...

I solved in .net 4.0 from a stackoverflow suggestion:
http://stackoverflow.com/questions/8633398/xmlserialize-an-observablecollection

The key, buried in the example was:
[System.Xml.Serialization.XmlInclude(typeof(UserStory))]
System.Xml.Serialization.XmlInclude(typeof(Task))]

Thanks Kent for the example (many years ago:-))

Good luck!