DependencyProperty Serialization

by Christoph Menge in Software

Ok, I did it: I tried to do it the easy way — serialization! And I quickly ran into the following issue: Serializing DependencyObjects. There are a lot of sources saying “you can’t serialize dependency objects”, which is true, but you can get pretty close rather quickly and it might not be the worst decision.

In this article, we’ll explore if and how reflection magic can assist us in doing it anyway, how we can locate DependencyProperties via reflection and what pitfalls remain. Moreover, I’ll give an outlook why the most desireable solution (that almost suggests itself) fails.

Using DPs in your business logic layer is a disputable decision. The application I work on has very unsual demands and I am still happy with the decision I made. I don’t want to discuss that here, though, since it is out of scope.

The Issue

When serializing objects, you can prevent certain properties from being serialized using the [NonSerialized] property, but if the base class is not marked as [Serializable], such as the DependencyObject class, attempting to serialize will throw an exception.

However, implementing ISerializable manually does the job:

[Serializable]
public class SerializableDependencyObject : DependencyObject, ISerializable
{
    // Note that this does not call base(info, context) since the base class does not have
    // a deserialization constructor
    public DagNode(SerializationInfo info, StreamingContext context)
    { }

    // again, the base class is not an ISerializable
    public virtual void GetObjectData(SerializationInfo info,
                                                 StreamingContext context)
    {}
// ...
}

What bugs me, however, is that now, all derived classes need to implement GetObjectData and the deserialization constructor explicitly, producing both stupid and error-prone code:

public class StupidClass : SerializableDependencyObject
{
  #region ISerializable Members
  public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
  {
    info.AddValue("PropertyA", mPropertyA);
    info.AddValue("PropertyB", mPropertyB);
    info.AddValue("PropertyC", mPropertyC);
  }

  public StupidClass (SerializationInfo info, StreamingContext context)
  {
    mPropertyA = info.GetDouble("PropertyA");
    mPropertyB = info.GetDouble("PropertyB");
    mPropertyC = info.GetDouble("PropertyC");
  }
  #endregion
}

We obviously don’t want this, since it produces a lot of manually written, error-prone code that needs to be adjusted constantly when code changes are made – a lot of work when you consider we’re not necessarily adding functionality yet.

One Step Back: .NET Serialization

It took me some time to realize that .NET has quite a number of techniques aimed at serialization. The process is described quite well at http://msdn.microsoft.com/en-us/library/tyf8zbfk.aspx, but it is still simplified since there are serialization attributes introduced in .NET 2.0 that allow you to hook up additional methods during the serialization/deserialization process:
“If the class being deserialized implements the IDeserilizationCallback, the OnDeserialization method is automatically called when the entire object graph has been deserialized.” (see http://msdn.microsoft.com/en-us/library/ty01x675(VS.71).aspx)

These concepts are somewhat different and allow you to control the process of serialization and deserialization to a certain degree.
You might be tempted to ask: “Why should I worry about .NET serialization if it is this complicated – isn’t it easier to do it myself?”, but the answer is probably “no”, since the BinaryFormatter really solves all the itty-gritty problems for you in a convenient manner. These are:

  • Solving dependency chains and circular links
  • Creating unique identifier and associating them w/ object instances
  • Serializing generic containers
  • Constructing objects on de-serialization and re-assigning pointers

All this does not help in serializing DependencyProperties, however, since your owning class is derived from DependencyObject, that is not marked as [Serializable] – unless you want to do it manually in each derived class.

An intelligent base class

How about a very intelligent base class that uses reflection to mimick the behaviour we’d expect?

We’d certainly need a class that is responsible for this behaviour, we might want to call it SerializableDependencyObject again:

[Serializable]
public class SerializableDependencyObject : DependencyObject, ISerializable
{
}

Now, the class needs to implement ISerializable in a sufficient manner, that is, in such way that derived classes behave just like normal serializable classes:

  • The value of DependencyProperties is being stored and restored automatically
  • DependencyProperties marked as [NonSerialized] are not being serialized

The GetObjectData method

Let’s try to fetch all fields and properties from the current object instance (this) in the base class. We want to add all these fields to the SerializationInfo instance:

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
  PropertyDescriptorCollection descriptors = TypeDescriptor.GetProperties(GetType(),
        new Attribute[] {
            new PropertyFilterAttribute(PropertyFilterOptions.SetValues |
                                        PropertyFilterOptions.UnsetValues |
                                        PropertyFilterOptions.Valid ) });

  List<FieldInfo> fieldInformation =
    ReflectionHelper.GetFieldInformation(GetType());

  foreach (FieldInfo fiRover in fieldInformation)
  {
    object[] attribs =
      fiRover.GetCustomAttributes(typeof(NonSerializedAttribute), false);

    if (attribs.Length > 0)
      continue; // Don't serialize this

    info.AddValue(fiRover.Name, fiRover.GetValue(this));
  }

  foreach (PropertyDescriptor propertyDescriptor in descriptors)
  {
    if (propertyDescriptor.Name == "Dispatcher" ||
        propertyDescriptor.Name == "DependencyObjectType" ||
        propertyDescriptor.Name == "IsSealed" )
          continue;

      SerializeProperty(info, propertyDescriptor);
  }
}

The first loop of the method will serialize all fields, resembling the functionality we have when not implementing ISerializable manually. The second part, adding functionality, cares for DependencyProperties. The ReflectionHelper is just a simple wrapper around the System.Type.GetFields() method with some performance improvements and minor tweaks.

