If I have a class with a number of fields, and I serialise it using binary serialisation.
I then extract a base class and move some of the fields from the class into the base class. Eg.
class Class
{
int field1;
string field2;
}
Is changed to
class Class : BaseClass
{
int field1;
}
class BaseClass
{
string field2;
}
Is it possible for this to break serialisation in any way - i.e. will it always be possible to deserialise old versions of Class into new versions of Class and vice versa.
Experiments I performed indicated that this is fine, but if somebody knows of any edge cases where this wouldn't work, that would obviously be great before I put this into production code.
EDIT:
There is a problem while deserializing: the member "field1" will not be properly deserialized.
1) Serialized person:
var person = new Employee()
{
Name = "Mark Zuckerberg",
Salary = 1000
};
var bf = new BinaryFormatter();
bf.Serialize(new FileStream("C:\\TEMP\\test.dat", FileMode.Create), person);
[Serializable]
public class Employee
{
public string Name { get; set; }
public decimal Salary { get; set; }
}
2) Changed class structure
[Serializable]
public abstract class Person
{
public string Name { get; set; }
}
[Serializable]
public class Employee : Person
{
public decimal Salary { get; set; }
}
3) Deserialized: Name is null
Original Answer:
Assuming you are using the BinaryFormatter, this article does not mention anything about it, so I guess it's fine (EDIT: It's NOT fine).
Here are some best practices to follow generally (also extracted from above article):
Never remove a serialized field.
Never apply the NonSerializedAttribute attribute to a field if the
attribute was not applied to the field in the previous version.
Never change the name or the type of a serialized field.
When adding a new serialized field, apply the OptionalFieldAttribute
attribute.
When removing a NonSerializedAttribute attribute from a field (that
was not serializable in a previous version), apply the
OptionalFieldAttribute attribute.
For all optional fields, set meaningful defaults using the
serialization callbacks unless 0 or null as defaults are acceptable.
To ensure that a type will be compatible with future serialization engines, follow these guidelines:
Always set the VersionAdded property on the OptionalFieldAttribute
attribute correctly.
Avoid branched versioning.
Related
In my ASP.NET MVC web application, I am using the built in Controller.Json() method to serialize an object and send it back to the client in response to an AJAX call. The class of the object being serialized inherits from another class with some shared property names. This is intentional, as I need the property names to match for some reflection that's happening. I am "shadowing" those properties in the derived class so that they can be a different type from their same-name counterpart in the base class. Here's a simplified example:
public class BaseModel
{
public string Title { get; set; }
public decimal CleanUpHours { get; set; }
public decimal InstallHours { get; set; }
}
public class DerivedModel : BaseModel
{
public new BucketHoursWithCalculations CleanUpHours { get; set; }
public new BucketHoursWithCalculations InstallHours { get; set; }
}
When I serialize an instance of DerivedModel, my JSON object on the client contains only the decimal versions of CleanUpHours and InstallHours, not my custom class BucketHoursWithCalculations.
Inspecting the object in Visual Studio before it gets serialized shows both the base and derived versions of those properties, as shown here (please excuse all the extra properties — my sample classes above are more simplified than what I'm actually using, but the principle is the same):
Here's what that object looks like on the client once it's serialized into JSON:
As you can see, the derived/shadowed properties were not serialized, and the base properties were, but only in the cases where there was a name conflict (for example, the Title property in the base model serialized just fine).
How can I serialize only the shadowed properties where there's a name conflict? I don't believe changing the access modifiers (i.e. from public to protected or something) on the base properties will work in my case, because the BaseModel is used by Entity Framework, and must have public properties. Any help would be appreciated.
One idea is to define type parameter on the base model that is used for the hours properties. Then, define derived models for decimal and BucketHoursWithCalculations. I would be interested to see how BucketHoursWithCalculations serializes to JSON, but in any case the CleanUpHours and InstallHours properties should be serialized.
// use a type parameter on the base model that must be specified
// in derived models.
public class BaseModel<THours>
{
public string Title { get; set; }
public THours CleanUpHours { get; set; }
public THours InstallHours { get; set; }
}
// hours are specified as decimals
public class DecimalModel : BaseModel<decimal>
{
}
// hours are specified as BucketHoursWithCalculations
public class BucketHoursWithCalculationsModel : BaseModel<BucketHoursWithCalculations>
{
}
// usage
DecimalModel d = new DecimalModel();
d.CleanUpHours = 1.0M; // CleanUpHours is a decimal here
BucketHoursWithCalculationsModel b = new BucketHoursWithCalculationsModel();
b.CleanUpHours = new BucketHoursWithCalculations();
b.CleanUpHours.SomeProperty = 1.0M;
I have a base class (eg)
[XmlType("address")]
[XmlInclude(typeof(AddressUK))]
[XmlInclude(typeof(AddressEurope))]
public class AddressBase
{
// No serialized properties
}
and as you can see, I've decorated it to serialize to an element of 'address' and to expect some subclasses:
public class AddressUK : AddressBase
{
[XmlElement("company")]
[AddressField(AddressFieldType.Organisation, Required = true, MaxSize = 60)]
public string Company { get; set; }
}
public class AddressEurope : AddressBase
{
[XmlElement("company")]
[AddressField(AddressFieldType.Organisation, Required = true, MaxSize = 40)]
public string Company { get; set; }
}
The only reason I have these subclasses is so that I can decorate them with custom AddressField attributes. (Each subclass potentially has different settings, and the base class has methods that populates the address fields based on these attributes). Other than the AddressField attributes, these sub classes should serialize identically
But when I do serialize this my address node serializes as (eg)
...
<address p5:type="AddressUK" xmlns:p5="http://www.w3.org/2001/XMLSchema-instance">
<company>Company Name</company>
...
I'd really like it to just serialize as <address> without the type and namespace information. I suspect it's trying to help me there, as without that information I wouldn't be able to deserialize it correctly, but for my scenario (integration with 3rd party provider) that information is redundant and not expected by them (and I don't need to deserialize in such an awkward way)
Is this possible, or am I approaching this from entirely the wrong angle?
I'm having a hard time finding a clear-cut answer. My understanding from the docs is that ProtoInclude and ProtoMember tags within a class need
to be unique, but not across the whole tree.
Meaning this should be fine:
[ProtoContract]
[ProtoInclude(2, typeof(Employee))]
public class Person{
[ProtoMember(1)]
public string Name { get; set; }
}
[ProtoContract]
public class Employee : Person{
[ProtoMember(1)]
public string Department{ get; set; }
}
Is this correct?
If so, what happens when serialized properties are overridden?
Thanks a lot.
do tags have to be unique across the entire inheritance tree?
No they do not. Your understanding is correct. protobuf-net implements inheritance as a shim via encapsulation of derived types, so the restriction is simply that the tags for the sub-types must not conflict with any other tags inside that local type. So:
// this contract is an INVALID example
[ProtoContract]
[ProtoInclude(1, typeof(Employee))]
public class Person{
[ProtoMember(1)]
public string Name { get; set; }
}
is invalid, as the tags of the sub-type Employee and the property Name conflict.
Outside of that type there is no conflict. Other types can use "sub-type 1" and other types can use "property 1", etc.
Say I have the following class:
[Serializable]
public class A
{
public string B { get; set; }
}
and the following method was used to serialize it:
public void Serialize()
{
BinaryFormatter b = new BinaryFormatter();
b.Serialize(new FileStream(#"C:\Temp\out.dat", FileMode.Create), new A());
}
If at some point, someone came along and modified the class definition to contain an extra property (or remove one):
[Serializable]
public class A // Same class
{
public string B { get; set; }
public string C { get; set; } // New property
}
then the following will break:
public void Deserialize()
{
BinaryFormatter b = new BinaryFormatter();
A deserialized = (A)b.Deserialize(new FileStream(#"C:\Temp\out.dat", FileMode.Open));
}
because the serialized version of the class does not match the class definition of the new class.
I really dislike the idea of serialization as a persistence mechanism because it's so fragile. I would have dealt with this much earlier if I had been involved in the decision.
Is there any way to get my serialized data into a form I can read it without reverting all of my current class definitions to their serialized state?
Even if it's hacky "to the max", I was hoping I could do this, because I would hopefully only do it once (or until I could figure out how to fix the root of the problem).
edit
There are dozens of these classes that have been serialized and then modified in my system already. It is not feasible to use version control to see exactly when and how each individual class changed.
I'm currently trying to figure out a way I can retain "old A"'s settings before the user tries to deserialize the object to the "new A" format and I have to 1) try, 2) catch, 3) swallow the error, and finally 4) recreate A with default values (based on the new object definition).
I believe that you can decorate newly added members with the OptionalFieldAttribute (System.Runtime.Serialization namespace), which will allow you to deserialize instances that were serialized before that member was added.
[Serializable]
public class A // Same class
{
public string B { get; set; }
[OptionalField] public string C { get; set; } // New property
}
Say I have the following set of classes, is it possible for the attributes of DerivedClass to be merged? Currently if I use the GetType().GetCustomAttributes()method passing in true for inheritance it takes the highest attributes in the inheritance structure.
i.e. [Another("Bob")] and [My(16)]
Is it possible for the attributes to be merged? So I would end up with two attributes of [My(16, "Male")] and [Another("Bob")]
I don't mean to say that I would specify an attribute of [My(16, "Male")] but rather I would be returned an attribute with an Age of 16 and a Gender of Male.
public class MyAttribute : Attribute
{
public MyAttribute(int age)
{
Age = age;
}
public MyAttribute(int age, string gender)
{
Age = age;
Gender = gender;
}
public int Age { get; set; }
public string Gender { get; set; }
}
public class AnotherAttribute : Attribute
{
public AnotherAttribute(string name)
{
Name = name;
}
public string Name { get; set; }
}
[My(12, "Male")]
[Another("Bob")]
public class BaseClass
{
}
[My(16)]
public class DerivedClass : BaseClass
{
}
You can have multiple instances of the same attribute on an entity (this is an option on the AttributeUsageAttribute applied to your attribute). When you get attributes on a type, you can get all the attributes applied to the inheritance chain.
However there is nothing in the standard tools that will take two attribute instances and make one.
(In theory a post processor that re-wrote the MSIL could do this.)
First, I don't believe any of the behavior you're inquiring about would depend on the AttributeUsage attribute which should be decorating your attribute classes (it looks like I could be wrong). Nor do I see where any attribute would be getting merged. Each attribute is a representation of a class in it's own right. Each attribute class should return it's own data. I would expect DerivedClass.GetCustomAttributes() to return three separate references for each attribute - 2 from the base class, and 1 for itself.
Try setting the AttributeUsage on the 'MyAttribute' to allow multiple. It may not be merging them, but taking the 'latest' version of the attribute.
I just thought of something else that could be causing the mysterious disappearing act. You may have to go after the base class to get the original attribute? You could try that:
base.GetType().GetCustomAttributes();
I did some digging and here is what the AttibuteUsage will do and could be causing you to only see one of the MyAttribute classes:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
I was close in my statement earlier about using AttributeUsage; however, it is the Inherited named parameter that will limit visibility to the base class' MyAttribute decoration. Try specifying Inherited = true and see what you get.