Serialization guidelines
This article lists the guidelines to consider when designing an API to be serialized.
Warning
Binary serialization can be dangerous. For more information, see BinaryFormatter security guide.
.NET offers the following serialization technologies that are optimized for various serialization scenarios. The following table lists these technologies and the main .NET types related to these technologies.
Technology | Relevant classes | Notes |
---|---|---|
JSON serialization | JsonSerializer | Introduced in .NET Core 3.0, this is the modern way to serialize to and from JSON. For more information, see JSON serialization and deserialization. |
Data contract serialization | DataContractAttribute DataMemberAttribute DataContractSerializer NetDataContractSerializer DataContractJsonSerializer ISerializable |
General persistence Web Services JSON |
XML serialization | XmlSerializer | XML format with full control |
Runtime serialization (binary and SOAP) | SerializableAttribute ISerializable BinaryFormatter SoapFormatter |
.NET Remoting Warning: Binary serialization can be dangerous. For more information, see BinaryFormatter security guide. |
When you design new types, you should decide which, if any, of these technologies those types need to support. The following guidelines describe how to make that choice and how to provide such support. These guidelines are not meant to help you choose which serialization technology you should use in the implementation of your application or library. Such guidelines are not directly related to API design and thus are not within the scope of this topic.
Guidelines
DO think about serialization when you design new types.
Serialization is an important design consideration for any type, because programs might need to persist or transmit instances of the type.
Choosing the right serialization technology to support
Any given type can support none, one, or more of the serialization technologies.
CONSIDER supporting data contract serialization if instances of your type might need to be persisted or used in Web Services.
CONSIDER supporting the XML serialization instead of or in addition to data contract serialization if you need more control over the XML format that is produced when the type is serialized.
This may be necessary in some interoperability scenarios where you need to use an XML construct that is not supported by data contract serialization, for example, to produce XML attributes.
CONSIDER supporting runtime serialization if instances of your type need to travel across .NET Remoting boundaries.
AVOID supporting runtime serialization or XML serialization just for general persistence reasons. Prefer data contract serialization instead
Data contract serialization
Types can support data contract serialization by applying the DataContractAttribute to the type and the DataMemberAttribute to the members (fields and properties) of the type.
[DataContract]
class Person
{
[DataMember]
string LastName { get; set; }
[DataMember]
string FirstName { get; set; }
public Person(string firstNameValue, string lastNameValue)
{
FirstName = firstNameValue;
LastName = lastNameValue;
}
}
<DataContract()> Public Class Person
<DataMember()> Public Property LastName As String
<DataMember()> Public Property FirstName As String
Public Sub New(ByVal firstNameValue As String, ByVal lastNameValue As String)
FirstName = firstNameValue
LastName = lastNameValue
End Sub
End Class
CONSIDER marking data members of your type public if the type can be used in partial trust. In full trust, data contract serializers can serialize and deserialize nonpublic types and members, but only public members can be serialized and deserialized in partial trust.
DO implement a getter and setter on all properties that have Data-MemberAttribute. Data contract serializers require both the getter and the setter for the type to be considered serializable. If the type won't be used in partial trust, one or both of the property accessors can be nonpublic.
[DataContract] class Person2 { string lastName; string firstName; public Person2(string firstName, string lastName) { this.lastName = lastName; this.firstName = firstName; } [DataMember] public string LastName { // Implement get and set. get { return lastName; } private set { lastName = value; } } [DataMember] public string FirstName { // Implement get and set. get { return firstName; } private set { firstName = value; } } }
<DataContract()> Class Person2 Private lastNameValue As String Private firstNameValue As String Public Sub New(ByVal firstName As String, ByVal lastName As String) Me.lastNameValue = lastName Me.firstNameValue = firstName End Sub <DataMember()> Property LastName As String Get Return lastNameValue End Get Set(ByVal value As String) lastNameValue = value End Set End Property <DataMember()> Property FirstName As String Get Return firstNameValue End Get Set(ByVal value As String) firstNameValue = value End Set End Property End Class
CONSIDER using the serialization callbacks for initialization of deserialized instances.
Constructors are not called when objects are deserialized. Therefore, any logic that executes during normal construction needs to be implemented as one of the serialization callbacks.
[DataContract] class Person3 { [DataMember] string lastName; [DataMember] string firstName; string fullName; public Person3(string firstName, string lastName) { // This constructor is not called during deserialization. this.lastName = lastName; this.firstName = firstName; fullName = firstName + " " + lastName; } public string FullName { get { return fullName; } } // This method is called after the object // is completely deserialized. Use it instead of the // constructror. [OnDeserialized] void OnDeserialized(StreamingContext context) { fullName = firstName + " " + lastName; } }
<DataContract()> _ Class Person3 <DataMember()> Private lastNameValue As String <DataMember()> Private firstNameValue As String Dim fullNameValue As String Public Sub New(ByVal firstName As String, ByVal lastName As String) lastNameValue = lastName firstNameValue = firstName fullNameValue = firstName & " " & lastName End Sub Public ReadOnly Property FullName As String Get Return fullNameValue End Get End Property <OnDeserialized()> Sub OnDeserialized(ByVal context As StreamingContext) fullNameValue = firstNameValue & " " & lastNameValue End Sub End Class
The OnDeserializedAttribute attribute is the most commonly used callback attribute. The other attributes in the family are OnDeserializingAttribute, OnSerializingAttribute, and OnSerializedAttribute. They can be used to mark callbacks that get executed before deserialization, before serialization, and finally, after serialization, respectively.
CONSIDER using the KnownTypeAttribute to indicate concrete types that should be used when deserializing a complex object graph.
For example, if a type of a deserialized data member is represented by an abstract class, the serializer will need the known type information to decide what concrete type to instantiate and assign to the member. If the known type is not specified using the attribute, it will need to be passed to the serializer explicitly (you can do it by passing known types to the serializer constructor) or it will need to be specified in the configuration file.
// The KnownTypeAttribute specifies types to be // used during serialization. [KnownType(typeof(USAddress))] [DataContract] class Person4 { [DataMember] string fullNameValue; [DataMember] Address address; // Address is abstract public Person4(string fullName, Address address) { this.fullNameValue = fullName; this.address = address; } public string FullName { get { return fullNameValue; } } } [DataContract] public abstract class Address { public abstract string FullAddress { get; } } [DataContract] public class USAddress : Address { [DataMember] public string Street { get; set; } [DataMember] public string City { get; set; } [DataMember] public string State { get; set; } [DataMember] public string ZipCode { get; set; } public override string FullAddress { get { return Street + "\n" + City + ", " + State + " " + ZipCode; } } }
<KnownType(GetType(USAddress)), _ DataContract()> Class Person4 <DataMember()> Property fullNameValue As String <DataMember()> Property addressValue As USAddress ' Address is abstract Public Sub New(ByVal fullName As String, ByVal address As Address) fullNameValue = fullName addressValue = address End Sub Public ReadOnly Property FullName() As String Get Return fullNameValue End Get End Property End Class <DataContract()> Public MustInherit Class Address Public MustOverride Function FullAddress() As String End Class <DataContract()> Public Class USAddress Inherits Address <DataMember()> Public Property Street As String <DataMember()> Public City As String <DataMember()> Public State As String <DataMember()> Public ZipCode As String Public Overrides Function FullAddress() As String Return Street & "\n" & City & ", " & State & " " & ZipCode End Function End Class
In cases where the list of known types is not known statically (when the Person class is compiled), the KnownTypeAttribute can also point to a method that returns a list of known types at run time.
DO consider backward and forward compatibility when creating or changing serializable types.
Keep in mind that serialized streams of future versions of your type can be deserialized into the current version of the type, and vice versa. Make sure you understand that data members, even private and internal, cannot change their names, types, or even their order in future versions of the type unless special care is taken to preserve the contract using explicit parameters to the data contract attributes.Test compatibility of serialization when making changes to serializable types. Try deserializing the new version into an old version, and vice versa.
CONSIDER implementing IExtensibleDataObject interface to allow round-tripping between different versions of the type.
The interface allows the serializer to ensure that no data is lost during round-tripping. The ExtensionData property stores any data from the future version of the type that is unknown to the current version. When the current version is subsequently serialized and deserialized into a future version, the additional data will be available in the serialized stream through the ExtensionData property value.
// Implement the IExtensibleDataObject interface. [DataContract] class Person5 : IExtensibleDataObject { ExtensionDataObject serializationData; [DataMember] string fullNameValue; public Person5(string fullName) { this.fullNameValue = fullName; } public string FullName { get { return fullNameValue; } } ExtensionDataObject IExtensibleDataObject.ExtensionData { get { return serializationData; } set { serializationData = value; } } }
<DataContract()> Class Person5 Implements IExtensibleDataObject <DataMember()> Dim fullNameValue As String Public Sub New(ByVal fullName As String) fullName = fullName End Sub Public ReadOnly Property FullName Get Return fullNameValue End Get End Property Private serializationData As ExtensionDataObject Public Property ExtensionData As ExtensionDataObject Implements IExtensibleDataObject.ExtensionData Get Return serializationData End Get Set(ByVal value As ExtensionDataObject) serializationData = value End Set End Property End Class
For more information, see Forward-Compatible Data Contracts.
XML serialization
Data contract serialization is the main (default) serialization technology in .NET Framework, but there are serialization scenarios that data contract serialization does not support. For example, it does not give you full control over the shape of XML produced or consumed by the serializer. If such fine control is required, XML serialization has to be used, and you need to design your types to support this serialization technology.
AVOID designing your types specifically for XML Serialization, unless you have a very strong reason to control the shape of the XML produced. This serialization technology has been superseded by the Data Contract Serialization discussed in the previous section.
In other words, don't apply attributes from the System.Xml.Serialization namespace to new types, unless you know that the type will be used with XML Serialization. The following example shows how System.Xml.Serialization can be used to control the shape of the XML -produced.
public class Address2 { [System.Xml.Serialization.XmlAttribute] // Serialize as XML attribute, instead of an element. public string Name { get { return "Poe, Toni"; } set { } } [System.Xml.Serialization.XmlElement(ElementName = "StreetLine")] // Explicitly name the element. public string Street = "1 Main Street"; }
Public Class Address2 ' Supports XML Serialization. <System.Xml.Serialization.XmlAttribute()> _ Public ReadOnly Property Name As String ' Serialize as XML attribute, instead of an element. Get Return "Poe, Toni" End Get End Property <System.Xml.Serialization.XmlElement(ElementName:="StreetLine")> _ Public Street As String = "1 Main Street" ' Explicitly names the element 'StreetLine'. End Class
CONSIDER implementing the IXmlSerializable interface if you want even more control over the shape of the serialized XML than what's offered by applying the XML Serialization attributes. Two methods of the interface, ReadXml and WriteXml, allow you to fully control the serialized XML stream. You can also control the XML schema that gets generated for the type by applying the XmlSchemaProviderAttribute attribute.
Runtime serialization
Runtime serialization is a technology used by .NET Remoting. If you think your types will be transported using .NET Remoting, make sure they support runtime serialization.
The basic support for runtime serialization can be provided by applying the SerializableAttribute attribute, and more advanced scenarios involve implementing a simple runtime serializable pattern (implement -ISerializable and provide a serialization constructor).
CONSIDER supporting runtime serialization if your types will be used with .NET Remoting. For example, the System.AddIn namespace uses .NET Remoting, and so all types exchanged between System.AddIn add-ins need to support runtime serialization.
// Apply SerializableAttribute to support runtime serialization. [Serializable] public class Person6 { // Code not shown. }
<Serializable()> Public Class Person6 ' Support runtime serialization with the SerializableAttribute. ' Code not shown. End Class
CONSIDER implementing the runtime serializable pattern if you want complete control over the serialization process. For example, if you want to transform data as it gets serialized or deserialized.
The pattern is very simple. All you need to do is implement the ISerializable interface and provide a special constructor that is used when the object is deserialized.
// Implement the ISerializable interface for more control. [Serializable] public class Person_Runtime_Serializable : ISerializable { string fullName; public Person_Runtime_Serializable() { } protected Person_Runtime_Serializable(SerializationInfo info, StreamingContext context){ if (info == null) throw new System.ArgumentNullException("info"); fullName = (string)info.GetValue("name", typeof(string)); } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { if (info == null) throw new System.ArgumentNullException("info"); info.AddValue("name", fullName); } public string FullName { get { return fullName; } set { fullName = value; } } }
' Implement the ISerializable interface for more control. <Serializable()> Public Class Person_Runtime_Serializable Implements ISerializable Private fullNameValue As String Public Sub New() ' empty constructor. End Sub Protected Sub New(ByVal info As SerializationInfo, _ ByVal context As StreamingContext) If info Is Nothing Then Throw New System.ArgumentNullException("info") FullName = CType(info.GetValue("name", GetType(String)), String) End If End Sub Private Sub GetObjectData(ByVal info As SerializationInfo, _ ByVal context As StreamingContext) _ Implements ISerializable.GetObjectData If info Is Nothing Then Throw New System.ArgumentNullException("info") End If info.AddValue("name", FullName) End Sub Public Property FullName As String Get Return fullNameValue End Get Set(ByVal value As String) fullNameValue = value End Set End Property End Class
DO make the serialization constructor protected and provide two parameters typed and named exactly as shown in the sample here.
protected Person_Runtime_Serializable(SerializationInfo info, StreamingContext context){
Protected Sub New(ByVal info As SerializationInfo, _ ByVal context As StreamingContext)
DO implement the ISerializable members explicitly.
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
Private Sub GetObjectData(ByVal info As SerializationInfo, _ ByVal context As StreamingContext) _ Implements ISerializable.GetObjectData