2011/05/22

Delegates serialization: Limits

When I heard about delegates’ serializability for the first time, my reaction was romantic. I dreamt about some mystical process that serializes delegate’s IL and about other magical things. It is, of course, absolute nonsense, but I love those fairy tales :).

All delegates in C# are inherited directly or indirectly from Delegate. Delegate class is class like any other. It is just marked as serializable – no black magic is needed.

What is buried there?

Basically, there are two properties: Method (MethodInfo) and Target (object). It is all what is needed to invoke any .NET method. When delegate points to a static method, then the Target property is null. There are some other internal details that I don’t understand but I hope they are irrelevant.

These facts reveal weaknesses of delegates’ serialization:
  1. Target property is serialized as well when using binary formatter. It could lead some serious issues when object referenced by Target is part of complex object graph. On the other side if Target is not serialized then the delegate cannot be deserialized at all – there wouldn’t be any suitable instance to make a method call.
  2. Versioning issues.
  3. State of static class is not serialized. Delegate of method that is using static fields (directly or indirectly) is deserialized into completely different context.
In addition to these disadvantages, there is a limitation associated with anonymous delegate that is closed over a variable. It is simply not serializable.

It is clear that “persistable continuation” implemented in C# by delegate serialization as it was suggested by Mike Hadlow would be very limited, which is a pity.

2011/05/15

NHibernate: Serializing Delegates

Mike Hadlow discovered very interesting delegate feature for me. They are serializable! In outher words they can be stored in a database, e.g. with NHibernate.

Entity that represents a persistable entity could look like this:
public class PersistableDelegate
{
    public virtual Delegate Delegate { get; set; }
    public virtual string Name { get; set; }
    public virtual int Id { get; set; }

    private byte[] SerializedDelegate {
        get {
            if (Delegate == null)
                return null;

            var formatter = new BinaryFormatter();
            using (var stream = new MemoryStream()) {
                formatter.Serialize(stream, this.Delegate);
                return stream.ToArray();
        }

        set {
            if (value != null) {
                var formatter = new BinaryFormatter();
                using (var stream = new MemoryStream(value))
                    Delegate = (Delegate)formatter.Deserialize(stream);
            }
            else
                Delegate = null;
        }
    }
}

Your model can manipulate with Delegate property. SerializedDelegate property provides access to serialized data of the delegate for NHibernate – it is implementation detail and is marked as private. Getter of this property returns the delegate serialized to an array and the setter reconstructs the delegate from provided data.

FNH mapping is simple as breath then:
public class PersistableDelegateMap : ClassMap<PersistableDelegate> {
    public PersistableDelegateMap() {
        Id(x => x.Id);
        Map(x => x.Name);
        Map(Reveal.Member<PersistableDelegate>("SerializedDelegate"))
            .Length(int.MaxValue);
    }
}
Reveal class makes it possible to map private property.

Corresponding database table for MS SQL is:
CREATE TABLE [dbo].[PersistenceAction](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](255) NULL,
    [SerializedDelegate] [image] NULL,
)

Now it is possible to store a delegate in database:
var action = new PersistableDelegate();
action.Delegate = (Action)(() => { Console.WriteLine("Hello from persisted delegate!"); });
action.Name = "First test";

session.Save(action);
session.Flush();

and load back:
var action = session.Query<PersistableDelegate>().Where(x => x.Name == "First test").FirstOrDefault();
action.Delegate.DynamicInvoke();

I am not sure yet how can be this useful. There are definitely many limitations, e.g. lambda expression closed over local variable is not serializable. In any case such feature is very promising...