Condividi tramite


Serializzazione personalizzata

La serializzazione personalizzata è il processo di controllo della serializzazione e deserializzazione di un tipo. Controllando la serializzazione è possibile assicurarne la compatibilità ovvero la capacità di eseguire serializzazioni e deserializzazioni fra le versioni di un tipo senza danneggiarne le funzionalità principali. La prima versione di un tipo, ad esempio, può includere solo due campi. Nella versione successiva vengono aggiunti molti altri campi. La seconda versione di un'applicazione, tuttavia, deve poter serializzare e deserializzare entrambi i tipi. Nelle sezioni seguenti viene illustrato come controllare la serializzazione.

Esecuzione di metodi personalizzati durante e dopo la serializzazione

La procedura consigliata e più semplice, introdotta nella versione 2.0 di .Net Framework, consiste nell'applicare gli attributi seguenti ai metodi utilizzati per correggere i dati durante e dopo la serializzazione:

Questi attributi consentono al tipo di partecipare a una qualsiasi delle fasi dei processi di serializzazione e deserializzazione o a tutte quattro. Gli attributi specificano i metodi del tipo da richiamare durante ogni fase. I metodi non accedono al flusso di serializzazione ma al contrario consentono di modificare l'oggetto prima e dopo la serializzazione o la deserializzazione. Gli attributi possono essere applicati a tutti i livelli della gerarchia di ereditarietà del tipo e ogni metodo viene chiamato nella gerarchia, da quello base a quello più derivato. Questo meccanismo evita la complessità ed eventuali problemi risultanti dall'implementazione dell'interfaccia ISerializable, poiché la responsabilità di serializzazione e deserializzazione viene assegnata all'implementazione maggiormente derivata. Il meccanismo inoltre consente ai formattatori di ignorare il popolamento dei campi e il recupero dal flusso di serializzazione. Per informazioni ed esempi sul controllo della serializzazione e della deserializzazione, fare clic su uno dei collegamenti sopra riportati.

Quando si aggiunge un nuovo campo a un tipo serializzabile esistente, è inoltre necessario applicare al campo l'attributo OptionalFieldAttribute. BinaryFormatter e SoapFormatter ignorano l'assenza del nuovo campo quando viene elaborato un flusso che non lo include.

Implementazione dell'interfaccia ISerializable

L'altro modo per controllare la serializzazione consiste nell'implementare l'interfaccia ISerializable su un oggetto. Si noti comunque che il metodo nella sezione precedente ha la priorità su questo metodo per controllare la serializzazione.

È inoltre consigliabile non utilizzare la serializzazione predefinita su una classe contrassegnata con l'attributo Serializable che dispone di protezione dichiarativa o imperativa a livello di classe o sui relativi costruttori. Per tali classi è invece opportuno implementare sempre l'interfaccia ISerializable.

L'implementazione di ISerializable implica l'implementazione del metodo GetObjectData e di un costruttore speciale, utilizzato quando l'oggetto viene deserializzato. Nel codice di esempio viene illustrato come implementare ISerializable sulla classe MyObject da una sezione precedente.

[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");
  }
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter 
=true)]
  public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
  {
    info.AddValue("i", n1);
    info.AddValue("j", n2);
    info.AddValue("k", str);
  }
}

Quando GetObjectData viene chiamato durante la serializzazione, è necessario popolare SerializationInfo fornito con la chiamata al metodo. È sufficiente aggiungere le variabili da serializzare come coppie di nome e valore. Il nome può essere composto da qualsiasi testo. È possibile scegliere le variabili membro da aggiungere a SerializationInfo, a condizione che venga serializzata una quantità sufficiente di dati per ripristinare l'oggetto durante la deserializzazione. Le classi derivate devono chiamare il metodo GetObjectData sull'oggetto di base se quest'ultimo implementa ISerializable.

Si noti che la serializzazione può consentire ad altro codice di visualizzare o modificare i dati di un'istanza di un oggetto altrimenti non accessibili. Con il codice che esegue la serializzazione è pertanto necessario utilizzare SecurityPermission con il flag SerializationFormatter specificato. In base ai criteri predefiniti, questa autorizzazione non è concessa a codice scaricato da Internet o a codice Intranet, ma solo al codice presente nel computer locale. È necessario proteggere il metodo GetObjectData in modo esplicito richiedendo SecurityPermission con il flag SerializationFormatter specificato oppure richiedendo altre autorizzazioni specifiche per la protezione di dati privati.

