Serialização binária personalizada

A serialização personalizada é o processo de controlar a serialização e a desserialização de um tipo. Controlando a serialização, é possível garantir a compatibilidade de serialização, que é a capacidade de serializar e desserializar entre versões de um tipo sem interromper a funcionalidade básica do tipo. Por exemplo, na primeira versão de um tipo, pode haver apenas dois campos. Na próxima versão de um tipo, vários outros campos são adicionados. No entanto, a segunda versão de um aplicativo deve ser capaz de serializar e desserializar ambos os tipos. As seções a seguir descrevem como controlar a serialização.

Aviso

A serialização binária pode ser perigosa. Para obter mais informações, confira o Guia de segurança do BinaryFormatter.

Importante

Nas versões anteriores ao .NET Framework 4.0, a serialização de dados de usuário personalizados em um assembly parcialmente confiável foi realizada usando GetObjectData. No .NET Framework versões 4.0 – 4.8, esse método é marcado com o atributo , o que impede a SecurityCriticalAttribute execução em assemblies parcialmente confiáveis. Para contornar esta condição, implemente a interface ISafeSerializationData.

Executar métodos personalizados durante e após a serialização

A maneira recomendada de executar métodos personalizados durante e após a serialização binária é aplicar os seguintes atributos aos métodos usados para corrigir dados durante e após a serialização:

Esses atributos permitem que o tipo participe de qualquer uma ou todas as quatro fases dos processos de serialização e desserialização. Os atributos especificam os métodos do tipo que devem ser chamados durante cada fase. Os métodos não acessam o fluxo de serialização. Em vez disso, permitem alterar o objeto antes e depois da serialização, ou antes e depois da desserialização. Os atributos podem ser aplicados a todos os níveis da hierarquia de herança do tipo e cada método é chamado na hierarquia da base para a mais derivada. Esse mecanismo impede a complexidade e os problemas resultantes de se implementar a interface ISerializable dando a responsabilidade para serialização e desserialização à implementação mais derivada. Além disso, esse mecanismo permite que os formatadores ignorem a população de campos e de recuperação do fluxo de serialização. Para obter detalhes e exemplos de controle de serialização e desserialização, clique em um dos links anteriores.

Além disso, ao adicionar um novo campo a um tipo serializável existente, aplique o atributo OptionalFieldAttribute ao campo. BinaryFormatter e SoapFormatter ignoram a ausência do campo quando um fluxo que não tem o novo campo é processado.

Implementar a interface ISerializable

A outra maneira de controlar a serialização binária é implementar a ISerializable interface em um objeto . Observe, no entanto, que o método na seção anterior substitui esse método para controlar a serialização.

Além disso, você não deve usar a serialização padrão em uma classe que está marcada com o atributo Serializable e que tem segurança declarativa ou imperativa no nível da classe ou em seus construtores. Em vez disso, essas classes sempre devem implementar a interface ISerializable.

A implementação de ISerializable envolve implementar o método GetObjectData e um construtor especial que é usado quando o objeto é desserializado. O código de exemplo a seguir mostra como implementar ISerializable na classe MyObject de uma seção anterior.

[Serializable]
public class MyObject : ISerializable
{
    public int n1;
    public int n2;
    public String str;

    public MyObject()
    {
    }

    protected MyObject(SerializationInfo info, StreamingContext context)
    {
      n1 = info.GetInt32("i");
      n2 = info.GetInt32("j");
      str = info.GetString("k");
    }

    public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("i", n1);
        info.AddValue("j", n2);
        info.AddValue("k", str);
    }
}
<Serializable()>  _
Public Class MyObject
    Implements ISerializable
    Public n1 As Integer
    Public n2 As Integer
    Public str As String

    Public Sub New()
    End Sub

    Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        n1 = info.GetInt32("i")
        n2 = info.GetInt32("j")
        str = info.GetString("k")
    End Sub 'New

    Public Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        info.AddValue("i", n1)
        info.AddValue("j", n2)
        info.AddValue("k", str)
    End Sub
End Class

