views:

1713

answers:

3

I have almost a hundred of entity classes looking like that:

[Serializable]
public class SampleEntity : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return this.name; }
        set { this.name = value; FirePropertyChanged("Name"); }
    }

    [field:NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;

    private void FirePropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this,
                new PropertyChangedEventArgs(propertyName));
    }
}

Notice the [field:NonSerialized] attribute on PropertyChanged. This is necessary as some of the observers (in my case - a grid displaying the entities for edition) may not be serializable, and the entity has to be serializable, because it is provided - via remoting - by an application running on a separater machine.

This solution works fine for trivial cases. However, it is possible that some of the observers are [Serializable], and would need to be preserved. How should I handle this?

Solutions I am considering:

  • full ISerializable - custom serialization requires writing a lot of code, I'd prefer not to do this
  • using [OnSerializing] and [OnDeserializing] attributes to serialize PropertyChanged manually - but those helper methods provide only SerializationContext, which AFAIK does not store serialization data (SerializationInfo does that)
+2  A: 

You're right that the first option is more work. Whilst it can potentially give you a more efficient implementation, it will complicate your entities a lot. Consider that if you have a base Entity class that implements ISerializable, all subclasses also have to manually implement serialization!

The trick to getting the second option to work, is to continue marking the event as non-serializable, but to have a second field that is serializable and that you populate yourself during the appropriate serialization hooks. Here is a sample I just wrote to show you how:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ConsoleApplication1
{
    class Program
    {
     static void Main(string[] args)
     {
      var entity = new Entity();
      entity.PropertyChanged += new SerializableHandler().PropertyChanged;
      entity.PropertyChanged += new NonSerializableHandler().PropertyChanged;

      Console.WriteLine("Before serialization:");
      entity.Name = "Someone";

      using (var memoryStream = new MemoryStream())
      {
       var binaryFormatter = new BinaryFormatter();
       binaryFormatter.Serialize(memoryStream, entity);
       memoryStream.Position = 0;
       entity = binaryFormatter.Deserialize(memoryStream) as Entity;
      }

      Console.WriteLine();
      Console.WriteLine("After serialization:");
      entity.Name = "Kent";

      Console.WriteLine();
      Console.WriteLine("Done - press any key");
      Console.ReadKey();
     }

     [Serializable]
     private class SerializableHandler
     {
      public void PropertyChanged(object sender, PropertyChangedEventArgs e)
      {
       Console.WriteLine("  Serializable handler called");
      }
     }

     private class NonSerializableHandler
     {
      public void PropertyChanged(object sender, PropertyChangedEventArgs e)
      {
       Console.WriteLine("  Non-serializable handler called");
      }
     }
    }

    [Serializable]
    public class Entity : INotifyPropertyChanged
    {
     private string _name;
     private readonly List<Delegate> _serializableDelegates;

     public Entity()
     {
      _serializableDelegates = new List<Delegate>();
     }

     public string Name
     {
      get { return _name; }
      set
      {
       if (_name != value)
       {
        _name = value;
        OnPropertyChanged("Name");
       }
      }
     }

     [field:NonSerialized]
     public event PropertyChangedEventHandler PropertyChanged;

     protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
     {
      var handler = PropertyChanged;

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

     protected void OnPropertyChanged(string propertyName)
     {
      OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
     }

     [OnSerializing]
     public void OnSerializing(StreamingContext context)
     {
      _serializableDelegates.Clear();
      var handler = PropertyChanged;

      if (handler != null)
      {
       foreach (var invocation in handler.GetInvocationList())
       {
        if (invocation.Target.GetType().IsSerializable)
        {
         _serializableDelegates.Add(invocation);
        }
       }
      }
     }

     [OnDeserialized]
     public void OnDeserialized(StreamingContext context)
     {
      foreach (var invocation in _serializableDelegates)
      {
       PropertyChanged += (PropertyChangedEventHandler)invocation;
      }
     }
    }
}

HTH, Kent

Kent Boogaart
invocation.Target can be null (for anonymous delegates), be sure to check for this
skolima
Those events have no effect on XmlSerializer (fine be me, I needed just the Remoting to work).
skolima
OnSerializing and OnDeserialized should be private, no need to expose them.
skolima
_serializableDelegates can be cleared in OnDeserialized and OnSerialized to reduce memory usage (instead of clearing in OnSerializing).
skolima
And I have to remember that the deserialized object graph contains new objects, the old references no longer work. D'oh!
skolima
A: 

[field:NonSerialized] The little snippet of gold. Thanks :)

Rich L
A: 

Agree with Rich L. [field:NonSerialized] solves a problem with serializing an object with a Property changed notification built in. Snippet of gold for sure. Thanks

Chris J