Se in un campo privato vengono archiviate informazioni riservate, sarà necessario proteggere i dati richiedendo le autorizzazioni appropriate su GetObjectData. Si noti che con il codice a cui è stato concesso SecurityPermission con il flag SerializationFormatter specificato è possibile visualizzare e modificare i dati archiviati nei campi privati. Un chiamante malintenzionato a cui sia stato concesso SecurityPermission può visualizzare dati quali i percorsi delle directory nascoste o le autorizzazioni concesse e utilizzare tali informazioni per individuare una vulnerabilità di protezione nel computer. Per un elenco completo dei flag delle autorizzazioni di protezione che è possibile specificare, vedere Enumerazione SecurityPermissionFlag.

È importante sottolineare che al momento di aggiungere ISerializable a una classe è necessario implementare sia GetObjectData sia il costruttore speciale. In mancanza di GetObjectData, il compilatore emette un avviso. Poiché è però impossibile imporre l'implementazione di un costruttore, in mancanza del costruttore l'avviso non viene emesso, ma viene generata un'eccezione quando si tenta di deserializzare una classe priva di costruttore.

La progettazione corrente è stata preferita al metodo SetObjectData per risolvere potenziali problemi di protezione e relativi al controllo delle versioni. Un metodo SetObjectData, ad esempio, deve essere pubblico se viene definito come parte di un'interfaccia. È pertanto necessario scrivere codice che impedisca di chiamare il metodo SetObjectData più volte. In caso contrario, un'applicazione dannosa che chiama il metodo SetObjectData su un oggetto durante l'esecuzione di un'operazione potrebbe causare problemi.

Durante la deserializzazione, SerializationInfo viene passato alla classe utilizzando il costruttore fornito per questo specifico scopo. Quando l'oggetto viene deserializzato, qualsiasi vincolo di visibilità apposto al costruttore verrà ignorato. È pertanto possibile contrassegnare la classe come pubblica, protetta, interna o privata. È comunque buona norma rendere il costruttore protetto, a meno che la classe non sia sealed, nel qual caso il costruttore dovrà essere contrassegnato come privato. Nel costruttore deve inoltre essere eseguita una completa convalida dell'input. Per evitare abusi di malware, è consigliabile che il costruttore imponga gli stessi controlli di protezione e le stesse autorizzazioni richieste per ottenere un'istanza della classe utilizzando qualsiasi altro costruttore. Se non si osservano questi consigli, malware potrebbe preserializzare un oggetto, ottenere il controllo con SecurityPermission con il flag SerializationFormatter specificato e deserializzare l'oggetto in un computer client aggirando le protezioni che sarebbero state applicate durante una costruzione di istanza standard eseguita mediate un costruttore pubblico.

Per ripristinare lo stato dell'oggetto, è sufficiente recuperare i valori delle variabili da SerializationInfo mediante i nomi utilizzati durante la serializzazione. Se la classe di base implementa ISerializable, deve essere chiamato il costruttore base per consentire all'oggetto di base di ripristinare le proprie variabili.

Quando una nuova classe viene derivata da una classe che implementa ISerializable, la nuova classe dovrà implementare sia il costruttore sia il metodo GetObjectData, se contiene variabili da serializzare. Nell'esempio di codice seguente vengono illustrate le modalità per eseguire questa operazione utilizzando la classe MyObject descritta in precedenza.

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

  public ObjectTwo() : base()
  {
  }

  protected ObjectTwo(SerializationInfo si, StreamingContext context) : base(si,context)
  {
    num = si.GetInt32("num");
  }
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter 
=true)]
  public override void GetObjectData(SerializationInfo si, StreamingContext context)
  {
    base.GetObjectData(si,context);
    si.AddValue("num", num);
  }
}

È necessario ricordare di chiamare la classe di base nel costruttore di deserializzazione. In caso contrario, il costruttore nella classe di base non viene mai chiamato e l'oggetto non viene costruito completamente dopo la deserializzazione.

Gli oggetti vengono ricostruiti dall'interno verso l'esterno e la chiamata ai metodi durante la deserializzazione può provocare effetti indesiderati, in quanto i metodi chiamati possono riguardare riferimenti a oggetti che non sono stati ancora deserializzati al momento della chiamata. Se la classe deserializzata implementa IDeserializationCallback, il metodo OnDeserialization verrà chiamato automaticamente dopo la deserializzazione dell'intero oggetto grafico. A questo punto tutti gli oggetti figlio a cui si è fatto riferimento sono stati completamente ripristinati. Una tabella hash è un tipico esempio di una classe difficile da deserializzare senza l'utilizzo del listener di eventi. Recuperare le coppie chiave e valore durante la deserializzazione è un'operazione semplice ma aggiungere nuovamente gli oggetti alla tabella hash può causare problemi, poiché non esiste alcuna garanzia che le classi derivate dalla tabella hash siano state deserializzate. Non è quindi consigliabile chiamare i metodi su una tabella hash in questa fase.

Vedere anche

Concetti

Security and Serialization

Altre risorse

Serializzazione binaria
Oggetti remoti
Serializzazione XML e SOAP
Protezione dall'accesso di codice