Partager via


Informations de référence sur les fonctionnalités de BinaryFormatter

Le BinaryFormatter a été introduit pour la première fois avec la version initiale de .NET Framework en 2002. Pour comprendre comment remplacer les utilisations de BinaryFormatter, il peut être utile de savoir comment BinaryFormatter fonctionne.

BinaryFormatter peut sérialiser n’importe quelle instance de n’importe quel type annoté avec [Serializable] ou implémente l’interface ISerializable .

Noms de membres

Dans le scénario le plus courant, le type est annoté avec [Serializable] et le sérialiseur utilise la réflexion pour sérialiser tous les champs (publics et non publics) à l’exception de ceux qui sont annotés avec [NonSerialized]. Par défaut, les noms de membres sérialisés correspondent aux noms de champs du type. Cela a historiquement conduit à des incompatibilités lorsque même les champs privés sont renommés sur les types [Serializable]. Pour faire sans BinaryFormatter, il est nécessaire de comprendre comment les noms de champs ont été gérés et remplacés.

Propriétés automatiques C#

Pour les propriétés{ get; set; } implémentées automatiquement par C#, BinaryFormatter sérialise les champs de stockage générés par le compilateur C#, et non les propriétés. Les noms de ces champs de stockage sérialisés contiennent des caractères C# illégaux et ne peuvent pas être contrôlés. Un décompileur C# (tel que https://sharplab.io/ ou ILSpy) peut démontrer comment les propriétés automatiques C# sont présentées à l'exécution.

[Serializable]
internal class PropertySample
{
    public string Name { get; set; }
}

La classe précédente est traduite par le compilateur C# de la manière suivante :

[Serializable]
internal class PropertySample
{
    private string <Name>k__BackingField;

    public string Name
    {
        get
        {
            return <Name>k__BackingField;
        }
        set
        {
            <Name>k__BackingField = value;
        }
    }
}

Dans ce cas, <Name>k__BackingField est le nom de membre que BinaryFormatter utilise dans la charge utile sérialisée. Il n’est pas possible d’utiliser nameof ni aucun autre opérateur C# pour obtenir ce nom.

L’interface ISerializable est fournie avec une méthode GetObjectData qui permet aux utilisateurs de contrôler les noms à l’aide de l’une des méthodes AddValue.

// Note lack of any special attribute.
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
    info.AddValue("Name", this.Name);
}

Si cette personnalisation a été appliquée, les informations doivent également être fournies lors de la désérialisation. Ceci est possible à l’aide du constructeur de serialization, où toutes les valeurs sont lues dans SerializationInfo avec l’une des méthodes Get fournies.

private PropertySample(SerializationInfo info, StreamingContext context)
{
    this.Name = info.GetString("Name");
}

Remarque

L’opérateur nameof n'a volontairement pas été utilisé ici, car la charge utile peut être conservée et la propriété peut être renommée ultérieurement. Ainsi, même si elle est renommée (par exemple en FirstName parce que vous décidez d'introduire également une propriété LastName), pour assurer la rétrocompatibilité, la serialization doit toujours utiliser l’ancien nom qui a pu être conservé quelque part.

SerializationBinder

Il est recommandé d’utiliser le SerializationBinder pour contrôler le chargement des classes et mandater la classe à charger. Cela minimise les vulnérabilités en matière de sécurité (seuls les types autorisés sont chargés, même si l'attaquant modifie la charge utile pour la désérialiser et charger quelque chose d'autre).

Pour utiliser ce type, il faut en hériter et remplacer la méthode BindToType.

Idéalement, la liste des types sérialisables est close, car cela signifie que vous savez quels types peuvent être instanciés, ce qui contribue à réduire les vulnérabilités de sécurité.