Diretrizes de serialização
Este documento lista as diretrizes a serem consideradas na criação de uma API a ser serializado.
O .NET Framework oferece três tecnologias de serialização principais que são otimizadas para vários cenários de serialização. A tabela a seguir lista essas tecnologias e os principais tipos de estrutura relacionados a elas.
Tecnologia |
Classes relevantes |
Notas |
---|---|---|
Serialização do contrato de dados |
DataContractAttribute DataMemberAttribute DataContractSerializer NetDataContractSerializer DataContractJsonSerializer ISerializable |
Persistência geral Serviços Web JSON |
Serialização XML |
XmlSerializer |
Formato XML com controle total |
Tempo de execução - serialização (binário e SOAP) |
SerializableAttribute ISerializable BinaryFormatter SoapFormatter |
Comunicação remota .NET |
Ao criar novos tipos, você deverá decidir a quais dessas tecnologias, se houver alguma, esses tipos precisam oferecer suporte. As diretrizes a seguir descrevem como fazer essa escolha e como fornecer esse suporte. Essas diretrizes não foram criadas para ajudar você a escolher qual tecnologia de serialização usará na implementação do aplicativo ou da biblioteca. Elas não estão diretamente relacionadas ao design da API e, portanto, não estão dentro do escopo deste tópico.
Diretrizes
Pense sobre a serialização ao criar novos tipos.
A serialização é uma questão de design importante a ser considerada para qualquer tipo, pois os programas talvez precisem persistir ou transmitir instâncias do tipo.
Escolhendo a tecnologia de serialização correta que receberá suporte
Qualquer tipo específico pode oferecer suporte a nenhuma, uma ou mais tecnologias de serialização.
CONSIDERE o suporte à serialização de contrato de dados se as instâncias do tipo talvez precisem ser persistentes ou usadas nos Serviços Web.
CONSIDERE o suporte à serialização XML em vez da ou além da serialização do contrato de dados se você precisar ter mais controle sobre o formato XML gerado quando o tipo for serializado.
Isso talvez seja necessário em alguns cenários de interoperabilidade no qual você precise usar uma construção XML para a qual não haja suporte na serialização do contrato de dados, por exemplo, para gerar atributos XML.
CONSIDERE o suporte à serialização em tempo de execução se as instâncias do tipo precisarem ultrapassar os limites da comunicação remota .NET.
EVITE o suporte à serialização em tempo de execução ou à serialização XML apenas por motivos gerais de persistência. Prefira a serialização do contrato de dados em vez disso
Oferecendo suporte à serialização do contrato de dados
Os tipos podem oferecer suporte à serialização do contrato de dados aplicando o DataContractAttribute ao tipo e o DataMemberAttribute aos membros (campos e propriedades) do tipo.
<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
[DataContract]
class Person
{
[DataMember]
string LastName { get; set; }
[DataMember]
string FirstName { get; set; }
public Person(string firstNameValue, string lastNameValue)
{
FirstName = firstNameValue;
LastName = lastNameValue;
}
}
CONSIDERE marcar os membros de dados do tipo público se o tipo puder ser usado na confiança parcial. Na confiança total, os serializadores do contrato de dados podem serializar e desserializar tipos e membros não públicos, mas somente os membros públicos podem ser serializados e desserializados na confiança parcial.
IMPLEMENTE um getter e um setter em todas as propriedades que têm Data-MemberAttribute. Os serializadores do contrato de dados requerem o getter e o setter para o tipo a ser considerado serializável. Se o tipo não será usado na confiança parcial, um ou ambos os acessadores de propriedade poderão ser não públicos.
<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
[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; } } }
CONSIDERE o uso de retornos de chamada de serialização para a inicialização de instâncias desserializadas.
Os construtores não são chamados quando os objetos são desserializados. Portanto, qualquer lógica que executada durante a construção normal precisa ser implementada como um dos retornos de chamada de serialização.
<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
[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; } }
OnDeserializedAttribute é o atributo de retorno de chamada mais usado. Os outros atributos da família são OnDeserializingAttribute, OnSeralizingAttribute e OnSerializedAttribute. Eles podem ser usados para marcar os retornos de chamada que são executados antes de desserialização, antes da serialização e, por fim, após a serialização, respectivamente.
CONSIDERE o uso do KnownTypeAttribute para indicar os tipos concretos que devem ser usados ao desserializar um gráfico de objeto complexo.
Por exemplo, se o tipo de um membro de dados desserializado for representado por uma classe abstrata, o serializador precisará das informações de tipo conhecido para decidir qual tipo concreto será instanciado e atribuído ao membro. Se o tipo conhecido não for especificado por meio do atributo, ele precisará ser passado para o serializador explicitamente (você pode fazer isso passando os tipos conhecidos para o construtor do serializador) ou precisará ser especificado no arquivo de configuração.
<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
// 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; } } }
Nos casos em que a lista de tipos conhecidos não é conhecida estaticamente (quando a classe Person é compilada), KnownTypeAttribute também pode apontar para um método que retorna uma lista de tipos conhecidos em tempo de execução.
Considere a compatibilidade com versões anteriores e posteriores ao criar ou modificar tipos serializáveis.
Tenha em mente que os fluxos serializados de versões futuras do tipo podem ser desserializados na versão atual de tipo e vice-versa. Verifique se compreendeu que os membros de dados, até mesmo os privados e internos, não podem alterar seus nomes, tipos ou mesmo sua ordem nas versões futuras do tipo, a menos que se tome um cuidado especial para preservar o contrato usando parâmetros explícitos para os atributos do contrato de dados. Teste a compatibilidade da serialização ao fazer alterações nos tipos serializáveis. Tente desserializar a nova versão em uma versão antiga e vice-versa.
CONSIDERE implementar a interface IExtensibleDataObject para permitir o ciclo completo entre diferentes versões do tipo.
A interface permite que o serializador assegure de que nenhum dado sejam perdido durante o ciclo completo. A propriedade ExtensionData armazena todos os dados da versão futura do tipo desconhecido na versão atual. Quando a versão atual for serializada e desserializada subsequentemente em uma versão futura, os dados adicionais estarão disponíveis no fluxo serializado através do valor de propriedade ExtensionData.
<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
// 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; } } }
Para obter mais informações, consulte Contratos de dados compatíveis por encaminhamento.
Suporte à serialização XML
A serialização do contrato de dados é a tecnologia de serialização principal (padrão) do .NET Framework, mas há situações de serialização para as quais a serialização do contrato de dados não oferece suporte. Por exemplo, ela não fornece controle total sobre o formato XML gerado ou consumido pelo serializador. Se esse controle fino for necessário, a serialização XML precisará ser usada, e você precisará criar seus tipos para oferecer suporte essa tecnologia de serialização.
EVITE criar os tipos especificamente para a Serialização XML, a menos que você tenha um motivo muito forte para controlar o formato do XML gerado Essa tecnologia de serialização foi substituída pela serialização do contrato de dados abordada na seção anterior.
Em outras palavras, não aplique atributos do namespace System.Runtime.Serialization a novos tipos, a menos que você saiba que o tipo será usado com a Serialização XML. O exemplo a seguir mostra como System.Xml.Serialization pode ser usado para controlar o formato do XML gerado.
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
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"; }
CONSIDERE implementar a interface IXmlSerializable se quiser ainda mais controle sobre o formato do XML serializado do que o que é oferecido aplicando os atributos de Serialização XML. Dois métodos da interface ReadXmle WriteXml permitem que você controle totalmente o fluxo XML serializável. Você também pode controlar o esquema XML gerado para o tipo aplicando o atributo XmlSchemaProviderAttribute.
Suporte à serialização em tempo de execução
A serialização em tempo de execução é uma tecnologia usada pela comunicação remota .NET. Se você considerar que os tipos serão transportados por meio da comunicação remota .NET, será necessário verificar se eles oferecem suporte à serialização em tempo de execução.
O suporte básico da serialização em tempo de execução pode ser fornecido aplicando o atributo SerializableAttribute, e os cenários mais avançados envolvem a implementação de um padrão serializável de tempo de execução simples (implemente -ISerializable e forneça um construtor de serialização).
CONSIDERE o suporte à serialização em tempo de execução se os tipos forem usados com a comunicação remota .NET. Por exemplo, o namespace System.AddIn usa a comunicação remota .NET e, portanto, todos os tipos trocados entre os suplementos System.AddIn precisam oferecer suporte à serialização em tempo de execução.
<Serializable()> Public Class Person6 ' Support runtime serialization with the SerializableAttribute. ' Code not shown. End Class
// Apply SerializableAttribute to support runtime serialization. [Serializable] public class Person6 { // Code not shown. }
CONSIDERE a implementação do padrão serializável em tempo de execução se quiser controle completo sobre o processo de serialização. Por exemplo, se você quiser transformar os dados à medida que eles forem serializados ou desserializados.
O padrão é muito simples. Tudo o que você precisa fazer é implementar a interface ISerializable e fornecer um construtor especial que é usado quando o objeto é desserializado.
' 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") info.AddValue("name", FullName) End If 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
// 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)); } [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] 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; } } }
PROTEJA o construtor de serialização e forneça dois parâmetros tipados e nomeados exatamente conforme mostrado no exemplo aqui.
Protected Sub New(ByVal info As SerializationInfo, _ ByVal context As StreamingContext)
protected Person_Runtime_Serializable(SerializationInfo info, StreamingContext context){
IMPLEMENTE os membros ISerializable explicitamente.
Private Sub GetObjectData(ByVal info As SerializationInfo, _ ByVal context As StreamingContext) _ Implements ISerializable.GetObjectData
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
APLIQUE uma demanda de link para a implementação de ISerializable.GetObjectData. Isso garantirá que o somente núcleo completamente confiável e o serializador em tempo de execução terão acesso ao membro.
<Serializable()> Public Class Person_Runtime_Serializable2 Implements ISerializable <SecurityPermission(SecurityAction.LinkDemand, Flags:=SecurityPermissionFlag.SerializationFormatter)> _ Private Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, _ ByVal context As System.Runtime.Serialization.StreamingContext) _ Implements System.Runtime.Serialization.ISerializable.GetObjectData ' Code not shown. End Sub End Class
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
Consulte também
Conceitos
Tipos com suporte fornecido pelo serializador de contrato de dados