Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would BinaryFormatter attempt to cast an object of type marked [Serializable] to IConvertible?

I realize it has been well-established elsewhere that serializing your NHibernate domain objects is generally a bad idea. My question here is in trying to understand how BinaryFormatter works, and why the scenario below yields the InvalidCastException.

The class structure roughly looks like this:

[Serializable]
public class Parent
{
    public virtual Child child{get; set;} 
}

[Serializable]
public class Child
{
    public virtual ICollection<GrandChild> GrandChildren { get; set; }
}

[Serializable]
public class GrandChild
{
    public virtual Pet pet{get; set;} 
}

[Serializable]
public class Pet
{
    public virtual IList<Toy> Toys { get; set; }
}

[Serializable]
public class Toy
{
    public string ToyName { get; set; }
}

The serialization method looks like this:

public static byte[] Serialize(this object t)
{
    using (var ms = new MemoryStream())
    {
        BinarySerializer.Serialize(ms, t);
        return ms.ToArray();
    }
}

Sometimes when calling Serialization e.g.

 Parent p = new Parent() ....;
 p.Serialize();

I will get

Unable to cast object of type 'NHibernate.Collection.Generic.PersistentGenericBag`1[Toy]' to type 'System.IConvertible'.

(all collections are mapped with bag semantics).

Even NHibernate.Collection.Generic.PersistentGenericBag<T> is marked [Serializable]

So given that everything here is marked as [Serializable] why would BinaryFormatter be attempting to cast PersistentGenericBag to an IConvertible in the first place?

Edit: In case it's relevant, this is under .NET 3.5 and NHibernate 3.1.0

like image 459
Nathan Avatar asked Feb 02 '12 00:02

Nathan


1 Answers

By having the Pet class inherit from System.Runtime.Serialization.ISerializable, we now have complete control over how the Pet Class, and its members, in this case Toy, are serialized and de-serialized. Please see System.Runtime.Serialization.ISerializable, for more information about implementing System.Runtime.Serialization.ISerializable.

The sample below will serialize and then de-serialize an instance of the Parent class into a byte array and back again.

The public method, public GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context), is called when the Pet type is being serialized; first we add an Int32 value indicating the number of items in the Toys list. Then we add each Toy from the list.

The protected constructor, protected Pet(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context), is called when this type is being de-serialized. First we read the number of items that were stored in the Toys list, then use that to read each Toy instance from the serialized stream.

Please notice that for each Toy instance added to the serialized stream, we give it a different name; in this case we simply appended the value of the index to the word Toy; i.e. "Toy1", "Toy2",... This is because each item in the serialized stream needs a unique name. See: System.Runtime.Serialization.ISerializable.

And by controlling the serialization/de-serialization of the Toys list in Pet, we can eliminate the problem of not being able to serialize/de-serialize the list based on the type: NHibernate.Collection.Generic.PersistentGenericBag.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

static class Program
{
    static void Main(string[] args)
    {
        Parent p = new Parent();
        p.child = new Child();
        p.child.GrandChildren = new List<GrandChild>();
        p.child.GrandChildren.Add(new GrandChild { pet = new Pet() });
        p.child.GrandChildren.First().pet.Toys = new List<Toy>();
        p.child.GrandChildren.First().pet.Toys.Add(new Toy { ToyName = "Test" });
        byte[] result = Serialize(p);
        Parent backAgain = Deserialize(result);
    }
    public static System.Runtime.Serialization.Formatters.Binary.BinaryFormatter BinarySerializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); 
    public static byte[] Serialize(Parent p) 
    { 
        using (var ms = new System.IO.MemoryStream()) 
        {
            BinarySerializer.Serialize(ms, p); 
            return ms.ToArray(); 
        } 
    }
    public static Parent Deserialize( byte[] data)
    {
        using (var ms = new System.IO.MemoryStream(data))
        {
            return (Parent)BinarySerializer.Deserialize(ms);
        }
    }
}

[Serializable]
public class Parent
{
    public virtual Child child { get; set; }
}

[Serializable]
public class Child
{
    public virtual ICollection<GrandChild> GrandChildren { get; set; }
}

[Serializable]
public class GrandChild
{
    public virtual Pet pet { get; set; }
}

[Serializable]
public class Pet : System.Runtime.Serialization.ISerializable
{
    public Pet() { }

    // called when de-serializing (binary)
    protected Pet(System.Runtime.Serialization.SerializationInfo info,
                  System.Runtime.Serialization.StreamingContext context) 
    {
        Toys = new List<Toy>(); 
        int counter = info.GetInt32("ListCount");
        for (int index = 0; index < counter; index++)
        {
            Toys.Add((Toy)info.GetValue(string.Format("Toy{0}",index.ToString()),typeof(Toy)));
        }
    }

    // called when serializing (binary)
    public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, 
                              System.Runtime.Serialization.StreamingContext context)
    {
        info.AddValue("ListCount", Toys.Count);
        for (int index = 0; index < Toys.Count; index++)
        {
            info.AddValue(string.Format("Toy{0}", index.ToString()), Toys[index], typeof(Toy));
        }
    }

    public virtual IList<Toy> Toys { get; set; }
}

[Serializable]
public class Toy
{
    public string ToyName { get; set; }
}
like image 168
Randy Dodson Avatar answered Sep 28 '22 06:09

Randy Dodson