BinaryFormatter referência de funcionalidade
O BinaryFormatter foi introduzido pela primeira vez com a versão inicial do .NET Framework em 2002. Para entender como substituir o uso do BinaryFormatter, ajuda a saber como BinaryFormatter funciona.
BinaryFormatter pode serializar qualquer instância de qualquer tipo que seja anotada ou [Serializable]
implemente a ISerializable interface.
Nomes dos membros
No cenário mais comum, o tipo é anotado com [Serializable]
e o serializador usa reflexão para serializar todos os campos (públicos e não públicos), exceto aqueles que são anotados com [NonSerialized]
. Por padrão, os nomes de membros serializados corresponderão aos nomes de campo do tipo. Isso historicamente levou a incompatibilidades quando até mesmo campos privados são renomeados em [Serializable]
tipos. Durante migrações para longe do , torna-se necessário entender como os nomes de BinaryFormattercampo serializados foram manipulados e substituídos.
Propriedades automáticas do C#
Para propriedades implementadas automaticamente em C# ({ get; set; }
), BinaryFormatter serializará os campos de suporte gerados pelo compilador C#, não as propriedades. Os nomes desses campos de suporte serializados contêm caracteres C# ilegais e não podem ser controlados. Um descompilador C# (como https://sharplab.io/ ou ILSpy) pode demonstrar como as propriedades automáticas do C# são apresentadas ao tempo de execução.
[Serializable]
internal class PropertySample
{
public string Name { get; set; }
}
A classe anterior é traduzida pelo compilador C# para:
[Serializable]
internal class PropertySample
{
private string <Name>k__BackingField;
public string Name
{
get
{
return <Name>k__BackingField;
}
set
{
<Name>k__BackingField = value;
}
}
}
Nesse caso, <Name>k__BackingField
é o nome do membro que BinaryFormatter
usa na carga serializada. Não é possível usar nameof
ou qualquer outro operador C# para obter esse nome.
A ISerializable interface vem com GetObjectData método que permite aos usuários controlar os nomes, usando um dos AddValue métodos.
// Note lack of any special attribute.
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Name", this.Name);
}
Se essa personalização tiver sido aplicada, as informações também precisarão ser fornecidas durante a desserialização. Isso é possível usando o serialization construtor, onde todos os valores são lidos SerializationInfo com um dos Get
métodos que ele fornece.
private PropertySample(SerializationInfo info, StreamingContext context)
{
this.Name = info.GetString("Name");
}
Nota
O nameof
operador não foi usado propositalmente aqui, pois a carga útil pode ser persistente e a propriedade pode ser renomeada em um momento posterior. Então, mesmo que ele seja renomeado (digamos porque FirstName
você decide também introduzir uma LastName
propriedade), para permanecer retrocompatível, o serialization ainda deve usar o nome antigo que poderia ter sido persistido em algum lugar.
Serialization aglutinante
Recomenda-se usar SerializationBinder para controlar o carregamento de classe e determinar qual classe carregar. Isso minimiza as vulnerabilidades de segurança (para que apenas os tipos permitidos sejam carregados, mesmo que o invasor modifique a carga para desserializar e carregar outra coisa).
Usar esse tipo requer herdar dele e substituir o BindToType método.
Idealmente, a lista de tipos serializáveis é fechada porque significa que você sabe quais tipos podem ser instanciados, o que ajudará a reduzir as vulnerabilidades de segurança.