3

Lacking any real foresight, I've serialized a large set of data decorated only with Serializable using NetDataContractSerializer, and now I'd like to add a new field. What are my options?

The original class looks something like this (with a few levels of inheritance and quite a few fields):

[Serializable]
public class InheritedClass : BaseClass
{
    public string StringId { get; set; }
}

And now I'd like to add another property, say something like:

[Serializable]
public class InheritedClass : BaseClass
{
    public string StringId { get; set; }
    public int IntId { get; set; }
}

Now when I update the class and go to deserialize, I receive an exception since the new field is not present, something like:

Exception thrown: 'System.Runtime.Serialization.SerializationException' in System.Runtime.Serialization.dll

Additional information: Error in line 1 position 601. 'Element' '_x003C_StringId_x003E_k__BackingField' from namespace 'http://schemas.datacontract.org/2004/07/QT' is not expected. Expecting element '_x003C_IntId_x003E_k__BackingField'.

Ok, so this makes sense since NetDataContractSerializer requires the same class. I can get around that using a DataMember attribute like:

[DataMember(IsRequired = false)]

The problem then is that switching to DataMember (as I should have done upfront, or used a different serializer) changes the implicit alphabetical ordering, and then most of my fields will silently not deserialize as is well known.

I've attempted to add an ordering that's inline with the ordering on disk manually (via Order properties on the attribute), but that doesn't appear to be respected either. (I don't see an order value I could match in the raw xml either.)

Are there any other options beyond writing something to load the xml and insert the missing node? (Or equivalently setup a parallel type and deserialize from one an re-serialize to another?) If not, I'll probably just load up with the current type and deserialize to JsonNet or protobuf, but am I missing anything more straightforward with DataMember/etc?

Gene
  • 1,587
  • 4
  • 18
  • 38

1 Answers1

2

Marking a type with [Serializable] means that the type can be serialized by serializing its public and private fields -- not its properties. NetDataContractSerializer respects this attribute when present, serializing the fields as indicated. For an auto-implemented property the secret backing field is what is actually serialized.

When adding a new field, what one generally does to handle legacy data is to mark it with [OptionalField] to indicate that it won't always be present in serialization streams. In c# 7.3 and later, it's possible to do this to the secret backing field of an auto-implemented property by using a field-targeted attribute:

[Serializable]
public class InheritedClass : BaseClass
{
    public string StringId { get; set; }

    [field: OptionalField]
    public int IntId { get; set; }
}    

Prior to c# 7.3 there is no way to apply an attribute to the backing field of an auto-implemented property. Thus you need to make the backing field be explicit and add the attribute to it:

[Serializable]
public class InheritedClass : BaseClass
{
    public string StringId { get; set; }

    [OptionalField]
    int intId;

    public int IntId { get { return intId; } set { intId = value; } }
}    

Notes:

  • As noted in the question, if a type is marked with data contract attributes then NetDataContractSerializer will use those in preference to the default [Serializable] contract and allow you to explicitly indicate properties to serialize (and provide names clearer than the secret backing field names).

    Unfortunately it is not always practical to add data contract attributes to legacy types.

  • NetDataContractSerializer has not been ported to .NET Core / .NET 5 and likely never will be.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 1
    I believe you actually *can* do that with auto-properties using the syntax `[field: OptionalField]`. – Sod Almighty Aug 01 '21 at 19:30
  • @SodAlmighty Hmm that looks correct. But [field-targeted attributes](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-7.3/auto-prop-field-attrs) were introduced in c# 7.3 nearly 2.5 years after I wrote this. Will check and update. – dbc Aug 01 '21 at 19:35