Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
527 views
in Technique[技术] by (71.8m points)

serialization - Changing types during binary deserialization in C#

One of the solutions in our company consumes a 3rd party service. Communication is done through XML messaging. On our end, we generate classes to be used based on XML schemas that they provide us, and at some point we serialize some of those types into a binary blob in our database for later use.

The problem comes in where that 3rd party company has changed one of the fields from a boolean to an integer type. Now when we try to deserialize the data that's already there we predictably get a type conversion exception (can't convert from boolean to integer).

My question is - how do we go about deserializing the existing data in our database with the old boolean type to convert it to the new integer type?

I've tried a number of things - among which were reflection and implementing ISerializable, but nothing's panned out so far. The ideal solution would be to implement ISerializable, but I get a "Member not found" error when trying to deserialize the existing data because it was already serialized using only the Serializable attribute.

Any suggestions are welcome!

Edit: Adding some code to clearly demonstrate my problem.

namespace ClassLibrary
{
    [Serializable]
    public class Foo //: ISerializable
    {
        public bool Bar { get; set; }

        public Foo() { }

        //[OnDeserializing()]
        //internal void OnDeserializingMethod(StreamingContext context)
        //{
        //    Bar = 10;
        //}

        //public Foo(SerializationInfo info, StreamingContext context)
        //{
        //    Bar = (int)info.GetValue("Bar", typeof(int));
        //}

        //public void GetObjectData(SerializationInfo info, StreamingContext context)
        //{
        //    info.AddValue("Bar", Bar);
        //}
    }
}

namespace ConsoleApplication2
{
    static class Program
    {
        static void Main(string[] args)
        {
            Foo foo;

            // Run #1, where Foo.Bar is a boolean

            foo = new Foo();
            foo.Bar = true;
            SerializeObject(foo);
            byte[] data = File.ReadAllBytes(@".Test.bin");
            foo = DeserializeObject(data) as Foo;

            // Now change Foo.Bar to an integer type, comment the lines above, and uncomment the two lines below
            //byte[] newData = File.ReadAllBytes(@".Test.bin");
            //foo = DeserializeObject(newData) as Foo;

            Console.WriteLine(foo.Bar);
            Console.ReadLine();
        }

        private static Object DeserializeObject(byte[] buff)
        {
            if (buff == null) return null;
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Binder = new CustomSerializationBinder();
            MemoryStream ms = new MemoryStream(buff);
            return formatter.Deserialize(ms);
        }

        private static void SerializeObject(Object obj)
        {
            if (obj == null) return;
            BinaryFormatter formatter = new BinaryFormatter();
            using (FileStream ms = new FileStream(@".Test.bin", FileMode.Create))
            {
                formatter.Serialize(ms, obj);
            }
        }
    }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

You can handle this situation with ISerializable, however you will need to loop through the deserialized properties using SerializationInfo.GetEnumerator to determine what type of data was actually read from the stream. In addition, you need to be aware that BinaryFormatter serializes fields not properties, so if your class used Auto-Implemented Properties then the name previously stored in the binary stream will be the name of the backing field not the name of the property.

For instance, say this is your original class:

[Serializable]
public class Foo
{
    public bool Bar { get; set; }

    public Foo() { }
}

Now you want to change Bar to an integer. To serialize or deserialize both old and new BinaryFormatter streams, use the following implementation of ISerializable:

[Serializable]
public class Foo : ISerializable
{
    public int Bar { get; set; }

    public Foo() { }

    public Foo(SerializationInfo info, StreamingContext context)
    {
        var enumerator = info.GetEnumerator();
        while (enumerator.MoveNext())
        {
            var current = enumerator.Current;
            Debug.WriteLine(string.Format("{0} of type {1}: {2}", current.Name, current.ObjectType, current.Value));
            if (current.Name == "Bar" && current.ObjectType == typeof(int))
            {
                Bar = (int)current.Value;
            }
            else if (current.Name == "<Bar>k__BackingField" && current.ObjectType == typeof(bool))
            {
                var old = (bool)current.Value;
                Bar = (old ? 1 : 0); // Or whatever.
            }
        }
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Bar", Bar);
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...