0

Sorry if I've worded the question a bit odd! Basically, I have a serializable class that has a single field at this current time, but will definitely gain more in the future as we add features to the system. The serialization process will be used for both passing instances to a WCF service, and for reading/writing it from/to file. Of course, the latter may be a big problem if I'm continually updating the class with extra fields.

Luckily, I think I've solved the problem by adding a try/catch block around the field setter in the constructor, which I'll also do for any other fields that are added to the class.

/// <summary>
/// Represents launch options supplied to an executable.
/// </summary>
[Serializable]
public sealed class ExecutableLaunchOptions : ISerializable
{
    /// <summary>
    /// Creates a new set of executable launch options.
    /// </summary>
    public ExecutableLaunchOptions() { }

    /// <summary>
    /// Creates a new set of executable launch options via deserialization.
    /// </summary>
    /// <param name="info">the serialization information.</param>
    /// <param name="context">the streaming context.</param>
    public ExecutableLaunchOptions(
        SerializationInfo info, StreamingContext context) : this()
    {
        // Get the value of the window style from the serialization information.
        try { this.windowStyle = (ProcessWindowStyle)info.GetValue(nameof(this.windowStyle), typeof(ProcessWindowStyle)); } catch { }
    }

    // Instance variables.
    private ProcessWindowStyle windowStyle = ProcessWindowStyle.Normal;

    /// <summary>
    /// Gets or sets the window style to apply to the executable when it launches.
    /// </summary>
    public ProcessWindowStyle WindowStyle
    {
        get { return this.windowStyle; }

        set { this.windowStyle = value; }
    }

    /// <summary>
    /// Gets the information required for the serialization of this set of launch options.
    /// </summary>
    /// <param name="info">the serialization information.</param>
    /// <param name="context">the streaming context.</param>
    public void GetObjectData(
        SerializationInfo info, StreamingContext context)
    {
        // Add the value of the window style to the serialization information.
        info.AddValue(nameof(this.windowStyle), this.windowStyle, typeof(ProcessWindowStyle));
    }
}

I'm guessing this will allow me to retain backwards-compatibility with files containing instances of previous versions of the class when I deserialize them, as the code will simply throw and subsequently catch exceptions for each field that doesn't exist in the serialization information, leaving their values at their default. Am I correct here, or am I missing something?

Lee.J.Baxter
  • 495
  • 4
  • 12

2 Answers2

0

Serializing objects with the FormatterAssemblyStyle.Simple formatter will allow you to read old versions of your serialized objects. Marking new fields with [OptionalField] will allow old versions of your app to open new serialization files without throwing. So should you use them? No. Nooo no noooo no no no.

Serialization was designed for exchanging data between processes, it was not, and is not, a data persistence mechanism. The format has changed in the past, and may change in the future, so it is unsafe to use it for persistent data you expect to open again in the future with a new .NET version.

The BinaryFormatter algorithm is proprietary, so it will be very difficult to write non-.NET applications using such data.

Serialization is inefficient for data that contains multiple properties because it requires deserializing the entire object to access any field. This is especially problematic if the data contains large data like images.

If your data does not require random access, I suggest serializing it to a text format like JSON or XML. If the data is large, you should consider compressing the text-encoded data.

If you require random-access to data you should investigate data stores like MySQL or SQL Server CE.

Dour High Arch
  • 21,513
  • 29
  • 75
  • 90
  • It's not clear that @Lee.J.Baxter is using `BinaryFormatter`. The question mentions WCF, and `DataContractSerializer` [supports `ISerializable`](https://msdn.microsoft.com/en-us/library/ms731923%28v=vs.110%29.aspx). So OP could be reading from XML or Json with a data contact serializer but still need to handle conditionally present values in `SerializationInfo`. Still, you're right that `BinaryFormatter` is a poor choice for data persistence. – dbc Sep 17 '15 at 23:40
0

You can use SerializationInfo.GetEnumerator() to loop through the name-value pairs contained in the SerializationInfo to look for items that might only be conditionally present:

    public ExecutableLaunchOptions(
        SerializationInfo info, StreamingContext context) : this()
    {
        // Get the value of the window style from the serialization information.
        var enumerator = info.GetEnumerator();
        while (enumerator.MoveNext())
        {
            var current = enumerator.Current;
            if (current.Name == "windowStyle" && current.ObjectType == typeof(ProcessWindowStyle))
            {
                this.windowStyle = (ProcessWindowStyle)current.Value;
            }
        }
    }

Note that this class is so old (i.e. from c# 1.0) that, despite having a GetEnumerator() method, it doesn't actually implement IEnumerable.

If you need to do this often, you can introduce an extension method like so:

public static class SerializationInfoExtensions
{
    public static IEnumerable<SerializationEntry> AsEnumerable(this SerializationInfo info)
    {
        if (info == null)
            throw new NullReferenceException();
        var enumerator = info.GetEnumerator();
        while (enumerator.MoveNext())
        {
            yield return enumerator.Current;
        }
    }
}

And then do:

    public ExecutableLaunchOptions(
        SerializationInfo info, StreamingContext context) : this()
    {
        foreach (var current in info.AsEnumerable())
        {
            if (current.Name == "windowStyle" && current.ObjectType == typeof(ProcessWindowStyle))
            {
                this.windowStyle = (ProcessWindowStyle)current.Value;
            }
        }
    }

Which is a bit more modern and readable.

dbc
  • 104,963
  • 20
  • 228
  • 340