Let’s have a look at the serialization constructor of SerializableDependencyObject:

public SerializableDependencyObject(SerializationInfo info,
                                     StreamingContext context)
{
    Type thisType = GetType();

    // De-Serialize Fields
    List<FieldInfo> fieldInformation =
        ReflectionHelper.GetFieldInformation(thisType);

    foreach (FieldInfo fiRover in fieldInformation)
    {
        object[] attribs =
          fiRover.GetCustomAttributes(typeof(NonSerializedAttribute), false);

        if (attribs.Length > 0)
            continue; // Don't try to deserialize this

        fiRover.SetValue(this, info.GetValue(fiRover.Name, fiRover.FieldType));
    }

    // De-Serialize DependencyProperties
    PropertyDescriptorCollection descriptors = TypeDescriptor.GetProperties(thisType,
        new Attribute[] {
           new PropertyFilterAttribute(PropertyFilterOptions.SetValues |
                                       PropertyFilterOptions.UnsetValues |
                                       PropertyFilterOptions.Valid) });

    foreach (PropertyDescriptor propertyDescriptor in descriptors)
    {
        if (propertyDescriptor.Name == "Dispatcher" ||
            propertyDescriptor.Name == "DependencyObjectType" ||
            propertyDescriptor.Name == "IsSealed")
            continue;

        DependencyProperty dp =
          DependencyPropertyHelper.FindDependencyProperty(this, propertyDescriptor.Name);

        if (null != dp)
            SetValue(dp, info.GetValue(propertyDescriptor.Name, propertyDescriptor.PropertyType));
        else
            throw new SerializationException(String.Format(@"Failed to deserialize
            property '{0}' on object of type '{1}'. Property could not be found.
            Version Conflict?",
            propertyDescriptor.Name, thisType));
    }
}

Again, the first loop handles simple fields while the second part adds functionality for DependencyProperties. One line in the second part deserves some attention:

DependencyProperty dp =
  DependencyPropertyHelper.FindDependencyProperty(this, propertyDescriptor.Name);

Remember DependencyProperties are static members which are registered in some application-wide registry, for example:

public static DependencyProperty NameProperty =
DependencyProperty.Register("Name",
                            typeof(string),
                            typeof(GraphNode),
                            new PropertyMetadata(String.Empty));

Unfortunately, there is no DependencyProperty.Find() method. That is where the DependencyPropertyHelper comes into play:

class DependencyPropertyHelper
{
    /// <summary>
    /// Finds a dependency property on an object by its name.
    ///
    /// Motivation: There is no accesible DP registry. When creating DPs, one must register them using
    /// DependencyProperty.Register(), but there is no DependencyProperty.FindProperty()
    /// </summary>
    /// <param name="_object">The object that supposedly has a property of the supplied name</param>
    /// <param name="_propertyName">The name of the property to find, e.g. "Height". NOTE: The corresponding property is expected to be named "HeightProperty" in this case.</param>
    /// <returns></returns>
    public static DependencyProperty FindDependencyProperty(object _object, string _propertyName)
    {
        if (null != _object && !String.IsNullOrEmpty(_propertyName))
        {
            Type typeAttachedTo = _object.GetType();
            FieldInfo fi = null;

            while (null == fi && typeAttachedTo != null)
            {
                fi = typeAttachedTo.GetField(_propertyName + "Property");
                typeAttachedTo = typeAttachedTo.BaseType;
            }

            if (null != fi)
            {
                return fi.GetValue(null) as DependencyProperty;
            }
        }
        return null;
    }
}

First results

At this point, we can supply a base class SerializableDependencyObject that allows simple serialization of DependencyObjects with their associated DependencyProperties. However, quite a number of dangerous pitfalls remain:

  • Setting DependencyValues can be problematic: We might trigger callbacks (PropertyChangedCallbacks, CoerceValueCallback)
  • Triggered callbacks might rely on yet unitialized data
  • We rely on the “Property” naming convention (e.g. the DP of property "Name" is called "NameProperty")
  • You need to define a deserialization constructor for each and every derived class which needs to call the base class’ deserialization constructor. Failing to do so throws a run-time exception.
  • The current code does not handle versioning

Outlook

Serializing dependency properties is indeed cumbersome. However, INotifyPropertyChanged might not be an alternative, and reinventing the wheel might not be desirable either. While basic functionality of DependencyProperties is probably quickly implemented, there are some obvious drawbacks, first and foremost the integration with other components that simply require a DependencyObject to work with.

Synchronizing a complex DataModel with sets of equally complex ViewModels can be a pain, too, so for those having such unusual demands, it’s their time to choose their poison.

In the second part, we’ll have a look at the ISerializationSurrogate interface and why we can’t implement it the same way we implemented the SerializableDependencyObject. We will also make the acquaintance of an infamous InvalidOperationException: “Current local value enumeration is outdated because one or more local values have been set since its creation.” which esentially keeps us (and probably microsoft) from implementing it just the way we did in here.

Post to Twitter Post to Delicious Post to Digg Post to Facebook

Related posts:

  1. DependencyProperty Serialization Part II

Tags: , ,

← Previous

Next →

2 Comments

  1. Interesting article. Unfortunately, you don’t provide full source code or a demo for. So, the question is Can you provide code for all referenced methods, classes? Also, have you published part II of the article.
    Thank you, anyway!

Leave a Comment