Quando GetObjectData é chamado durante a serialização, você é responsável por preencher o SerializationInfo fornecido com a chamada de método. Adicione as variáveis a serem serializadas como pares de nome e valor. Qualquer texto pode ser usado como o nome. Você tem a liberdade para decidir quais variáveis de membro serão adicionadas às SerializationInfo, desde que os dados suficientes sejam serializados para restaurar o objeto durante a desserialização. Classes derivadas devem chamar o GetObjectData método no objeto base se o último implementar ISerializable.

É importante ressaltar que, quando ISerializable é adicionado a uma classe, você deve implementar o GetObjectData construtor especial e o . O compilador avisa se GetObjectData está ausente. No entanto, como é impossível impor a implementação de um construtor, nenhum aviso será fornecido se o construtor estiver ausente, e uma exceção é gerada quando é feita uma tentativa de desserializar uma classe sem o construtor.

O projeto atual foi favorecido em relação a um método SetObjectData para solucionar possíveis problemas de segurança e controle de versão. Por exemplo, um SetObjectData método deve ser público se for definido como parte de uma interface; portanto, os usuários devem escrever código para se defenderem de ter o SetObjectData método chamado várias vezes. Caso contrário, um aplicativo mal-intencionado que chama o SetObjectData método em um objeto no processo de execução de uma operação pode causar possíveis problemas.

Durante a desserialização, SerializationInfo é passado para a classe usando o construtor fornecido para essa finalidade. Todas as restrições de visibilidade colocadas no construtor são ignorados quando o objeto é desserializado; assim você pode marcar a classe como pública, protegida, interna ou privada. No entanto, é uma prática recomendada marcar o construtor como protegido a menos que a classe seja fechada; nesse caso, o construtor é marcado particular. O construtor também deve executar a validação completa de entrada.

Para restaurar o estado do objeto, basta recuperar os valores das variáveis de SerializationInfo usando os nomes usados durante a serialização. Se a classe base implementar o ISerializable, o construtor de base deverá ser chamado para permitir que o objeto base restaure suas variáveis.

Quando você deriva uma nova classe de uma que implementa ISerializable, a classe derivada deve implementar tanto o construtor quanto o GetObjectData método se ele tiver variáveis que precisam ser serializadas. O exemplo de código a seguir mostra como isso é feito usando a classe MyObject mostrada anteriormente.

[Serializable]
public class ObjectTwo : MyObject
{
    public int num;

    public ObjectTwo()
      : base()
    {
    }

    protected ObjectTwo(SerializationInfo si, StreamingContext context)
      : base(si, context)
    {
        num = si.GetInt32("num");
    }

    public override void GetObjectData(SerializationInfo si, StreamingContext context)
    {
        base.GetObjectData(si,context);
        si.AddValue("num", num);
    }
}
<Serializable()>  _
Public Class ObjectTwo
    Inherits MyObject
    Public num As Integer

    Public Sub New()

    End Sub

    Protected Sub New(ByVal si As SerializationInfo, _
    ByVal context As StreamingContext)
        MyBase.New(si, context)
        num = si.GetInt32("num")
    End Sub

    Public Overrides Sub GetObjectData(ByVal si As SerializationInfo, ByVal context As StreamingContext)
        MyBase.GetObjectData(si, context)
        si.AddValue("num", num)
    End Sub
End Class

Não se esqueça de chamar a classe base no construtor de desserialização. Se isso não for feito, o construtor na classe base nunca será chamado e o objeto não será totalmente construído após a desserialização.

Os objetos são recriados de dentro para fora; e os métodos de chamada durante a desserialização podem ter efeitos colaterais indesejáveis, porque os métodos chamados podem se referir a referências de objeto que não foram desserializados no momento em que a chamada foi feita. Se a classe que está sendo desserializada implementar o IDeserializationCallback, o método OnDeserialization será chamado automaticamente quando o gráfico do objeto inteiro tiver sido desserializado. Neste ponto, todos os objetos filho referenciados foram restaurados totalmente. Uma tabela de hash é um exemplo típico de uma classe que é difícil desserializar sem usar a escuta de eventos. É fácil recuperar os pares de chave e valor durante a desserialização, mas adicionar esses objetos de volta à tabela de hash pode causar problemas, porque não há nenhuma garantia de que as classes que derivaram da tabela de hash foram desserializadas. Os métodos de chamada em uma tabela de hash nesse estágio são, portanto, não aconselháveis.

Confira também