views:

156

answers:

5

It sounds strange but this is exactly what I want because I am using a data structure named "Project" which gets serialized to a save file. I would like to be able to de-serialize an older version of a save file with deprecated fields in tact, but then re-serialize it using only the currently used fields. The problem is that I want to get rid of those deprecated fields when re-serializing the structure in order to minimize file size. Is it possible to mark a field as "de-serializable only"?

Edit:

Thanks for the ideas! I decided to build mostly off of NickLarsen's suggestions and create an old version of the project structure with all depreciated fields in a separate namespace. The difference is that I decided to perform the upgrade upon deserialization. This is great because I can do it all in one line (hopefully you can get the gist what I'm doing here):

Project myProject = new Project((Depreciated.Project)myFormatter.Deserialize(myStream));

The constructor simply returns a new instance of the fresh minimal data structure based on the old bloated one.

Second Edit:

I decided to follow the advice of bebop instead and create new classes for each project version with the oldest version including all depreciated and new fields. Then the constructor of each project upgrades to the next version getting rid of depreciated fields along the way. Here is an illustrative example of converting from version 1.0.0 -> 1.0.5 -> current.

Project myProject = new Project(new Project105((Project100)myFormatter.Deserialize(myStream)));

One key to this is to forced the deserialized file as well as any fields into the older versions of the classes by using a SerializationBinder.

A: 

As far as I know .NET is very careful to only serialise things it can deserialise and vice versa.

Pike65
A: 

There isn't an attribute defined to explicitly support that but one possbility may be to define custom serialization using either ISerializable or by defining a custom serializer class for your type that takes no action when serializing

Crippledsmurf
It looks like in both of those cases, the events get fired only after deserialization has already occurred.
Ben McIntosh
My bad, I meant ISerializable, the other two are post serialization, my bad :( Updated post to reflect this, sorry if that caused confusion for people
Crippledsmurf
+1  A: 

could you not create a new version of your data structure class each time the structure changes, and have the constructor for the new class take an instance of the previous class, and populate itself from there. To load the newest class you try and create the earliest class from the serialised file until one succeeds, and then pass that into the constructor of the next class in the chain repeatedly until you get the latest version of the data structure then you can save that.

Having a new class for each change in format would avoid having to change any existing code when the data structure changed, and your app could be ignorant of the fact that the save file was some older version. It would allow you to load from any previous version, not just the last one.

This sort of thing implemented by a chain of responsibility can make it easy to slot in a new format with minimal changes to your existing code.

Whilst not a textbook chain of responsibility you could implement with something like this:

(NOTE: untested code)

public interface IProductFactory<T> where T : class
{
    T CreateProduct(string filename);
    T DeserializeInstance(string filename);
}

public abstract  class ProductFactoryBase<T> :  IProductFactory<T> where T : class
{
    public abstract T CreateProduct(string filename);

    public T DeserializeInstance(string filename)
    {
        var myFormatter = new BinaryFormatter();
        using (FileStream stream = File.Open(filename, FileMode.Open))
        {
            return myFormatter.Deserialize(stream) as T;
        }

    }
}

public class ProductV1Factory : ProductFactoryBase<ProductV1>
{
    public override ProductV1 CreateProduct(string filename)
    {
        return DeserializeInstance(filename);
    }
}

public class ProductV2Factory : ProductFactoryBase<ProductV2>
{
    ProductV1Factory successor = new ProductV1Factory();
    public override ProductV2 CreateProduct(string filename)
    {
        var product = DeserializeInstance(filename);
        if (product==null)
        {
            product = new ProductV2(successor.CreateProduct(filename));
        }
        return product;
    }

}

public class ProductV2
{
    public ProductV2(ProductV1 product)
    {
        //construct from V1 information
    }
}


public class ProductV1  
{
}

this has the advantage that when you want to add ProductV3 you only need to change the class you are using in your app to be a ProductV3 type, which you need to do anyway, then you change your loading code so that it uses a ProductV3Factory, which is basically the same as a ProductV2Factory, but it uses a ProductV2Factory as the successor. You don't need to change any existing classes. you could probably refactor this a bit to get the commanality of CreateProduct into a base class, but it gets the idea across.

Sam Holder
+1  A: 

You have a couple options for this.

First you could create a version of your class (maybe in a different namespace) for the old format, and one for the new format. In the old class, overload the serialize function to throw an error, or convert itself to the new class and serialize that.

Second, you could just write your own serializer, which would be a bit more involved. There are plenty of resources which can help you though.

NickLarsen
A: 

I think what you are searching for is the OptionalFieldAttribute.

Ucodia
This handles the case when fields added in later versions aren't available on old version serialized data, but in this case the problem is deprecated fields, which this attribute does not handle.
NickLarsen
Yes you are totally true. This would only work in case of a "serializable only" field. I red too fast. Also, I did not thought that deprecated fields should be removed.
Ucodia
Deprecated means "slated for removal" or "there is a preferred way to get the same result by other means" in the programming world.
NickLarsen
From what I learnt, deleting serialized field is not a really good practice for version compatibility, that is why I could not imagine newer versions with fewer fields.
Ucodia