5

I have the following class serialized into a file using BinaryFormatter:

[Serializable]
public class TestClass
{
    public String ItemTwo { get; set; }
    public String ItemOne { get; set; }
}

Using this code:

FileStream fs = new FileStream("DataFile.dat", FileMode.Create);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, new TestClass{ItemOne = "ItemOne", ItemTwo = "ItemTwo"});
fs.Close();

When deserializing using this code:

FileStream fs = new FileStream("DataFile.dat", FileMode.Open);
BinaryFormatter formatter = new BinaryFormatter();
TestClass addresses = (TestClass)formatter.Deserialize(fs);
fs.Close();

I get everything normally. However, now I need the class to have some backing fields like so:

[Serializable]
public class TestClass
{
    private string _itemTwo;
    private string _itemOne;

    public String ItemTwo
    {
        get { return _itemTwo; }
        set { _itemTwo = value; }
    }

    public String ItemOne
    {
        get { return _itemOne; }
        set { _itemOne = value; }
    }
}

My problem is that now, for some reason, deserialization from previous version doesn't work anymore. I get the class but the Properties are left null.
I cannot affect the serialization process, or the former class state.
How can I get the file to deserialize to the current class?

atlanteh
  • 5,615
  • 2
  • 33
  • 54
  • I tried your code and it works fine. BinaryFormatter doesn't serialize private fields but it does serialize properties with backing fields. Which version of .NET are you using? – codingadventures Apr 16 '15 at 13:49
  • the binary formatter isn't version tolerant. you won't be able to without the old dll. load the old and new .dll files into a new project and write a converter program. or maybe ildasm the old dll to get the name it gave the backing field. – RadioSpace Apr 16 '15 at 13:51
  • @GiovanniCampo I'm using 4.5. We have a real life problem with this and I created a small program that simulates it and just as well it doesn't work. – atlanteh Apr 16 '15 at 13:53
  • @RadioSpace so if I find the backing field and add it to the class it should work? – atlanteh Apr 16 '15 at 13:53
  • I tried within linqpad and it deserializes correctly the properties, with or without backing fields. I tried as well in a VS2013 using .NET framework 4.5 and it works. please provide a snippet of the code you are using on gist – codingadventures Apr 16 '15 at 13:54
  • @GiovanniCampo are you sure you serialize the first version (with auto properties) and deserialize with second version (with backing fields)? – atlanteh Apr 16 '15 at 13:58
  • I can reproduce the problem (Using VS2013 .NET 4.5) I then created one serialized binary file for each version of your Testclass and this resulted in different files: [link](http://s14.postimg.org/srg20wtdd/binary_formaters.png) As you can see it refers to the backed field instead of the property and does some extra work for the auto properties. don't know how to solve this problem without creating a wrapper class though – Sors Apr 16 '15 at 14:11
  • @atlanteh I added an answer, look at it there is a detailed explanation – codingadventures Apr 16 '15 at 14:13
  • 3
    It does not matter whether you declare the backing field explicitly or not. If you don't then the compiler auto-generates one. The odds that you get binary compatibility with previous versions of the class are however zippo. You need to keep the original assembly around so that old data can still be deserialized with the old class definition. If your new class changed to much to still keep old data usable then you have a problem you can't solve. – Hans Passant Apr 16 '15 at 14:56

1 Answers1

6

If you try to serialize the first version of TestClass the backfields will be serialized automatically by the binary formatter. Auto properties are only syntactic sugar, the compiler transform them in normal properties with backing fields. If you decompile your original console application (or class library) with ILSpy for example you'll see that your private fields are declared as:

.field private string '<ItemOne>k__BackingField'

which is way different from your second declaration with backing fields which yields to:

.field private string _itemOne

An approach would be declaring the ISerializable interface on your TestClass and get the value of the original properties using the information contained in the class SerializationInfo

[Serializable]
public class TestClass : ISerializable
{
    private string _itemTwo;
    private string _itemOne;

    public String ItemTwo
    {
        get { return _itemTwo; }
        set { _itemTwo = value; }
    }

    public String ItemOne
    {
        get { return _itemOne; }
        set { _itemOne = value; }
    }

    protected TestClass(SerializationInfo info, StreamingContext context)
    {
        _itemTwo = info.GetString("<ItemTwo>k__BackingField");
        _itemOne = info.GetString("<ItemOne>k__BackingField");
    }

    [SecurityPermissionAttribute(SecurityAction.Demand,
    SerializationFormatter = true)]
    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        //Add data to your serialization process here
    }
}

so you tell the BinaryFormatter how you want your backing fields to be initialized during deserialization.

codingadventures
  • 2,924
  • 2
  • 19
  • 36